summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatija Čupić <matteeyah@gmail.com>2017-12-22 20:04:54 +0100
committerMatija Čupić <matteeyah@gmail.com>2017-12-22 20:04:54 +0100
commitb3f27aed775232200533a4df2a6e7cc70be13574 (patch)
tree2d998e4677f8bd81077f4b8ce70c2479d376510e
parentf8c044bad3266ee4d31b9606024f4572d1fac3bd (diff)
parent46be07d2c1da93cffd59fb03036e357c04ff6f3b (diff)
downloadgitlab-ce-b3f27aed775232200533a4df2a6e7cc70be13574.tar.gz
Merge branch 'master' into 41249-clearing-the-cache
-rw-r--r--.codeclimate.yml1
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--.rubocop.yml1216
-rw-r--r--.rubocop_todo.yml714
-rw-r--r--.ruby-version2
-rw-r--r--CHANGELOG.md149
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock26
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/icons.json2
-rw-r--r--app/assets/images/icons.svg2
-rw-r--r--app/assets/images/illustrations/job_not_triggered.svg1
-rw-r--r--app/assets/images/illustrations/manual_action.svg1
-rw-r--r--app/assets/images/illustrations/merge_request_changes_empty.svg2
-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/javascripts/api.js14
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js2
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue20
-rw-r--r--app/assets/javascripts/clusters/services/clusters_service.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue7
-rw-r--r--app/assets/javascripts/dispatcher.js9
-rw-r--r--app/assets/javascripts/fly_out_nav.js17
-rw-r--r--app/assets/javascripts/helpers/user_feature_helper.js7
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue66
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue (renamed from app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue)0
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue (renamed from app/assets/javascripts/repo/components/commit_sidebar/list_item.vue)0
-rw-r--r--app/assets/javascripts/ide/components/ide.vue73
-rw-r--r--app/assets/javascripts/ide/components/ide_context_bar.vue75
-rw-r--r--app/assets/javascripts/ide/components/ide_project_branches_tree.vue47
-rw-r--r--app/assets/javascripts/ide/components/ide_project_tree.vue47
-rw-r--r--app/assets/javascripts/ide/components/ide_repo_tree.vue66
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue62
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue71
-rw-r--r--app/assets/javascripts/ide/components/new_branch_form.vue (renamed from app/assets/javascripts/repo/components/new_branch_form.vue)2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue101
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue (renamed from app/assets/javascripts/repo/components/new_dropdown/modal.vue)16
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue (renamed from app/assets/javascripts/repo/components/new_dropdown/upload.vue)35
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue (renamed from app/assets/javascripts/repo/components/repo_commit_section.vue)45
-rw-r--r--app/assets/javascripts/ide/components/repo_edit_button.vue (renamed from app/assets/javascripts/repo/components/repo_edit_button.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue (renamed from app/assets/javascripts/repo/components/repo_editor.vue)42
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue (renamed from app/assets/javascripts/repo/components/repo_file.vue)74
-rw-r--r--app/assets/javascripts/ide/components/repo_file_buttons.vue (renamed from app/assets/javascripts/repo/components/repo_file_buttons.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue (renamed from app/assets/javascripts/repo/components/repo_loading_file.vue)8
-rw-r--r--app/assets/javascripts/ide/components/repo_prev_directory.vue (renamed from app/assets/javascripts/repo/components/repo_prev_directory.vue)8
-rw-r--r--app/assets/javascripts/ide/components/repo_preview.vue (renamed from app/assets/javascripts/repo/components/repo_preview.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue (renamed from app/assets/javascripts/repo/components/repo_tab.vue)6
-rw-r--r--app/assets/javascripts/ide/components/repo_tabs.vue (renamed from app/assets/javascripts/repo/components/repo_tabs.vue)0
-rw-r--r--app/assets/javascripts/ide/ide_router.js101
-rw-r--r--app/assets/javascripts/ide/index.js55
-rw-r--r--app/assets/javascripts/ide/lib/common/disposable.js (renamed from app/assets/javascripts/repo/lib/common/disposable.js)0
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js (renamed from app/assets/javascripts/repo/lib/common/model.js)8
-rw-r--r--app/assets/javascripts/ide/lib/common/model_manager.js (renamed from app/assets/javascripts/repo/lib/common/model_manager.js)0
-rw-r--r--app/assets/javascripts/ide/lib/decorations/controller.js (renamed from app/assets/javascripts/repo/lib/decorations/controller.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/controller.js (renamed from app/assets/javascripts/repo/lib/diff/controller.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff.js (renamed from app/assets/javascripts/repo/lib/diff/diff.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff_worker.js (renamed from app/assets/javascripts/repo/lib/diff/diff_worker.js)0
-rw-r--r--app/assets/javascripts/ide/lib/editor.js (renamed from app/assets/javascripts/repo/lib/editor.js)30
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js (renamed from app/assets/javascripts/repo/lib/editor_options.js)0
-rw-r--r--app/assets/javascripts/ide/monaco_loader.js (renamed from app/assets/javascripts/repo/monaco_loader.js)0
-rw-r--r--app/assets/javascripts/ide/services/index.js (renamed from app/assets/javascripts/repo/services/index.js)7
-rw-r--r--app/assets/javascripts/ide/stores/actions.js179
-rw-r--r--app/assets/javascripts/ide/stores/actions/branch.js43
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js (renamed from app/assets/javascripts/repo/stores/actions/file.js)41
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js25
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js188
-rw-r--r--app/assets/javascripts/ide/stores/getters.js19
-rw-r--r--app/assets/javascripts/ide/stores/index.js (renamed from app/assets/javascripts/repo/stores/index.js)0
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js (renamed from app/assets/javascripts/repo/stores/mutation_types.js)19
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js (renamed from app/assets/javascripts/repo/stores/mutations.js)18
-rw-r--r--app/assets/javascripts/ide/stores/mutations/branch.js28
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js (renamed from app/assets/javascripts/repo/stores/mutations/file.js)20
-rw-r--r--app/assets/javascripts/ide/stores/mutations/project.js23
-rw-r--r--app/assets/javascripts/ide/stores/mutations/tree.js (renamed from app/assets/javascripts/repo/stores/mutations/tree.js)9
-rw-r--r--app/assets/javascripts/ide/stores/state.js (renamed from app/assets/javascripts/repo/stores/state.js)20
-rw-r--r--app/assets/javascripts/ide/stores/utils.js (renamed from app/assets/javascripts/repo/stores/utils.js)64
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js18
-rw-r--r--app/assets/javascripts/new_commit_form.js7
-rw-r--r--app/assets/javascripts/pages/users/show/index.js3
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue11
-rw-r--r--app/assets/javascripts/preview_markdown.js6
-rw-r--r--app/assets/javascripts/repo/components/commit_sidebar/list.vue89
-rw-r--r--app/assets/javascripts/repo/components/new_dropdown/index.vue89
-rw-r--r--app/assets/javascripts/repo/components/repo.vue63
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue85
-rw-r--r--app/assets/javascripts/repo/index.js106
-rw-r--r--app/assets/javascripts/repo/stores/actions.js146
-rw-r--r--app/assets/javascripts/repo/stores/actions/branch.js20
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js163
-rw-r--r--app/assets/javascripts/repo/stores/getters.js40
-rw-r--r--app/assets/javascripts/repo/stores/mutations/branch.js9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js11
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue103
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue8
-rw-r--r--app/assets/stylesheets/framework/contextual-sidebar.scss1
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss31
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/framework/wells.scss6
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss4
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss6
-rw-r--r--app/assets/stylesheets/pages/repo.scss239
-rw-r--r--app/controllers/ide_controller.rb6
-rw-r--r--app/controllers/projects/blob_controller.rb5
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb45
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb10
-rw-r--r--app/models/clusters/applications/helm.rb17
-rw-r--r--app/models/clusters/applications/ingress.rb23
-rw-r--r--app/models/clusters/applications/prometheus.rb26
-rw-r--r--app/models/clusters/cluster.rb7
-rw-r--r--app/models/clusters/concerns/application_core.rb29
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/discussion_on_diff.rb10
-rw-r--r--app/models/concerns/note_on_diff.rb4
-rw-r--r--app/models/concerns/project_features_compatibility.rb1
-rw-r--r--app/models/diff_note.rb6
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/identity.rb10
-rw-r--r--app/models/legacy_diff_note.rb6
-rw-r--r--app/models/project.rb16
-rw-r--r--app/serializers/merge_request_serializer.rb2
-rw-r--r--app/services/clusters/applications/base_helm_service.rb2
-rw-r--r--app/services/files/base_service.rb14
-rw-r--r--app/services/files/delete_service.rb10
-rw-r--r--app/services/files/multi_service.rb13
-rw-r--r--app/services/files/update_service.rb21
-rw-r--r--app/services/search/global_service.rb5
-rw-r--r--app/services/search/group_service.rb1
-rw-r--r--app/views/admin/groups/index.html.haml18
-rw-r--r--app/views/ide/index.html.haml12
-rw-r--r--app/views/layouts/nav_only.html.haml13
-rw-r--r--app/views/projects/_files.html.haml2
-rw-r--r--app/views/projects/_last_push.html.haml27
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/blob/_header.html.haml1
-rw-r--r--app/views/projects/blob/show.html.haml15
-rw-r--r--app/views/projects/blob/viewers/_download.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml3
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/clusters/show.html.haml1
-rw-r--r--app/views/projects/commit/_limit_exceeded_message.html.haml2
-rw-r--r--app/views/projects/deployments/_commit.html.haml2
-rw-r--r--app/views/projects/forks/error.html.haml3
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/tree/_old_tree_content.html.haml24
-rw-r--r--app/views/projects/tree/_old_tree_header.html.haml64
-rw-r--r--app/views/projects/tree/_tree_content.html.haml29
-rw-r--r--app/views/projects/tree/_tree_header.html.haml78
-rw-r--r--app/views/projects/tree/show.html.haml8
-rw-r--r--app/views/shared/_ref_switcher.html.haml2
-rw-r--r--app/views/shared/groups/_dropdown.html.haml5
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/repo/_repo.html.haml13
-rw-r--r--changelogs/unreleased/13634-broadcast-message.yml5
-rw-r--r--changelogs/unreleased/15588-fix-empty-dropdown-on-create-new-pipeline-in-case-of-validation-errors.yml5
-rw-r--r--changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml5
-rw-r--r--changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml5
-rw-r--r--changelogs/unreleased/15955-improve-search-query.yml5
-rw-r--r--changelogs/unreleased/18040-rubocop-line-break-after-guard-clause.yml5
-rw-r--r--changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml5
-rw-r--r--changelogs/unreleased/22680-unlabel-slash-command-limit-autocomplete-to-applied-labels.yml5
-rw-r--r--changelogs/unreleased/28377-add-edit-button-to-mobile-file-view.yml5
-rw-r--r--changelogs/unreleased/29483-no-feedback-when-checking-on-checklist-if-potential-spam-was-detected.yml5
-rw-r--r--changelogs/unreleased/32057_support_ordering_project_notes_in_notes_api.yml5
-rw-r--r--changelogs/unreleased/32098-pipelines-navigation.yml6
-rw-r--r--changelogs/unreleased/32878-merge-request-from-email.yml5
-rw-r--r--changelogs/unreleased/33338-internationalization-support-for-prometheus-service-configuration.yml5
-rw-r--r--changelogs/unreleased/33609-hide-pagination.yml5
-rw-r--r--changelogs/unreleased/34600-performance-wiki-pages.yml5
-rw-r--r--changelogs/unreleased/35385-allow-git-pull-push-on-project-redirects.yml5
-rw-r--r--changelogs/unreleased/35616-move-k8-to-cluster-page.yml5
-rw-r--r--changelogs/unreleased/35724-animate-sidebar.yml5
-rw-r--r--changelogs/unreleased/36400-trigger-job.yml5
-rw-r--r--changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml5
-rw-r--r--changelogs/unreleased/37354-pipelines-update.yml5
-rw-r--r--changelogs/unreleased/38032-deploy-markers-should-be-more-verbose.yml5
-rw-r--r--changelogs/unreleased/38075_allow_refernce_integer_labels.yml5
-rw-r--r--changelogs/unreleased/38393-Milestone-duration-error-message-is-not-accurate-enough.yml5
-rw-r--r--changelogs/unreleased/38822-oauth-search-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/38862-email-notifications-not-sent-as-expected.yml6
-rw-r--r--changelogs/unreleased/38869-templates.yml5
-rw-r--r--changelogs/unreleased/38877-disable-autocomplete-in-filtered-search.yml5
-rw-r--r--changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml5
-rw-r--r--changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml5
-rw-r--r--changelogs/unreleased/39335-add-time-spend-to-milestones.yml5
-rw-r--r--changelogs/unreleased/39364-in-issue-board-url-doesn-t-take-in-account-hostname-settings.yml5
-rw-r--r--changelogs/unreleased/39367-fix-new-email-session-path.yml5
-rw-r--r--changelogs/unreleased/39436-pages-api-administrative.yml5
-rw-r--r--changelogs/unreleased/39455-clone-dropdown-should-not-have-a-tooltip.yml5
-rw-r--r--changelogs/unreleased/39497-inline-edit-issue-on-mobile.yml5
-rw-r--r--changelogs/unreleased/39601-create-issuable-destroy-service.yml5
-rw-r--r--changelogs/unreleased/39602-move-update-project-counter-caches-out-of-issues-merge-requests.yml5
-rw-r--r--changelogs/unreleased/39720-group-milestone-sorting.yml5
-rw-r--r--changelogs/unreleased/39727-add-axios-to-common.yml5
-rw-r--r--changelogs/unreleased/39821-fix-commits-list-with-multi-file-editor.yml5
-rw-r--r--changelogs/unreleased/39827-fix-projects-dropdown-overflow.yml5
-rw-r--r--changelogs/unreleased/39869_show_closed_status_of_links_to_issues_on_wiki_pages.yml5
-rw-r--r--changelogs/unreleased/39884-fix-pipeline-transition-with-single-manual-action.yml6
-rw-r--r--changelogs/unreleased/39895-cant-set-mattermost-username-channel-from-api.yml5
-rw-r--r--changelogs/unreleased/39977-gitlab-shell-default-timeout.yml5
-rw-r--r--changelogs/unreleased/40016-log-header.yml5
-rw-r--r--changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml5
-rw-r--r--changelogs/unreleased/40063-markdown-editor-improvements.yml5
-rw-r--r--changelogs/unreleased/40122-only-one-note-webhook-is-triggered-when-a-comment-with-time-spent-is-added.yml5
-rw-r--r--changelogs/unreleased/40146_fix_special_charecter_search_in_filenames.yml5
-rw-r--r--changelogs/unreleased/40161-extra-margin-on-svg-logo-in-header.yml5
-rw-r--r--changelogs/unreleased/40286-hide-full-namespace-groups-tree.yml6
-rw-r--r--changelogs/unreleased/40290-remove-rake-gitlab-sidekiq-drop-post-receive.yml5
-rw-r--r--changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml6
-rw-r--r--changelogs/unreleased/40481-bump-jquery-to-2-2-4.yml5
-rw-r--r--changelogs/unreleased/40508-snippets-zen-mode.yml5
-rw-r--r--changelogs/unreleased/40530-merge-request-generates-wrong-diff-when-branch-and-tag-have-the-same-name.yml5
-rw-r--r--changelogs/unreleased/40561-environment-scope-value-is-not-trimmed.yml5
-rw-r--r--changelogs/unreleased/40568-bump-seed-fu-to-2-3-7.yml5
-rw-r--r--changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml5
-rw-r--r--changelogs/unreleased/40770-doc-elasticsearch.yml5
-rw-r--r--changelogs/unreleased/4080-align-retry-btn.yml5
-rw-r--r--changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml5
-rw-r--r--changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml5
-rw-r--r--changelogs/unreleased/add_project_ci_config_path_leading_slash_validation.yml6
-rw-r--r--changelogs/unreleased/admin-welcome-new-group-link.yml5
-rw-r--r--changelogs/unreleased/an-gitaly-timeouts.yml5
-rw-r--r--changelogs/unreleased/brand_header_change.yml5
-rw-r--r--changelogs/unreleased/bvl-circuitbreaker-process.yml5
-rw-r--r--changelogs/unreleased/bvl-delete-empty-fork-networks.yml5
-rw-r--r--changelogs/unreleased/bvl-limit-fork-queries-on-project-show.yml5
-rw-r--r--changelogs/unreleased/bvl-subgroup-in-dropdowns.yml5
-rw-r--r--changelogs/unreleased/cleanup-issues-schema.yml5
-rw-r--r--changelogs/unreleased/commit-title-wrapping.yml5
-rw-r--r--changelogs/unreleased/deploy-keys-loading-icon.yml5
-rw-r--r--changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml5
-rw-r--r--changelogs/unreleased/dm-diff-note-for-line-performance.yml5
-rw-r--r--changelogs/unreleased/dm-image-blob-diff-full-url.yml5
-rw-r--r--changelogs/unreleased/dm-issue-move-transaction-error.yml5
-rw-r--r--changelogs/unreleased/dm-ldap-email-readonly.yml5
-rw-r--r--changelogs/unreleased/dm-search-pattern.yml5
-rw-r--r--changelogs/unreleased/enable-scss-lint-unnecessary-mantissa.yml5
-rw-r--r--changelogs/unreleased/feature-custom-text-for-new-projects.yml5
-rw-r--r--changelogs/unreleased/feature-disable-password-authentication.yml5
-rw-r--r--changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml5
-rw-r--r--changelogs/unreleased/feature_add_mermaid.yml5
-rw-r--r--changelogs/unreleased/fix-event-target-author-preloading.yml5
-rw-r--r--changelogs/unreleased/fix-filter-by-my-reaction.yml5
-rw-r--r--changelogs/unreleased/fix-last-push-event-widget-layout.yml5
-rw-r--r--changelogs/unreleased/fix-new-project-guidelines-styling.yml5
-rw-r--r--changelogs/unreleased/fix-protected-branches-descriptions.yml5
-rw-r--r--changelogs/unreleased/fix-sm-31771-do-not-allow-jobs-to-be-erased-new.yml5
-rw-r--r--changelogs/unreleased/fix-sm-37991-avoid-deactivation-when-pipeline-schedules-execute-a-commit-includes-ci-skip.yml6
-rw-r--r--changelogs/unreleased/fix-todos-last-page.yml5
-rw-r--r--changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml5
-rw-r--r--changelogs/unreleased/fj-40407-missing-order-paginate.yml5
-rw-r--r--changelogs/unreleased/fj-40752-forks-api-not-using-services.yml5
-rw-r--r--changelogs/unreleased/fl-upgrade-svg.yml5
-rw-r--r--changelogs/unreleased/group-new-miletone-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/hashed-storage-attachments-migration-path.yml5
-rw-r--r--changelogs/unreleased/improved-changes-dropdown.yml5
-rw-r--r--changelogs/unreleased/index-namespaces-lower-name.yml5
-rw-r--r--changelogs/unreleased/issue_39238.yml5
-rw-r--r--changelogs/unreleased/merge-request-lock-icon-size-fix.yml5
-rw-r--r--changelogs/unreleased/merge-requests-schema-cleanup.yml5
-rw-r--r--changelogs/unreleased/mk-add-old-attachments-to-uploads-table.yml5
-rw-r--r--changelogs/unreleased/mk-add-user-rate-limits.yml6
-rw-r--r--changelogs/unreleased/mk-fix-schema-dump-of-untracked-files-for-uploads.yml5
-rw-r--r--changelogs/unreleased/optimize-projects-for-imported-projects.yml6
-rw-r--r--changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml5
-rw-r--r--changelogs/unreleased/outdated-browser-position-fix.yml5
-rw-r--r--changelogs/unreleased/patch-24.yml5
-rw-r--r--changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml5
-rw-r--r--changelogs/unreleased/pawel-update_prometheus_gem_to_well_tested_version.yml5
-rw-r--r--changelogs/unreleased/perform-sql-matching-of-tags.yml5
-rw-r--r--changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml5
-rw-r--r--changelogs/unreleased/skip_confirmation_user_API.yml7
-rw-r--r--changelogs/unreleased/text-utils.yml5
-rw-r--r--changelogs/unreleased/throttle-touching-of-objects.yml5
-rw-r--r--changelogs/unreleased/tm-feature-list-runners-jobs-api.yml5
-rw-r--r--changelogs/unreleased/tm-feature-namespace-by-id-api.yml5
-rw-r--r--changelogs/unreleased/update-emoji-digests-with-latest-from-gemojione.yml6
-rw-r--r--changelogs/unreleased/update-merge-worker-metrics.yml5
-rw-r--r--changelogs/unreleased/update_mr_changes_empty_page.yml5
-rw-r--r--changelogs/unreleased/use-count_commits-directly.yml5
-rw-r--r--changelogs/unreleased/use-merge-requests-diff-id-column.yml5
-rw-r--r--changelogs/unreleased/user-agent-gke-api.yml5
-rw-r--r--changelogs/unreleased/winh-subgroups-api.yml5
-rw-r--r--changelogs/unreleased/zj-commit-show-n-1.yml5
-rw-r--r--changelogs/unreleased/zj-empty-repo-importer.yml5
-rw-r--r--changelogs/unreleased/zj-memoization-mr-commits.yml5
-rw-r--r--config/initializers/forbid_sidekiq_in_transactions.rb8
-rw-r--r--config/initializers/peek.rb2
-rw-r--r--config/routes.rb2
-rw-r--r--config/webpack.config.js19
-rw-r--r--db/migrate/20171212203433_create_clusters_applications_prometheus.rb18
-rw-r--r--db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb30
-rw-r--r--db/post_migrate/20171219121201_normalize_extern_uid_from_identities.rb29
-rw-r--r--db/schema.rb11
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png (renamed from doc/articles/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png)bin27877 -> 27877 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif (renamed from doc/articles/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif)bin222162 -> 222162 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/user_auth.gif (renamed from doc/articles/how_to_configure_ldap_gitlab_ce/img/user_auth.gif)bin110971 -> 110971 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md267
-rw-r--r--doc/administration/auth/ldap.md3
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/api/repository_files.md1
-rw-r--r--doc/articles/artifactory_and_gitlab/index.md279
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md268
-rw-r--r--doc/articles/index.md11
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/docker/using_docker_build.md15
-rw-r--r--doc/ci/examples/README.md33
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md282
-rw-r--r--doc/ci/examples/browser_performance.md50
-rw-r--r--doc/development/doc_styleguide.md4
-rw-r--r--doc/development/ux_guide/img/james-mackey.pngbin11147 -> 32552 bytes
-rw-r--r--doc/development/ux_guide/img/karolina-plaskaty.pngbin33498 -> 29553 bytes
-rw-r--r--doc/development/ux_guide/img/matthieu-poirier.pngbin0 -> 32819 bytes
-rw-r--r--doc/development/ux_guide/img/nazim-ramesh.pngbin31163 -> 27475 bytes
-rw-r--r--doc/development/ux_guide/users.md291
-rw-r--r--doc/install/installation.md8
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/img/auto_devops_settings.pngbin95233 -> 0 bytes
-rw-r--r--doc/topics/autodevops/index.md23
-rw-r--r--doc/update/10.1-to-10.2.md4
-rw-r--r--doc/update/10.2-to-10.3.md4
-rw-r--r--doc/update/10.3-to-10.4.md360
-rw-r--r--doc/user/group/img/transfer_project_to_other_group.pngbin66460 -> 0 bytes
-rw-r--r--doc/user/group/index.md56
-rw-r--r--doc/user/profile/index.md94
-rw-r--r--doc/user/project/index.md32
-rw-r--r--doc/user/project/integrations/samples/prometheus.yml2
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md7
-rw-r--r--doc/user/project/settings/index.md73
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md2
-rw-r--r--features/steps/project/source/browse_files.rb11
-rw-r--r--lib/after_commit_queue.rb10
-rw-r--r--lib/banzai/filter/relative_link_filter.rb46
-rw-r--r--lib/banzai/filter/upload_link_filter.rb60
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/gitlab/ci/config/entry/node.rb4
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/git/repository.rb11
-rw-r--r--lib/gitlab/kubernetes/helm.rb90
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb42
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb53
-rw-r--r--lib/gitlab/kubernetes/helm/pod.rb69
-rw-r--r--lib/gitlab/metrics/influx_db.rb1
-rw-r--r--lib/gitlab/metrics/method_call.rb12
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb2
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb2
-rw-r--r--lib/gitlab/metrics/system.rb16
-rw-r--r--lib/gitlab/metrics/transaction.rb8
-rw-r--r--lib/gitlab/search_results.rb15
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
-rw-r--r--package.json3
-rw-r--r--qa/qa.rb2
-rw-r--r--qa/qa/factory/base.rb27
-rw-r--r--qa/qa/factory/dependency.rb38
-rw-r--r--qa/qa/factory/product.rb26
-rw-r--r--qa/qa/factory/repository/push.rb19
-rw-r--r--qa/qa/factory/resource/group.rb22
-rw-r--r--qa/qa/factory/resource/project.rb19
-rw-r--r--qa/qa/factory/resource/sandbox.rb16
-rw-r--r--qa/qa/git/repository.rb2
-rw-r--r--qa/qa/page/group/show.rb2
-rw-r--r--qa/qa/specs/features/repository/push_spec.rb15
-rw-r--r--qa/spec/factory/base_spec.rb88
-rw-r--r--qa/spec/factory/dependency_spec.rb59
-rw-r--r--qa/spec/factory/product_spec.rb44
-rw-r--r--rubocop/cop/active_record_dependent.rb26
-rw-r--r--rubocop/cop/active_record_serialize.rb18
-rw-r--r--rubocop/cop/custom_error_class.rb64
-rw-r--r--rubocop/cop/gem_fetcher.rb37
-rw-r--r--rubocop/cop/gitlab/module_with_instance_variables.rb4
-rw-r--r--rubocop/cop/in_batches.rb16
-rw-r--r--rubocop/cop/include_sidekiq_worker.rb4
-rw-r--r--rubocop/cop/line_break_after_guard_clauses.rb100
-rw-r--r--rubocop/cop/migration/add_column.rb2
-rw-r--r--rubocop/cop/migration/add_concurrent_foreign_key.rb2
-rw-r--r--rubocop/cop/migration/add_concurrent_index.rb2
-rw-r--r--rubocop/cop/migration/add_index.rb2
-rw-r--r--rubocop/cop/migration/add_timestamps.rb2
-rw-r--r--rubocop/cop/migration/datetime.rb4
-rw-r--r--rubocop/cop/migration/hash_index.rb2
-rw-r--r--rubocop/cop/migration/remove_column.rb2
-rw-r--r--rubocop/cop/migration/remove_concurrent_index.rb2
-rw-r--r--rubocop/cop/migration/remove_index.rb2
-rw-r--r--rubocop/cop/migration/reversible_add_column_with_default.rb4
-rw-r--r--rubocop/cop/migration/safer_boolean_column.rb4
-rw-r--r--rubocop/cop/migration/timestamps.rb2
-rw-r--r--rubocop/cop/migration/update_column_in_batches.rb2
-rw-r--r--rubocop/cop/migration/update_large_table.rb4
-rw-r--r--rubocop/cop/polymorphic_associations.rb23
-rw-r--r--rubocop/cop/project_path_helper.rb2
-rw-r--r--rubocop/cop/redirect_with_status.rb44
-rw-r--r--rubocop/cop/rspec/env_assignment.rb5
-rw-r--r--rubocop/cop/rspec/single_line_hook.rb38
-rw-r--r--rubocop/cop/rspec/verbose_include_metadata.rb74
-rw-r--r--rubocop/cop/sidekiq_options_queue.rb4
-rw-r--r--rubocop/model_helpers.rb11
-rw-r--r--rubocop/rubocop.rb13
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/factories/clusters/applications/helm.rb5
-rw-r--r--spec/factories/clusters/applications/ingress.rb35
-rw-r--r--spec/factories/issues.rb1
-rw-r--r--spec/features/merge_requests/image_diff_notes_spec.rb3
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb15
-rw-r--r--spec/features/projects/clusters/applications_spec.rb2
-rw-r--r--spec/features/projects/ref_switcher_spec.rb78
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb26
-rw-r--r--spec/features/projects/tree/create_file_spec.rb16
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb9
-rw-r--r--spec/finders/group_descendants_finder_spec.rb35
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js5
-rw-r--r--spec/javascripts/clusters/services/mock_data.js6
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js7
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js14
-rw-r--r--spec/javascripts/monitoring/mock_data.js4
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js4
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_item_spec.js2
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_spec.js29
-rw-r--r--spec/javascripts/repo/components/ide_context_bar_spec.js49
-rw-r--r--spec/javascripts/repo/components/ide_repo_tree_spec.js (renamed from spec/javascripts/repo/components/repo_sidebar_spec.js)23
-rw-r--r--spec/javascripts/repo/components/ide_side_bar_spec.js43
-rw-r--r--spec/javascripts/repo/components/ide_spec.js (renamed from spec/javascripts/repo/components/repo_spec.js)12
-rw-r--r--spec/javascripts/repo/components/new_branch_form_spec.js4
-rw-r--r--spec/javascripts/repo/components/new_dropdown/index_spec.js17
-rw-r--r--spec/javascripts/repo/components/new_dropdown/modal_spec.js149
-rw-r--r--spec/javascripts/repo/components/new_dropdown/upload_spec.js81
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js33
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js8
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js6
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js13
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js5
-rw-r--r--spec/javascripts/repo/components/repo_prev_directory_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_preview_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js10
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js4
-rw-r--r--spec/javascripts/repo/helpers.js5
-rw-r--r--spec/javascripts/repo/lib/common/disposable_spec.js2
-rw-r--r--spec/javascripts/repo/lib/common/model_manager_spec.js4
-rw-r--r--spec/javascripts/repo/lib/common/model_spec.js4
-rw-r--r--spec/javascripts/repo/lib/decorations/controller_spec.js8
-rw-r--r--spec/javascripts/repo/lib/diff/controller_spec.js12
-rw-r--r--spec/javascripts/repo/lib/diff/diff_spec.js2
-rw-r--r--spec/javascripts/repo/lib/editor_options_spec.js2
-rw-r--r--spec/javascripts/repo/lib/editor_spec.js4
-rw-r--r--spec/javascripts/repo/monaco_loader_spec.js2
-rw-r--r--spec/javascripts/repo/stores/actions/branch_spec.js20
-rw-r--r--spec/javascripts/repo/stores/actions/file_spec.js75
-rw-r--r--spec/javascripts/repo/stores/actions/tree_spec.js326
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js95
-rw-r--r--spec/javascripts/repo/stores/getters_spec.js38
-rw-r--r--spec/javascripts/repo/stores/mutations/branch_spec.js6
-rw-r--r--spec/javascripts/repo/stores/mutations/file_spec.js4
-rw-r--r--spec/javascripts/repo/stores/mutations/tree_spec.js4
-rw-r--r--spec/javascripts/repo/stores/mutations_spec.js40
-rw-r--r--spec/javascripts/repo/stores/utils_spec.js43
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js16
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb81
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb133
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb (renamed from spec/lib/gitlab/kubernetes/helm_spec.rb)49
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb111
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb86
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb11
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb8
-rw-r--r--spec/lib/gitlab/search_results_spec.rb11
-rw-r--r--spec/models/clusters/applications/helm_spec.rb12
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb102
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb16
-rw-r--r--spec/models/clusters/cluster_spec.rb10
-rw-r--r--spec/models/diff_note_spec.rb16
-rw-r--r--spec/models/identity_spec.rb27
-rw-r--r--spec/rubocop/cop/active_record_dependent_spec.rb33
-rw-r--r--spec/rubocop/cop/active_record_serialize_spec.rb33
-rw-r--r--spec/rubocop/cop/custom_error_class_spec.rb111
-rw-r--r--spec/rubocop/cop/gem_fetcher_spec.rb46
-rw-r--r--spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb4
-rw-r--r--spec/rubocop/cop/in_batches_spec.rb19
-rw-r--r--spec/rubocop/cop/include_sidekiq_worker_spec.rb6
-rw-r--r--spec/rubocop/cop/line_break_after_guard_clauses_spec.rb160
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_index_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/add_timestamps_spec.rb12
-rw-r--r--spec/rubocop/cop/migration/datetime_spec.rb16
-rw-r--r--spec/rubocop/cop/migration/hash_index_spec.rb8
-rw-r--r--spec/rubocop/cop/migration/remove_column_spec.rb10
-rw-r--r--spec/rubocop/cop/migration/remove_concurrent_index_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/remove_index_spec.rb4
-rw-r--r--spec/rubocop/cop/migration/reversible_add_column_with_default_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/safer_boolean_column_spec.rb10
-rw-r--r--spec/rubocop/cop/migration/timestamps_spec.rb12
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb8
-rw-r--r--spec/rubocop/cop/migration/update_large_table_spec.rb10
-rw-r--r--spec/rubocop/cop/polymorphic_associations_spec.rb33
-rw-r--r--spec/rubocop/cop/project_path_helper_spec.rb6
-rw-r--r--spec/rubocop/cop/redirect_with_status_spec.rb86
-rw-r--r--spec/rubocop/cop/rspec/env_assignment_spec.rb6
-rw-r--r--spec/rubocop/cop/rspec/single_line_hook_spec.rb66
-rw-r--r--spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb53
-rw-r--r--spec/rubocop/cop/sidekiq_options_queue_spec.rb6
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb4
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb4
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb4
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb12
-rw-r--r--spec/services/clusters/applications/schedule_installation_service_spec.rb2
-rw-r--r--spec/services/files/delete_service_spec.rb64
-rw-r--r--spec/services/files/multi_service_spec.rb144
-rw-r--r--spec/services/issues/move_service_spec.rb12
-rw-r--r--spec/support/cluster_application_spec.rb105
-rw-r--r--vendor/prometheus/values.yaml134
-rw-r--r--yarn.lock10
524 files changed, 7234 insertions, 6437 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 42afed54371..d4905856e72 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -16,6 +16,7 @@ engines:
enabled: true
rubocop:
enabled: true
+ channel: "gitlab-rubocop-0-52"
ratings:
paths:
- Gemfile.lock
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c26e7f0aeba..4b149b13178 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
@@ -594,7 +594,7 @@ codequality:
script:
- cp .rubocop.yml .rubocop.yml.bak
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- - 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 analyze -f json > raw_codeclimate.json
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-v2 analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
- mv .rubocop.yml.bak .rubocop.yml
artifacts:
diff --git a/.rubocop.yml b/.rubocop.yml
index 7721cfaf850..0199bb9683a 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,1190 +1,22 @@
-require:
- - rubocop-rspec
- - rubocop-gitlab-security
- - ./rubocop/rubocop
+inherit_gem:
+ gitlab-styles:
+ - rubocop-default.yml
inherit_from: .rubocop_todo.yml
AllCops:
- TargetRubyVersion: 2.3
TargetRailsVersion: 4.2
- # Cop names are not d§splayed in offense messages by default. Change behavior
- # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
- # option.
- DisplayCopNames: true
- # Style guide URLs are not displayed in offense messages by default. Change
- # behavior by overriding DisplayStyleGuide, or by giving the
- # -S/--display-style-guide option.
- DisplayStyleGuide: false
- # Exclude some GitLab files
Exclude:
- 'vendor/**/*'
- 'node_modules/**/*'
- 'db/*'
- 'db/fixtures/**/*'
+ - 'db/geo/*'
- 'tmp/**/*'
- 'bin/**/*'
- 'generator_templates/**/*'
- 'builds/**/*'
-# Gems in consecutive lines should be alphabetically sorted
-Bundler/OrderedGems:
- Enabled: false
-
-# Layout ######################################################################
-
-# Check indentation of private/protected visibility modifiers.
-Layout/AccessModifierIndentation:
- Enabled: true
-
-# Align the elements of an array literal if they span more than one line.
-Layout/AlignArray:
- Enabled: true
-
-# Align the elements of a hash literal if they span more than one line.
-Layout/AlignHash:
- Enabled: true
-
-# Here we check if the parameters on a multi-line method call or
-# definition are aligned.
-Layout/AlignParameters:
- Enabled: false
-
-# Put end statement of multiline block on its own line.
-Layout/BlockEndNewline:
- Enabled: true
-
-# Indentation of when in a case/when/[else/]end.
-Layout/CaseIndentation:
- Enabled: true
-
-# Indentation of comments.
-Layout/CommentIndentation:
- Enabled: true
-
-# Multi-line method chaining should be done with leading dots.
-Layout/DotPosition:
- Enabled: true
- EnforcedStyle: leading
-
-# Align elses and elsifs correctly.
-Layout/ElseAlignment:
- Enabled: true
-
-# Add an empty line after magic comments to separate them from code.
-Layout/EmptyLineAfterMagicComment:
- Enabled: false
-
-# Use empty lines between defs.
-Layout/EmptyLineBetweenDefs:
- Enabled: true
-
-# Don't use several empty lines in a row.
-Layout/EmptyLines:
- Enabled: true
-
-# Keep blank lines around access modifiers.
-Layout/EmptyLinesAroundAccessModifier:
- Enabled: true
-
-# Keeps track of empty lines around block bodies.
-Layout/EmptyLinesAroundBlockBody:
- Enabled: true
-
-# Keeps track of empty lines around class bodies.
-Layout/EmptyLinesAroundClassBody:
- Enabled: true
-
-# Keeps track of empty lines around exception handling keywords.
-Layout/EmptyLinesAroundExceptionHandlingKeywords:
- Enabled: false
-
-# Keeps track of empty lines around method bodies.
-Layout/EmptyLinesAroundMethodBody:
- Enabled: true
-
-# Keeps track of empty lines around module bodies.
-Layout/EmptyLinesAroundModuleBody:
- Enabled: true
-
-# Use Unix-style line endings.
-Layout/EndOfLine:
- Enabled: true
-
-# Checks for a line break before the first parameter in a multi-line method
-# parameter definition.
-Layout/FirstMethodParameterLineBreak:
- Enabled: true
-
-# Keep indentation straight.
-Layout/IndentationConsistency:
- Enabled: true
-
-# Use 2 spaces for indentation.
-Layout/IndentationWidth:
- Enabled: true
-
-# Checks the indentation of the first line of the right-hand-side of a
-# multi-line assignment.
-Layout/IndentAssignment:
- Enabled: true
-
-# This cops checks the indentation of the here document bodies.
-Layout/IndentHeredoc:
- Enabled: false
-
-# Comments should start with a space.
-Layout/LeadingCommentSpace:
- Enabled: true
-
-# Checks that the closing brace in an array literal is either on the same line
-# as the last array element, or a new line.
-Layout/MultilineArrayBraceLayout:
- Enabled: true
- EnforcedStyle: symmetrical
-
-# Ensures newlines after multiline block do statements.
-Layout/MultilineBlockLayout:
- Enabled: true
-
-# Checks that the closing brace in a hash literal is either on the same line as
-# the last hash element, or a new line.
-Layout/MultilineHashBraceLayout:
- Enabled: true
- EnforcedStyle: symmetrical
-
-# Checks that the closing brace in a method call is either on the same line as
-# the last method argument, or a new line.
-Layout/MultilineMethodCallBraceLayout:
- Enabled: false
- EnforcedStyle: symmetrical
-
-# Checks indentation of method calls with the dot operator that span more than
-# one line.
-Layout/MultilineMethodCallIndentation:
- Enabled: false
-
-# Checks that the closing brace in a method definition is symmetrical with
-# respect to the opening brace and the method parameters.
-Layout/MultilineMethodDefinitionBraceLayout:
- Enabled: false
-
-# Checks indentation of binary operations that span more than one line.
-Layout/MultilineOperationIndentation:
- Enabled: true
- EnforcedStyle: indented
-
-# Use spaces after colons.
-Layout/SpaceAfterColon:
- Enabled: true
-
-# Use spaces after commas.
-Layout/SpaceAfterComma:
- Enabled: true
-
-# Do not put a space between a method name and the opening parenthesis in a
-# method definition.
-Layout/SpaceAfterMethodName:
- Enabled: true
-
-# Tracks redundant space after the ! operator.
-Layout/SpaceAfterNot:
- Enabled: true
-
-# Use spaces after semicolons.
-Layout/SpaceAfterSemicolon:
- Enabled: true
-
-# Use space around equals in parameter default
-Layout/SpaceAroundEqualsInParameterDefault:
- Enabled: true
-
-# Use a space around keywords if appropriate.
-Layout/SpaceAroundKeyword:
- Enabled: true
-
-# Use a single space around operators.
-Layout/SpaceAroundOperators:
- Enabled: true
-
-# Checks that block braces have or don't have a space before the opening
-# brace depending on configuration.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: space, no_space
-Layout/SpaceBeforeBlockBraces:
- Enabled: true
-
-# No spaces before commas.
-Layout/SpaceBeforeComma:
- Enabled: true
-
-# Checks for missing space between code and a comment on the same line.
-Layout/SpaceBeforeComment:
- Enabled: true
-
-# No spaces before semicolons.
-Layout/SpaceBeforeSemicolon:
- Enabled: true
-
-# Checks for spaces inside square brackets.
-Layout/SpaceInsideBrackets:
- Enabled: true
-
-# Use spaces inside hash literal braces - or don't.
-Layout/SpaceInsideHashLiteralBraces:
- Enabled: true
-
-# No spaces inside range literals.
-Layout/SpaceInsideRangeLiteral:
- Enabled: true
-
-# Checks for padding/surrounding spaces inside string interpolation.
-Layout/SpaceInsideStringInterpolation:
- EnforcedStyle: no_space
- Enabled: true
-
-# No hard tabs.
-Layout/Tab:
- Enabled: true
-
-# Checks trailing blank lines and final newline.
-Layout/TrailingBlankLines:
- Enabled: true
-
-# Avoid trailing whitespace.
-Layout/TrailingWhitespace:
- Enabled: true
-
-# Style #######################################################################
-
-# Check the naming of accessor methods for get_/set_.
-Style/AccessorMethodName:
- Enabled: false
-
-# Use alias_method instead of alias.
-Style/Alias:
- EnforcedStyle: prefer_alias_method
- Enabled: true
-
-# Whether `and` and `or` are banned only in conditionals (conditionals)
-# or completely (always).
-Style/AndOr:
- Enabled: true
-
-# Use `Array#join` instead of `Array#*`.
-Style/ArrayJoin:
- Enabled: true
-
-# Use only ascii symbols in comments.
-Style/AsciiComments:
- Enabled: true
-
-# Use only ascii symbols in identifiers.
-Style/AsciiIdentifiers:
- Enabled: true
-
-# Checks for uses of Module#attr.
-Style/Attr:
- Enabled: true
-
-# Avoid the use of BEGIN blocks.
-Style/BeginBlock:
- Enabled: true
-
-# Do not use block comments.
-Style/BlockComments:
- Enabled: true
-
-# Avoid using {...} for multi-line blocks (multiline chaining is # always
-# ugly). Prefer {...} over do...end for single-line blocks.
-Style/BlockDelimiters:
- Enabled: true
-
- # This cop checks for braces around the last parameter in a method call
-# if the last parameter is a hash.
-Style/BracesAroundHashParameters:
- Enabled: false
-
-# This cop checks for uses of the case equality operator(===).
-Style/CaseEquality:
- Enabled: false
-
-# Checks for uses of character literals.
-Style/CharacterLiteral:
- Enabled: true
-
-# Use CamelCase for classes and modules.'
-Style/ClassAndModuleCamelCase:
- Enabled: true
-
-# Checks style of children classes and modules.
-Style/ClassAndModuleChildren:
- Enabled: false
-
-# Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.
-Style/ClassCheck:
- Enabled: true
-
-# Use self when defining module/class methods.
-Style/ClassMethods:
- Enabled: true
-
-# Avoid the use of class variables.
-Style/ClassVars:
- Enabled: true
-
-# This cop checks for methods invoked via the :: operator instead
-# of the . operator (like FileUtils::rmdir instead of FileUtils.rmdir).
-Style/ColonMethodCall:
- Enabled: true
-
-# This cop checks that comment annotation keywords are written according
-# to guidelines.
-Style/CommentAnnotation:
- Enabled: false
-
-# Check for `if` and `case` statements where each branch is used for
-# assignment to the same variable when using the return of the
-# condition can be used instead.
-Style/ConditionalAssignment:
- Enabled: true
-
-# Constants should use SCREAMING_SNAKE_CASE.
-Style/ConstantName:
- Enabled: true
-
-# Use def with parentheses when there are arguments.
-Style/DefWithParentheses:
- Enabled: true
-
-# Document classes and non-namespace modules.
-Style/Documentation:
- Enabled: false
-
-# This cop checks for uses of double negation (!!) to convert something
-# to a boolean value. As this is both cryptic and usually redundant, it
-# should be avoided.
-Style/DoubleNegation:
- Enabled: false
-
-# Avoid the use of END blocks.
-Style/EndBlock:
- Enabled: true
-
-# Favor the use of Fixnum#even? && Fixnum#odd?
-Style/EvenOdd:
- Enabled: true
-
-# Use snake_case for source file names.
-Style/FileName:
- Enabled: true
-
-# Checks for flip flops.
-Style/FlipFlop:
- Enabled: true
-
-# Checks use of for or each in multiline loops.
-Style/For:
- Enabled: true
-
-# Use a consistent style for format string tokens.
-Style/FormatStringToken:
- Enabled: false
-
-# Checks if there is a magic comment to enforce string literals
-Style/FrozenStringLiteralComment:
- Enabled: false
-
-# Do not introduce global variables.
-Style/GlobalVars:
- Enabled: true
- Exclude:
- - 'lib/backup/**/*'
- - 'lib/tasks/**/*'
-
-# Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }`
-# over 1.8 syntax `{ :a => 1, :b => 2 }`.
-Style/HashSyntax:
- Enabled: true
-
-# Checks that conditional statements do not have an identical line at the
-# end of each branch, which can validly be moved out of the conditional.
-Style/IdenticalConditionalBranches:
- Enabled: true
-
-# Do not use if x; .... Use the ternary operator instead.
-Style/IfWithSemicolon:
- Enabled: true
-
-# Use Kernel#loop for infinite loops.
-Style/InfiniteLoop:
- Enabled: true
-
-# Use the inverse method instead of `!.method`
-# if an inverse method is defined.
-Style/InverseMethods:
- Enabled: false
-
-# Use lambda.call(...) instead of lambda.(...).
-Style/LambdaCall:
- Enabled: true
-
-# Checks if the method definitions have or don't have parentheses.
-Style/MethodDefParentheses:
- Enabled: true
-
-# Use the configured style when naming methods.
-Style/MethodName:
- Enabled: true
-
-# Checks for usage of `extend self` in modules.
-Style/ModuleFunction:
- Enabled: false
-
-# Avoid multi-line chains of blocks.
-Style/MultilineBlockChain:
- Enabled: true
-
-# Do not use then for multi-line if/unless.
-Style/MultilineIfThen:
- Enabled: true
-
-# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
-Style/MultilineTernaryOperator:
- Enabled: true
-
-# Avoid comparing a variable with multiple items in a conditional,
-# use Array#include? instead.
-Style/MultipleComparison:
- Enabled: false
-
-# This cop checks whether some constant value isn't a
-# mutable literal (e.g. array or hash).
-Style/MutableConstant:
- Enabled: true
- Exclude:
- - 'db/migrate/**/*'
- - 'db/post_migrate/**/*'
-
-# Favor unless over if for negative conditions (or control flow or).
-Style/NegatedIf:
- Enabled: true
-
-# Avoid using nested modifiers.
-Style/NestedModifier:
- Enabled: true
-
-# Use one expression per branch in a ternary operator.
-Style/NestedTernaryOperator:
- Enabled: true
-
-# Prefer x.nil? to x == nil.
-Style/NilComparison:
- Enabled: true
-
-# Checks for redundant nil checks.
-Style/NonNilCheck:
- Enabled: true
-
-# Use ! instead of not.
-Style/Not:
- Enabled: true
-
-# Add underscores to large numeric literals to improve their readability.
-Style/NumericLiterals:
- Enabled: false
-
-# Favor the ternary operator(?:) over if/then/else/end constructs.
-Style/OneLineConditional:
- Enabled: true
-
-# When defining binary operators, name the argument other.
-Style/OpMethod:
- Enabled: true
-
-# Don't use parentheses around the condition of an if/unless/while.
-Style/ParenthesesAroundCondition:
- Enabled: true
-
-# This cop (by default) checks for uses of methods Hash#has_key? and
-# Hash#has_value? where it enforces Hash#key? and Hash#value?
-# It is configurable to enforce the inverse, using `verbose` method
-# names also.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: short, verbose
-Style/PreferredHashMethods:
- Enabled: false
-
-# Checks for an obsolete RuntimeException argument in raise/fail.
-Style/RedundantException:
- Enabled: true
-
-# Checks for parentheses that seem not to serve any purpose.
-Style/RedundantParentheses:
- Enabled: true
-
-# Don't use semicolons to terminate expressions.
-Style/Semicolon:
- Enabled: true
-
-# Checks for proper usage of fail and raise.
-Style/SignalException:
- EnforcedStyle: only_raise
- Enabled: true
-
-# Check for the usage of parentheses around stabby lambda arguments.
-Style/StabbyLambdaParentheses:
- EnforcedStyle: require_parentheses
- Enabled: true
-
-# Checks if uses of quotes match the configured preference.
-Style/StringLiterals:
- Enabled: false
-
-# Checks if configured preferred methods are used over non-preferred.
-Style/StringMethods:
- PreferredMethods:
- intern: to_sym
- Enabled: true
-
-# Use %i or %I for arrays of symbols.
-Style/SymbolArray:
- Enabled: false
-
-# This cop checks for trailing comma in array and hash literals.
-Style/TrailingCommaInLiteral:
- Enabled: true
- EnforcedStyleForMultiline: no_comma
-
-# This cop checks for trailing comma in argument lists.
-Style/TrailingCommaInArguments:
- Enabled: true
- EnforcedStyleForMultiline: no_comma
-
-# Checks for %W when interpolation is not needed.
-Style/UnneededCapitalW:
- Enabled: true
-
-# Checks for %q/%Q when single quotes or double quotes would do.
-Style/UnneededPercentQ:
- Enabled: false
-
-# Don't interpolate global, instance and class variables directly in strings.
-Style/VariableInterpolation:
- Enabled: true
-
-# Use the configured style when naming variables.
-Style/VariableName:
- EnforcedStyle: snake_case
- Enabled: true
-
-# Use the configured style when numbering variables.
-Style/VariableNumber:
- Enabled: false
-
-# Use when x then ... for one-line cases.
-Style/WhenThen:
- Enabled: true
-
-# Checks for redundant do after while or until.
-Style/WhileUntilDo:
- Enabled: true
-
-# Favor modifier while/until usage when you have a single-line body.
-Style/WhileUntilModifier:
- Enabled: true
-
-# Use %w or %W for arrays of words.
-Style/WordArray:
- Enabled: true
-
-# Do not use literals as the first operand of a comparison.
-Style/YodaCondition:
- Enabled: false
-
-# Use `proc` instead of `Proc.new`.
-Style/Proc:
- Enabled: true
-
-# Use `spam?` instead of `is_spam?`
-# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
-# NamePrefix: is_, has_, have_
-# NamePrefixBlacklist: is_, has_, have_
-# NameWhitelist: is_a?
-Style/PredicateName:
- Enabled: true
- NamePrefixBlacklist: is_
- Exclude:
- - 'spec/**/*'
- - 'features/**/*'
-
-# Metrics #####################################################################
-
-# A calculated magnitude based on number of assignments,
-# branches, and conditions.
-Metrics/AbcSize:
- Enabled: true
- Max: 54.28
-
-# This cop checks if the length of a block exceeds some maximum value.
-Metrics/BlockLength:
- Enabled: false
-
-# Avoid excessive block nesting.
-Metrics/BlockNesting:
- Enabled: true
- Max: 4
-
-# Avoid classes longer than 100 lines of code.
-Metrics/ClassLength:
- Enabled: false
-
-# A complexity metric that is strongly correlated to the number
-# of test cases needed to validate a method.
-Metrics/CyclomaticComplexity:
- Enabled: true
- Max: 13
-
-# Limit lines to 80 characters.
-Metrics/LineLength:
- Enabled: false
-
-# Avoid methods longer than 10 lines of code.
-Metrics/MethodLength:
- Enabled: false
-
-# Avoid modules longer than 100 lines of code.
-Metrics/ModuleLength:
- Enabled: false
-
-# Avoid parameter lists longer than three or four parameters.
-Metrics/ParameterLists:
- Enabled: true
- Max: 8
-
-# A complexity metric geared towards measuring complexity for a human reader.
-Metrics/PerceivedComplexity:
- Enabled: true
- Max: 14
-
-# Lint ########################################################################
-
-# Checks for ambiguous block association with method when param passed without
-# parentheses.
-Lint/AmbiguousBlockAssociation:
- Enabled: false
-
-# Checks for ambiguous operators in the first argument of a method invocation
-# without parentheses.
-Lint/AmbiguousOperator:
- Enabled: true
-
-# This cop checks for ambiguous regexp literals in the first argument of
-# a method invocation without parentheses.
-Lint/AmbiguousRegexpLiteral:
- Enabled: false
-
-# This cop checks for assignments in the conditions of
-# if/while/until.
-Lint/AssignmentInCondition:
- Enabled: false
-
-# Align block ends correctly.
-Lint/BlockAlignment:
- Enabled: true
-
-# Default values in optional keyword arguments and optional ordinal arguments
-# should not refer back to the name of the argument.
-Lint/CircularArgumentReference:
- Enabled: true
-
-# Checks for condition placed in a confusing position relative to the keyword.
-Lint/ConditionPosition:
- Enabled: true
-
-# Check for debugger calls.
-Lint/Debugger:
- Enabled: true
-
-# Align ends corresponding to defs correctly.
-Lint/DefEndAlignment:
- Enabled: true
-
-# Check for deprecated class method calls.
-Lint/DeprecatedClassMethods:
- Enabled: true
-
-# Check for immutable argument given to each_with_object.
-Lint/EachWithObjectArgument:
- Enabled: true
-
-# Check for odd code arrangement in an else block.
-Lint/ElseLayout:
- Enabled: true
-
-# Checks for empty ensure block.
-Lint/EmptyEnsure:
- Enabled: true
-
-# Checks for the presence of `when` branches without a body.
-Lint/EmptyWhen:
- Enabled: true
-
-# Align ends correctly.
-Lint/EndAlignment:
- Enabled: true
-
-# END blocks should not be placed inside method definitions.
-Lint/EndInMethod:
- Enabled: true
-
-# Do not use return in an ensure block.
-Lint/EnsureReturn:
- Enabled: true
-
-# Catches floating-point literals too large or small for Ruby to represent.
-Lint/FloatOutOfRange:
- Enabled: true
-
-# The number of parameters to format/sprint must match the fields.
-Lint/FormatParameterMismatch:
- Enabled: true
-
-# This cop checks for *rescue* blocks with no body.
-Lint/HandleExceptions:
- Enabled: false
-
-# Checks for adjacent string literals on the same line, which could better be
-# represented as a single string literal.
-Lint/ImplicitStringConcatenation:
- Enabled: true
-
-# Checks for attempts to use `private` or `protected` to set the visibility
-# of a class method, which does not work.
-Lint/IneffectiveAccessModifier:
- Enabled: false
-
-# Checks for invalid character literals with a non-escaped whitespace
-# character.
-Lint/InvalidCharacterLiteral:
- Enabled: true
-
-# Checks of literals used in conditions.
-Lint/LiteralInCondition:
- Enabled: true
-
-# Checks for literals used in interpolation.
-Lint/LiteralInInterpolation:
- Enabled: true
-
-# This cop checks for uses of *begin...end while/until something*.
-Lint/Loop:
- Enabled: false
-
-# Do not use nested method definitions.
-Lint/NestedMethodDefinition:
- Enabled: true
-
-# Do not omit the accumulator when calling `next` in a `reduce`/`inject` block.
-Lint/NextWithoutAccumulator:
- Enabled: true
-
-# Checks for method calls with a space before the opening parenthesis.
-Lint/ParenthesesAsGroupedExpression:
- Enabled: true
-
-# Checks for `rand(1)` calls. Such calls always return `0` and most likely
-# a mistake.
-Lint/RandOne:
- Enabled: true
-
-# Use parentheses in the method call to avoid confusion about precedence.
-Lint/RequireParentheses:
- Enabled: true
-
-# Avoid rescuing the Exception class.
-Lint/RescueException:
- Enabled: true
-
-# Checks for the order which exceptions are rescued to avoid rescueing a less specific exception before a more specific exception.
-Lint/ShadowedException:
- Enabled: false
-
-# This cop looks for use of the same name as outer local variables
-# for block arguments or block local variables.
-Lint/ShadowingOuterLocalVariable:
- Enabled: false
-
-# Checks for Object#to_s usage in string interpolation.
-Lint/StringConversionInInterpolation:
- Enabled: true
-
-# Do not use prefix `_` for a variable that is used.
-Lint/UnderscorePrefixedVariableName:
- Enabled: true
-
-# This cop checks for using Fixnum or Bignum constant
-Lint/UnifiedInteger:
- Enabled: true
-
-# Checks for rubocop:disable comments that can be removed.
-# Note: this cop is not disabled when disabling all cops.
-# It must be explicitly disabled.
-Lint/UnneededDisable:
- Enabled: false
-
-# This cop checks for unneeded usages of splat expansion
-Lint/UnneededSplatExpansion:
- Enabled: false
-
-# Unreachable code.
-Lint/UnreachableCode:
- Enabled: true
-
-# This cop checks for unused block arguments.
-Lint/UnusedBlockArgument:
- Enabled: false
-
-# This cop checks for unused method arguments.
-Lint/UnusedMethodArgument:
- Enabled: false
-
-# Checks for useless access modifiers.
-Lint/UselessAccessModifier:
- Enabled: true
-
-# Checks for useless assignment to a local variable.
-Lint/UselessAssignment:
- Enabled: true
-
-# Checks for comparison of something with itself.
-Lint/UselessComparison:
- Enabled: true
-
-# Checks for useless `else` in `begin..end` without `rescue`.
-Lint/UselessElseWithoutRescue:
- Enabled: true
-
-# Checks for useless setter call to a local variable.
-Lint/UselessSetterCall:
- Enabled: true
-
-# Possible use of operator/literal/variable in void context.
-Lint/Void:
- Enabled: true
-
-# Performance #################################################################
-
-# Use `caller(n..n)` instead of `caller`.
-Performance/Caller:
- Enabled: false
-
-# Use `casecmp` rather than `downcase ==`.
-Performance/Casecmp:
- Enabled: true
-
-# Use `str.{start,end}_with?(x, ..., y, ...)` instead of
-# `str.{start,end}_with?(x, ...) || str.{start,end}_with?(y, ...)`.
-Performance/DoubleStartEndWith:
- Enabled: true
-
-# Use `strip` instead of `lstrip.rstrip`.
-Performance/LstripRstrip:
- Enabled: true
-
-# Use `Range#cover?` instead of `Range#include?`.
-Performance/RangeInclude:
- Enabled: true
-
-# This cop identifies the use of a `&block` parameter and `block.call`
-# where `yield` would do just as well.
-Performance/RedundantBlockCall:
- Enabled: true
-
-# This cop identifies use of `Regexp#match` or `String#match in a context
-# where the integral return value of `=~` would do just as well.
-Performance/RedundantMatch:
- Enabled: true
-
-# This cop identifies places where `Hash#merge!` can be replaced by
-# `Hash#[]=`.
-Performance/RedundantMerge:
- Enabled: true
- MaxKeyValuePairs: 1
-
-# Use `sort` instead of `sort_by { |x| x }`.
-Performance/RedundantSortBy:
- Enabled: true
-
-# Use `start_with?` instead of a regex match anchored to the beginning of a
-# string.
-Performance/StartWith:
- Enabled: true
-
-# Use `tr` instead of `gsub` when you are replacing the same number of
-# characters. Use `delete` instead of `gsub` when you are deleting
-# characters.
-Performance/StringReplacement:
- Enabled: true
-
-# Checks for `.times.map` calls.
-Performance/TimesMap:
- Enabled: true
-
-# Security ####################################################################
-
-# This cop checks for the use of JSON class methods which have potential
-# security issues.
-Security/JSONLoad:
- Enabled: true
-
-# This cop checks for the use of *Kernel#eval*.
-Security/Eval:
- Enabled: true
-
-# Rails #######################################################################
-
-# Enables Rails cops.
-Rails:
- Enabled: true
-
-# Enforces consistent use of action filter methods.
-Rails/ActionFilter:
- Enabled: true
- EnforcedStyle: action
-
-# Check that models subclass ApplicationRecord.
-Rails/ApplicationRecord:
- Enabled: false
-
-# Enforce using `blank?` and `present?`.
-Rails/Blank:
- Enabled: false
-
-# Checks the correct usage of date aware methods, such as `Date.today`,
-# `Date.current`, etc.
-Rails/Date:
- Enabled: false
-
-# Prefer delegate method for delegations.
-# Disabled per https://gitlab.com/gitlab-org/gitlab-ce/issues/35869
-Rails/Delegate:
- Enabled: false
-
-# This cop checks dynamic `find_by_*` methods.
-Rails/DynamicFindBy:
- Enabled: false
-
-# This cop enforces that 'exit' calls are not used within a rails app.
-Rails/Exit:
- Enabled: true
- Exclude:
- - lib/gitlab/upgrader.rb
- - 'lib/backup/**/*'
-
-# Prefer `find_by` over `where.first`.
-Rails/FindBy:
- Enabled: true
-
-# Prefer `all.find_each` over `all.find`.
-Rails/FindEach:
- Enabled: true
-
-# Prefer has_many :through to has_and_belongs_to_many.
-Rails/HasAndBelongsToMany:
- Enabled: true
-
-# This cop is used to identify usages of http methods like `get`, `post`,
-# `put`, `patch` without the usage of keyword arguments in your tests and
-# change them to use keyword args.
-Rails/HttpPositionalArguments:
- Enabled: false
-
-# Checks for calls to puts, print, etc.
-Rails/Output:
- Enabled: true
- Exclude:
- - lib/gitlab/seeder.rb
- - lib/gitlab/upgrader.rb
- - 'lib/backup/**/*'
- - 'lib/tasks/**/*'
-
-# This cop checks for the use of output safety calls like html_safe and
-# raw.
-Rails/OutputSafety:
- Enabled: false
-
-# Checks for incorrect grammar when using methods like `3.day.ago`.
-Rails/PluralizationGrammar:
- Enabled: true
-
-# Enforce using `blank?` and `present?`.
-Rails/Present:
- Enabled: false
-
-# Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
-Rails/ReadWriteAttribute:
- Enabled: false
-
-# Do not assign relative date to constants.
-Rails/RelativeDateConstant:
- Enabled: false
-
-# Checks the arguments of ActiveRecord scopes.
-Rails/ScopeArgs:
- Enabled: true
-
-# This cop checks for the use of Time methods without zone.
-Rails/TimeZone:
- Enabled: false
-
-# This cop checks for the use of old-style attribute validation macros.
-Rails/Validation:
- Enabled: true
-
-# RSpec #######################################################################
-
-# Check that instances are not being stubbed globally.
-RSpec/AnyInstance:
- Enabled: false
-
-# Check for expectations where `be(...)` can replace `eql(...)`.
-RSpec/BeEql:
- Enabled: true
-
-# We don't enforce this as we use this technique in a few places.
-RSpec/BeforeAfterAll:
- Enabled: false
-
-# Check that the first argument to the top level describe is the tested class or
-# module.
-RSpec/DescribeClass:
- Enabled: false
-
-# Checks that the second argument to `describe` specifies a method.
-RSpec/DescribeMethod:
- Enabled: false
-
-# Avoid describing symbols.
-RSpec/DescribeSymbol:
- Enabled: true
-
-# Checks that tests use `described_class`.
-RSpec/DescribedClass:
- Enabled: true
-
-# Checks if an example group does not include any tests.
-RSpec/EmptyExampleGroup:
- Enabled: true
- CustomIncludeMethods:
- - run_permission_checks
-
-# Checks for long example.
-RSpec/ExampleLength:
- Enabled: false
- Max: 5
-
-# Do not use should when describing your tests.
-RSpec/ExampleWording:
- Enabled: false
- CustomTransform:
- be: is
- have: has
- not: does not
- IgnoredWords: []
-
-# Checks for `expect(...)` calls containing literal values.
-RSpec/ExpectActual:
- Enabled: true
-
-# Checks for opportunities to use `expect { … }.to output`.
-RSpec/ExpectOutput:
- Enabled: true
-
-# Checks the file and folder naming of the spec file.
-RSpec/FilePath:
- Enabled: true
- IgnoreMethods: true
- Exclude:
- - 'qa/**/*'
- - 'spec/javascripts/fixtures/*'
- - 'spec/requests/api/v3/*'
-
-# Checks if there are focused specs.
-RSpec/Focus:
- Enabled: true
-
-# Checks the arguments passed to `before`, `around`, and `after`.
-RSpec/HookArgument:
- Enabled: true
- EnforcedStyle: implicit
-
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: is_expected, should
-RSpec/ImplicitExpect:
- Enabled: true
- EnforcedStyle: is_expected
-
-# Checks for the usage of instance variables.
-RSpec/InstanceVariable:
- Enabled: false
-
-# Checks for `subject` definitions that come after `let` definitions.
-RSpec/LeadingSubject:
- Enabled: false
-
-# Checks unreferenced `let!` calls being used for test setup.
-RSpec/LetSetup:
- Enabled: false
-
-# Check that chains of messages are not being stubbed.
-RSpec/MessageChain:
- Enabled: false
-
-# Checks that message expectations are set using spies.
-RSpec/MessageSpies:
- Enabled: false
-
-# Checks for multiple top-level describes.
-RSpec/MultipleDescribes:
- Enabled: false
-
-# Checks if examples contain too many `expect` calls.
-RSpec/MultipleExpectations:
- Enabled: false
-
-# Checks for explicitly referenced test subjects.
-RSpec/NamedSubject:
- Enabled: false
-
-# Checks for nested example groups.
-RSpec/NestedGroups:
- Enabled: false
-
-# Enforces the usage of the same method on all negative message expectations.
-RSpec/NotToNot:
- EnforcedStyle: not_to
- Enabled: true
-
-# Check for repeated description strings in example groups.
-RSpec/RepeatedDescription:
- Enabled: false
-
-# Ensure RSpec hook blocks are always multi-line.
-RSpec/SingleLineHook:
- Enabled: true
- Exclude:
- - 'spec/factories/*'
- - 'spec/requests/api/v3/*'
-
-# Checks for stubbed test subjects.
-RSpec/SubjectStub:
- Enabled: false
-
-# Prefer using verifying doubles over normal doubles.
-RSpec/VerifiedDoubles:
- Enabled: false
-
# Gitlab ###################################################################
Gitlab/ModuleWithInstanceVariables:
@@ -1197,43 +29,3 @@ Gitlab/ModuleWithInstanceVariables:
# We ignore spec helpers because it usually doesn't matter
- spec/support/**/*.rb
- features/steps/**/*.rb
-
-# GitlabSecurity ###########################################################
-
-GitlabSecurity/DeepMunge:
- Enabled: true
- Exclude:
- - 'lib/**/*.rake'
- - 'spec/**/*'
-
-# To be enabled by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13610
-GitlabSecurity/JsonSerialization:
- Enabled: false
-
-GitlabSecurity/PublicSend:
- Enabled: true
- Exclude:
- - 'config/**/*'
- - 'db/**/*'
- - 'features/**/*'
- - 'lib/**/*.rake'
- - 'qa/**/*'
- - 'spec/**/*'
-
-GitlabSecurity/RedirectToParamsUpdate:
- Enabled: true
- Exclude:
- - 'lib/**/*.rake'
- - 'spec/**/*'
-
-GitlabSecurity/SqlInjection:
- Enabled: true
- Exclude:
- - 'lib/**/*.rake'
- - 'spec/**/*'
-
-GitlabSecurity/SystemCommandInjection:
- Enabled: true
- Exclude:
- - 'lib/**/*.rake'
- - 'spec/**/*'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index cdf97d1d842..085dc153596 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,53 +1,111 @@
# This configuration was generated by
-# `rubocop --auto-gen-config --exclude-limit 0`
-# on 2017-07-10 01:48:30 +0900 using RuboCop version 0.49.1.
+# `rubocop --auto-gen-config`
+# on 2017-12-14 12:04:26 +0100 using RuboCop version 0.52.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 181
+# Offense count: 174
+Capybara/CurrentPathExpectation:
+ Enabled: false
+
+# Offense count: 951
+Capybara/FeatureMethods:
+ Enabled: false
+
+# Offense count: 24
+FactoryBot/DynamicAttributeDefinedStatically:
+ Exclude:
+ - 'spec/factories/broadcast_messages.rb'
+ - 'spec/factories/ci/builds.rb'
+ - 'spec/factories/ci/runners.rb'
+ - 'spec/factories/clusters/applications/helm.rb'
+ - 'spec/factories/clusters/applications/ingress.rb'
+ - 'spec/factories/clusters/platforms/kubernetes.rb'
+ - 'spec/factories/emails.rb'
+ - 'spec/factories/gpg_keys.rb'
+ - 'spec/factories/group_members.rb'
+ - 'spec/factories/merge_requests.rb'
+ - 'spec/factories/notes.rb'
+ - 'spec/factories/oauth_access_grants.rb'
+ - 'spec/factories/project_members.rb'
+ - 'spec/factories/todos.rb'
+ - 'spec/factories/uploads.rb'
+
+# Offense count: 65
+# Cop supports --auto-correct.
+Layout/EmptyLinesAroundArguments:
+ Enabled: false
+
+# Offense count: 249
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Enabled: false
-# Offense count: 119
+# Offense count: 82
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Layout/IndentArray:
Enabled: false
-# Offense count: 208
+# Offense count: 239
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Layout/IndentHash:
Enabled: false
-# Offense count: 8
+# Offense count: 15
# Cop supports --auto-correct.
-# Configuration parameters: AllowForAlignment.
-Layout/SpaceBeforeFirstArg:
- Enabled: false
+# Configuration parameters: .
+# SupportedStyles: space, no_space
+# SupportedStylesForEmptyBraces: space, no_space
+Layout/SpaceBeforeBlockBraces:
+ EnforcedStyle: space
+ EnforcedStyleForEmptyBraces: space
-# Offense count: 64
+# Offense count: 11
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+# Configuration parameters: AllowForAlignment.
+Layout/SpaceBeforeFirstArg:
+ Exclude:
+ - 'config/routes/project.rb'
+ - 'db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb'
+ - 'features/steps/project/source/browse_files.rb'
+ - 'features/steps/project/source/markdown_render.rb'
+ - 'lib/api/runners.rb'
+ - 'spec/features/search/user_uses_search_filters_spec.rb'
+ - 'spec/routing/project_routing_spec.rb'
+ - 'spec/services/system_note_service_spec.rb'
+
+# Offense count: 93
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_no_space, require_space
Layout/SpaceInLambdaLiteral:
Enabled: false
-# Offense count: 256
+# Offense count: 1
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
+# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
+# SupportedStyles: space, no_space, compact
+# SupportedStylesForEmptyBrackets: space, no_space
+Layout/SpaceInsideArrayLiteralBrackets:
+ Exclude:
+ - 'spec/lib/gitlab/import_export/relation_factory_spec.rb'
+
+# Offense count: 323
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceInsideBlockBraces:
Enabled: false
-# Offense count: 135
+# Offense count: 146
# Cop supports --auto-correct.
Layout/SpaceInsideParens:
Enabled: false
@@ -55,178 +113,535 @@ Layout/SpaceInsideParens:
# Offense count: 14
# Cop supports --auto-correct.
Layout/SpaceInsidePercentLiteralDelimiters:
+ Exclude:
+ - 'lib/gitlab/git_access.rb'
+ - 'lib/gitlab/health_checks/fs_shards_check.rb'
+ - 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb'
+
+# Offense count: 25
+Lint/DuplicateMethods:
+ Exclude:
+ - 'app/models/application_setting.rb'
+ - 'app/models/commit.rb'
+ - 'app/models/note.rb'
+ - 'app/services/merge_requests/merge_service.rb'
+ - 'lib/bitbucket/representation/repo.rb'
+ - 'lib/declarative_policy/base.rb'
+ - 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
+ - 'lib/gitlab/cycle_analytics/base_event_fetcher.rb'
+ - 'lib/gitlab/diff/formatters/base_formatter.rb'
+ - 'lib/gitlab/git/blob.rb'
+ - 'lib/gitlab/git/repository.rb'
+ - 'lib/gitlab/git/tree.rb'
+ - 'lib/gitlab/git/wiki_page.rb'
+ - 'lib/gitlab/ldap/person.rb'
+ - 'lib/gitlab/o_auth/user.rb'
+
+# Offense count: 4
+Lint/InterpolationCheck:
+ Exclude:
+ - 'spec/features/issues/filtered_search/filter_issues_spec.rb'
+ - 'spec/features/users_spec.rb'
+ - 'spec/services/quick_actions/interpret_service_spec.rb'
+
+# Offense count: 198
+# Configuration parameters: MaximumRangeSize.
+Lint/MissingCopEnableDirective:
+ Enabled: false
+
+# Offense count: 2
+Lint/NestedPercentLiteral:
+ Exclude:
+ - 'lib/gitlab/git/repository.rb'
+ - 'spec/support/email_format_shared_examples.rb'
+
+# Offense count: 1
+Lint/ReturnInVoidContext:
+ Exclude:
+ - 'app/models/project.rb'
+
+# Offense count: 1
+# Configuration parameters: IgnoreImplicitReferences.
+Lint/ShadowedArgument:
+ Exclude:
+ - 'lib/gitlab/database/sha_attribute.rb'
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Lint/UnneededRequireStatement:
+ Exclude:
+ - 'db/post_migrate/20161221153951_rename_reserved_project_names.rb'
+ - 'db/post_migrate/20170313133418_rename_more_reserved_project_names.rb'
+ - 'lib/declarative_policy.rb'
+
+# Offense count: 9
+Lint/UriEscapeUnescape:
+ Exclude:
+ - 'app/controllers/application_controller.rb'
+ - 'app/models/project_services/drone_ci_service.rb'
+ - 'spec/lib/google_api/auth_spec.rb'
+ - 'spec/requests/api/files_spec.rb'
+ - 'spec/requests/api/internal_spec.rb'
+ - 'spec/requests/api/issues_spec.rb'
+ - 'spec/requests/api/v3/issues_spec.rb'
+
+# Offense count: 2
+Naming/ConstantName:
+ Exclude:
+ - 'lib/gitlab/import_sources.rb'
+ - 'lib/gitlab/ssh_public_key.rb'
+
+# Offense count: 11
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: lowercase, uppercase
+Naming/HeredocDelimiterCase:
+ Exclude:
+ - 'spec/lib/gitlab/diff/parser_spec.rb'
+ - 'spec/lib/json_web_token/rsa_token_spec.rb'
+ - 'spec/models/commit_spec.rb'
+ - 'spec/support/repo_helpers.rb'
+ - 'spec/support/seed_repo.rb'
+
+# Offense count: 101
+# Configuration parameters: Blacklist.
+# Blacklist: END, (?-mix:EO[A-Z]{1})
+Naming/HeredocDelimiterNaming:
+ Enabled: false
+
+# Offense count: 28
+# Cop supports --auto-correct.
+# Configuration parameters: AutoCorrect.
+Performance/HashEachMethods:
+ Enabled: false
+
+# Offense count: 1
+Performance/UnfreezeString:
+ Exclude:
+ - 'features/steps/project/commits/commits.rb'
+
+# Offense count: 1
+# Cop supports --auto-correct.
+Performance/UriDefaultParser:
+ Exclude:
+ - 'lib/gitlab/url_sanitizer.rb'
+
+# Offense count: 3745
+# Configuration parameters: Prefixes.
+# Prefixes: when, with, without
+RSpec/ContextWording:
Enabled: false
-# Offense count: 272
+# Offense count: 291
RSpec/EmptyLineAfterFinalLet:
Enabled: false
-# Offense count: 181
+# Offense count: 180
RSpec/EmptyLineAfterSubject:
Enabled: false
-# Offense count: 9
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+# Offense count: 220
+RSpec/ExpectInHook:
+ Enabled: false
+
+# Offense count: 7
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: implicit, each, example
+RSpec/HookArgument:
+ Exclude:
+ - 'spec/spec_helper.rb'
+ - 'spec/support/carrierwave.rb'
+ - 'spec/support/db_cleaner.rb'
+ - 'spec/support/gitaly.rb'
+ - 'spec/support/setup_builds_storage.rb'
+
+# Offense count: 19
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: it_behaves_like, it_should_behave_like
RSpec/ItBehavesLike:
- Enabled: false
+ Exclude:
+ - 'spec/lib/gitlab/git/commit_spec.rb'
+ - 'spec/lib/gitlab/git/repository_spec.rb'
+ - 'spec/lib/gitlab/shell_spec.rb'
+ - 'spec/services/notification_service_spec.rb'
+ - 'spec/workers/git_garbage_collect_worker_spec.rb'
-# Offense count: 4
+# Offense count: 5
RSpec/IteratedExpectation:
- Enabled: false
+ Exclude:
+ - 'spec/features/admin/admin_settings_spec.rb'
+ - 'spec/features/merge_requests/diff_notes_resolve_spec.rb'
+ - 'spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb'
+ - 'spec/lib/gitlab/gitlab_import/client_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/client_spec.rb'
+
+# Offense count: 75
+RSpec/LetBeforeExamples:
+ Exclude:
+ - 'spec/controllers/projects/commit_controller_spec.rb'
+ - 'spec/lib/banzai/filter/issue_reference_filter_spec.rb'
+ - 'spec/lib/banzai/filter/user_reference_filter_spec.rb'
+ - 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb'
+ - 'spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb'
+ - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
+ - 'spec/models/commit_range_spec.rb'
+ - 'spec/models/milestone_spec.rb'
+ - 'spec/models/project_services/packagist_service_spec.rb'
+ - 'spec/models/repository_spec.rb'
+ - 'spec/rubocop/cop/migration/update_column_in_batches_spec.rb'
+ - 'spec/serializers/pipeline_details_entity_spec.rb'
+ - 'spec/views/ci/lints/show.html.haml_spec.rb'
-# Offense count: 2
+# Offense count: 1
+RSpec/MultipleSubjects:
+ Exclude:
+ - 'spec/services/merge_requests/create_from_issue_service_spec.rb'
+
+# Offense count: 4
RSpec/OverwritingSetup:
+ Exclude:
+ - 'spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb'
+ - 'spec/models/email_spec.rb'
+ - 'spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
+ - 'spec/services/notes/quick_actions_service_spec.rb'
+
+# Offense count: 917
+# Configuration parameters: Strict, EnforcedStyle.
+# SupportedStyles: inflected, explicit
+RSpec/PredicateMatcher:
Enabled: false
-# Offense count: 36
+# Offense count: 35
RSpec/RepeatedExample:
Enabled: false
-# Offense count: 86
+# Offense count: 132
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: and_return, block
+RSpec/ReturnFromStub:
+ Enabled: false
+
+# Offense count: 105
RSpec/ScatteredLet:
Enabled: false
-# Offense count: 20
+# Offense count: 22
RSpec/ScatteredSetup:
- Enabled: false
+ Exclude:
+ - 'spec/controllers/projects/templates_controller_spec.rb'
+ - 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
+ - 'spec/lib/gitlab/git/env_spec.rb'
+ - 'spec/requests/api/jobs_spec.rb'
+ - 'spec/requests/api/v3/builds_spec.rb'
+ - 'spec/requests/api/v3/projects_spec.rb'
+ - 'spec/services/projects/create_service_spec.rb'
# Offense count: 1
RSpec/SharedContext:
+ Exclude:
+ - 'spec/features/admin/admin_groups_spec.rb'
+
+# Offense count: 90
+RSpec/SingleLineHook:
+ Enabled: false
+
+# Offense count: 5
+RSpec/VoidExpect:
+ Exclude:
+ - 'spec/features/projects/artifacts/download_spec.rb'
+ - 'spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb'
+ - 'spec/models/ci/group_spec.rb'
+ - 'spec/models/ci/runner_spec.rb'
+ - 'spec/services/users/destroy_service_spec.rb'
+
+# Offense count: 40
+# Configuration parameters: Include.
+# Include: db/migrate/*.rb
+Rails/CreateTableWithTimestamps:
Enabled: false
-# Offense count: 115
+# Offense count: 149
Rails/FilePath:
Enabled: false
+# Offense count: 119
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/HasManyOrHasOneDependent:
+ Enabled: false
+
+# Offense count: 113
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/InverseOf:
+ Enabled: false
+
+# Offense count: 48
+# Configuration parameters: Include.
+# Include: app/controllers/**/*.rb
+Rails/LexicallyScopedActionFilter:
+ Enabled: false
+
+# Offense count: 14
+# Cop supports --auto-correct.
+Rails/Presence:
+ Exclude:
+ - 'app/controllers/projects/blob_controller.rb'
+ - 'app/models/ci/pipeline.rb'
+ - 'app/models/clusters/platforms/kubernetes.rb'
+ - 'app/models/concerns/mentionable.rb'
+ - 'app/models/concerns/token_authenticatable.rb'
+ - 'app/models/project_services/hipchat_service.rb'
+ - 'app/models/project_services/irker_service.rb'
+ - 'app/models/project_services/jira_service.rb'
+ - 'app/models/project_services/kubernetes_service.rb'
+ - 'app/models/project_services/packagist_service.rb'
+ - 'app/models/wiki_page.rb'
+ - 'lib/gitlab/git/hook.rb'
+ - 'lib/gitlab/github_import/importer/releases_importer.rb'
+
+# Offense count: 14
+# Cop supports --auto-correct.
+Rails/RedundantReceiverInWithOptions:
+ Exclude:
+ - 'config/initializers/doorkeeper_openid_connect.rb'
+
# Offense count: 2
# Configuration parameters: Include.
# Include: db/migrate/*.rb
Rails/ReversibleMigration:
- Enabled: false
+ Exclude:
+ - 'db/migrate/20160824103857_drop_unused_ci_tables.rb'
-# Offense count: 336
+# Offense count: 430
# Configuration parameters: Blacklist.
# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
Rails/SkipsModelValidations:
Enabled: false
-# Offense count: 11
-# Cop supports --auto-correct.
-Security/YAMLLoad:
- Enabled: false
+# Offense count: 1
+# Configuration parameters: Environments.
+# Environments: development, test, production
+Rails/UnknownEnv:
+ Exclude:
+ - 'db/migrate/20171124125748_populate_missing_merge_request_statuses.rb'
-# Offense count: 58
+# Offense count: 13
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+Security/YAMLLoad:
+ Exclude:
+ - 'config/initializers/carrierwave.rb'
+ - 'lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb'
+ - 'lib/gitlab/redis/wrapper.rb'
+ - 'lib/system_check/incoming_email/imap_authentication_check.rb'
+ - 'spec/config/mail_room_spec.rb'
+ - 'spec/initializers/secret_token_spec.rb'
+ - 'spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb'
+ - 'spec/models/clusters/platforms/kubernetes_spec.rb'
+ - 'spec/models/project_services/kubernetes_service_spec.rb'
+
+# Offense count: 63
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: percent_q, bare_percent
Style/BarePercentLiterals:
Enabled: false
-# Offense count: 6
-# Cop supports --auto-correct.
-Style/EachWithObject:
+# Offense count: 5
+Style/CommentedKeyword:
+ Exclude:
+ - 'lib/tasks/gitlab/backup.rake'
+ - 'spec/tasks/gitlab/backup_rake_spec.rb'
+
+# Offense count: 30
+Style/DateTime:
Enabled: false
-# Offense count: 31
+# Offense count: 1
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+Style/Dir:
+ Exclude:
+ - 'qa/qa.rb'
+
+# Offense count: 9
+# Cop supports --auto-correct.
+Style/EachWithObject:
+ Exclude:
+ - 'config/initializers/gollum.rb'
+ - 'lib/expand_variables.rb'
+ - 'lib/gitlab/ci/ansi2html.rb'
+ - 'lib/gitlab/ee_compat_check.rb'
+ - 'lib/gitlab/hook_data/issuable_builder.rb'
+ - 'lib/gitlab/i18n/po_linter.rb'
+ - 'lib/gitlab/import_export/members_mapper.rb'
+ - 'lib/gitlab/import_export/relation_factory.rb'
+ - 'scripts/static-analysis'
+
+# Offense count: 24
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: empty, nil, both
Style/EmptyElse:
Enabled: false
-# Offense count: 9
+# Offense count: 14
+# Cop supports --auto-correct.
+Style/EmptyLambdaParameter:
+ Exclude:
+ - 'app/models/ci/build.rb'
+ - 'app/models/ci/runner.rb'
+
+# Offense count: 12
# Cop supports --auto-correct.
Style/EmptyLiteral:
- Enabled: false
+ Exclude:
+ - 'features/steps/project/commits/commits.rb'
+ - 'lib/gitlab/fogbugz_import/importer.rb'
+ - 'lib/gitlab/git/diff_collection.rb'
+ - 'lib/gitlab/gitaly_client.rb'
+ - 'scripts/trigger-build-omnibus'
+ - 'spec/features/merge_requests/versions_spec.rb'
+ - 'spec/helpers/merge_requests_helper_spec.rb'
+ - 'spec/lib/gitlab/request_context_spec.rb'
+ - 'spec/lib/gitlab/workhorse_spec.rb'
+ - 'spec/requests/api/jobs_spec.rb'
+ - 'spec/support/chat_slash_commands_shared_examples.rb'
-# Offense count: 78
+# Offense count: 98
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Enabled: false
# Offense count: 23
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+Style/Encoding:
+ Enabled: false
+
+# Offense count: 2
+Style/EvalWithLocation:
+ Exclude:
+ - 'app/models/service.rb'
+
+# Offense count: 52
+# Cop supports --auto-correct.
+# Configuration parameters: Autocorrect, EnforcedStyle.
+# SupportedStyles: module_function, extend_self
+Style/ExtendSelf:
+ Enabled: false
+
+# Offense count: 34
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Enabled: false
-# Offense count: 301
+# Offense count: 371
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
-# Offense count: 18
+# Offense count: 21
Style/IfInsideElse:
Enabled: false
-# Offense count: 182
+# Offense count: 781
# Cop supports --auto-correct.
-# Configuration parameters: MaxLineLength.
Style/IfUnlessModifier:
Enabled: false
-# Offense count: 52
+# Offense count: 71
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: line_count_dependent, lambda, literal
Style/Lambda:
Enabled: false
-# Offense count: 6
+# Offense count: 11
# Cop supports --auto-correct.
Style/LineEndConcatenation:
- Enabled: false
+ Exclude:
+ - 'app/helpers/tree_helper.rb'
+ - 'spec/features/issuables/markdown_references_spec.rb'
+ - 'spec/lib/gitlab/checks/project_moved_spec.rb'
+ - 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
+ - 'spec/lib/gitlab/incoming_email_spec.rb'
-# Offense count: 40
+# Offense count: 39
# Cop supports --auto-correct.
Style/MethodCallWithoutArgsParentheses:
Enabled: false
-# Offense count: 13
+# Offense count: 17
Style/MethodMissing:
Enabled: false
+# Offense count: 7
+Style/MixinUsage:
+ Exclude:
+ - 'features/support/env.rb'
+ - 'spec/factories/ci/builds.rb'
+ - 'spec/factories/ci/job_artifacts.rb'
+ - 'spec/factories/lfs_objects.rb'
+ - 'spec/factories/notes.rb'
+ - 'spec/lib/gitlab/import_export/project_tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/import_export/version_checker_spec.rb'
+
# Offense count: 6
# Cop supports --auto-correct.
Style/MultilineIfModifier:
- Enabled: false
+ Exclude:
+ - 'app/helpers/snippets_helper.rb'
+ - 'app/models/project_wiki.rb'
+ - 'app/services/ci/process_pipeline_service.rb'
+ - 'app/services/create_deployment_service.rb'
+ - 'lib/api/commit_statuses.rb'
+ - 'lib/gitlab/ci/trace.rb'
-# Offense count: 26
+# Offense count: 23
# Cop supports --auto-correct.
+# Configuration parameters: Whitelist.
+# Whitelist: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
Style/NestedParenthesizedCalls:
Enabled: false
# Offense count: 20
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
+# Configuration parameters: EnforcedStyle, MinBodyLength.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Enabled: false
-# Offense count: 45
+# Offense count: 58
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
+# Configuration parameters: EnforcedOctalStyle.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 98
+# Offense count: 112
# Cop supports --auto-correct.
-# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
+# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Enabled: false
-# Offense count: 42
+# Offense count: 4
+# Cop supports --auto-correct.
+Style/OrAssignment:
+ Exclude:
+ - 'app/models/concerns/token_authenticatable.rb'
+ - 'lib/api/commit_statuses.rb'
+ - 'lib/api/v3/members.rb'
+ - 'lib/gitlab/project_transfer.rb'
+
+# Offense count: 50
# Cop supports --auto-correct.
Style/ParallelAssignment:
Enabled: false
-# Offense count: 800
+# Offense count: 891
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
@@ -235,106 +650,194 @@ Style/PercentLiteralDelimiters:
# Offense count: 15
# Cop supports --auto-correct.
Style/PerlBackrefs:
- Enabled: false
-
-# Offense count: 58
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+ Exclude:
+ - 'app/controllers/projects/application_controller.rb'
+ - 'app/helpers/submodule_helper.rb'
+ - 'lib/backup/manager.rb'
+ - 'lib/banzai/filter/abstract_reference_filter.rb'
+ - 'lib/banzai/filter/autolink_filter.rb'
+ - 'lib/banzai/filter/emoji_filter.rb'
+ - 'lib/banzai/filter/gollum_tags_filter.rb'
+ - 'lib/expand_variables.rb'
+ - 'lib/gitlab/diff/highlight.rb'
+ - 'lib/gitlab/search_results.rb'
+ - 'lib/gitlab/sherlock/query.rb'
+
+# Offense count: 82
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
-# Offense count: 6
+# Offense count: 8
# Cop supports --auto-correct.
Style/RedundantBegin:
- Enabled: false
+ Exclude:
+ - 'app/controllers/projects/clusters/gcp_controller.rb'
+ - 'app/models/merge_request.rb'
+ - 'app/services/projects/import_service.rb'
+ - 'lib/api/branches.rb'
+ - 'lib/gitlab/current_settings.rb'
+ - 'lib/gitlab/git/commit.rb'
+ - 'lib/gitlab/health_checks/base_abstract_check.rb'
+ - 'lib/tasks/gitlab/task_helpers.rb'
+
+# Offense count: 1
+# Cop supports --auto-correct.
+Style/RedundantConditional:
+ Exclude:
+ - 'lib/system_check/helpers.rb'
-# Offense count: 37
+# Offense count: 58
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
-# Offense count: 14
+# Offense count: 15
# Cop supports --auto-correct.
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
- Enabled: false
-
-# Offense count: 406
+ Exclude:
+ - 'app/controllers/application_controller.rb'
+ - 'app/controllers/concerns/issuable_actions.rb'
+ - 'app/controllers/groups/application_controller.rb'
+ - 'app/controllers/omniauth_callbacks_controller.rb'
+ - 'app/controllers/profiles/keys_controller.rb'
+ - 'app/controllers/projects/application_controller.rb'
+ - 'app/services/access_token_validation_service.rb'
+ - 'lib/gitlab/utils.rb'
+ - 'lib/google_api/auth.rb'
+
+# Offense count: 454
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
-# Offense count: 115
+# Offense count: 140
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
+# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
-# Offense count: 29
+# Offense count: 35
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
+# Offense count: 105
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: implicit, explicit
+Style/RescueStandardError:
+ Enabled: false
+
+# Offense count: 91
+# Cop supports --auto-correct.
+# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
+Style/SafeNavigation:
+ Enabled: false
+
# Offense count: 8
# Cop supports --auto-correct.
Style/SelfAssignment:
- Enabled: false
+ Exclude:
+ - 'app/models/concerns/bulk_member_access_load.rb'
+ - 'app/serializers/base_serializer.rb'
+ - 'app/services/notification_service.rb'
+ - 'lib/api/runners.rb'
+ - 'spec/features/merge_requests/diff_notes_resolve_spec.rb'
+ - 'spec/features/projects/clusters/interchangeability_spec.rb'
+ - 'spec/support/import_export/configuration_helper.rb'
# Offense count: 50
# Cop supports --auto-correct.
# Configuration parameters: AllowIfMethodIsEmpty.
Style/SingleLineMethods:
- Enabled: false
+ Exclude:
+ - 'lib/gitlab/ci/ansi2html.rb'
-# Offense count: 64
+# Offense count: 66
# Cop supports --auto-correct.
-# Configuration parameters: SupportedStyles.
+# Configuration parameters: .
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
EnforcedStyle: use_perl_names
-# Offense count: 44
+# Offense count: 1
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
+Style/StderrPuts:
+ Exclude:
+ - 'config/initializers/rspec_profiling.rb'
+
+# Offense count: 45
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Enabled: false
-# Offense count: 84
+# Offense count: 99
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
Style/SymbolProc:
Enabled: false
-# Offense count: 8
+# Offense count: 9
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
+# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
Style/TernaryParentheses:
- Enabled: false
+ Exclude:
+ - 'app/finders/projects_finder.rb'
+ - 'app/helpers/namespaces_helper.rb'
+ - 'features/support/capybara.rb'
+ - 'lib/api/v3/projects.rb'
+ - 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
+ - 'spec/requests/api/pipeline_schedules_spec.rb'
+ - 'spec/support/capybara.rb'
# Offense count: 17
# Cop supports --auto-correct.
# Configuration parameters: AllowNamedUnderscoreVariables.
Style/TrailingUnderscoreVariable:
- Enabled: false
+ Exclude:
+ - 'app/controllers/admin/background_jobs_controller.rb'
+ - 'app/controllers/invites_controller.rb'
+ - 'app/helpers/tab_helper.rb'
+ - 'lib/backup/manager.rb'
+ - 'lib/gitlab/logger.rb'
+ - 'lib/gitlab/upgrader.rb'
+ - 'lib/system_check/app/migrations_are_up_check.rb'
+ - 'lib/system_check/incoming_email/mail_room_running_check.rb'
+ - 'lib/tasks/gitlab/check.rake'
+ - 'lib/tasks/gitlab/task_helpers.rb'
+ - 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
+ - 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 4
+# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
Style/TrivialAccessors:
- Enabled: false
+ Exclude:
+ - 'app/models/external_issue.rb'
+ - 'app/serializers/base_serializer.rb'
+ - 'lib/gitlab/ldap/person.rb'
+ - 'lib/system_check/base_check.rb'
-# Offense count: 5
+# Offense count: 4
# Cop supports --auto-correct.
Style/UnlessElse:
- Enabled: false
+ Exclude:
+ - 'lib/backup/manager.rb'
+ - 'lib/gitlab/project_search_results.rb'
+ - 'lib/tasks/gitlab/check.rake'
+ - 'spec/features/issues/award_emoji_spec.rb'
-# Offense count: 28
+# Offense count: 30
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Enabled: false
@@ -342,4 +845,19 @@ Style/UnneededInterpolation:
# Offense count: 11
# Cop supports --auto-correct.
Style/ZeroLengthPredicate:
- Enabled: false
+ Exclude:
+ - 'app/models/deploy_key.rb'
+ - 'app/models/network/commit.rb'
+ - 'app/models/network/graph.rb'
+ - 'app/models/project_services/asana_service.rb'
+ - 'app/services/boards/create_service.rb'
+ - 'app/services/merge_requests/conflicts/list_service.rb'
+ - 'lib/declarative_policy/dsl.rb'
+ - 'lib/extracts_path.rb'
+ - 'lib/gitlab/git/repository.rb'
+
+# Offense count: 22050
+# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
+# URISchemes: http, https
+Metrics/LineLength:
+ Max: 1310
diff --git a/.ruby-version b/.ruby-version
index cc6c9a491e0..e75da3e63d6 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.3.5
+2.3.6
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd7825b5f82..a9539bbed87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,155 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.3.0 (2017-12-22)
+
+### Security (1 change, 1 of them is from the community)
+
+- Upgrade jQuery to 2.2.4. !15570 (Takuya Noguchi)
+
+### Fixed (55 changes, 8 of them are from the community)
+
+- Fail jobs if its dependency is missing. !14009
+- Fix errors when selecting numeric-only labels in the labels autocomplete selector. !14607 (haseebeqx)
+- Fix pipeline status transition for single manual job. This would also fix pipeline duration becuse it is depending on status transition. !15251
+- Fix acceptance of username for Mattermost service update. !15275
+- Set the default gitlab-shell timeout to 3 hours. !15292
+- Make sure a user can add projects to subgroups they have access to. !15294
+- OAuth identity lookups case-insensitive. !15312
+- Fix filter by my reaction is not working. !15345 (Hiroyuki Sato)
+- Avoid deactivation when pipeline schedules execute a branch includes `[ci skip]` comment. !15405
+- Add recaptcha modal to issue updates detected as spam. !15408
+- Fix item name and namespace text overflow in Projects dropdown. !15451
+- Removed unused rake task, 'rake gitlab:sidekiq:drop_post_receive'. !15493
+- Fix commits page throwing 500 when the multi-file editor was enabled. !15502
+- Fix Issue comment submit button being disabled when pasting content from another GFM note. !15530
+- Reenable Prometheus metrics, add more control over Prometheus method instrumentation. !15558
+- Fix broadcast message not showing up on login page. !15578
+- Initializes the branches dropdown when the 'Start new pipeline' failed due to validation errors. !15588 (Christiaan Van den Poel)
+- Fix merge requests where the source or target branch name matches a tag name. !15591
+- Create a fork network for forks with a deleted source. !15595
+- Fix search results when a filename would contain a special character. !15606 (haseebeqx)
+- Strip leading & trailing whitespaces in CI/CD secret variable keys. !15615
+- Correctly link to a forked project from the new fork page. !15653
+- Fix the fork project functionality for projects with hashed storage. !15671
+- Added default order to UsersFinder. !15679
+- Fix graph notes number duplication. !15696 (Vladislav Kaverin)
+- Fix updateEndpoint undefined error for issue_show app root. !15698
+- Change boards page boards_data absolute urls to paths. !15703
+- Using appropiate services in the API for managing forks. !15709
+- Confirming email with invalid token should no longer generate an error. !15726
+- fix #39233 - 500 in merge request. !15774 (Martin Nowak)
+- Use Markdown styling for new project guidelines. !15785 (Markus Koller)
+- Fix error during schema dump. !15866
+- Fix broken illustration images for monitoring page empty states. !15889
+- Make sure user email is read only when synced with LDAP. !15915
+- Fixed outdated browser flash positioning.
+- Fix gitlab:import:repos Rake task moving repositories into the wrong location.
+- Gracefully handle case when repository's root ref does not exist.
+- Fix GitHub importer using removed interface.
+- Align retry button with job title with new grid size.
+- Fixed admin welcome screen new group path.
+- Fix related branches/Merge requests failing to load when the hostname setting is changed.
+- Init zen mode in snippets pages.
+- Remove extra margin from wordmark in header.
+- Fixed long commit links not wrapping correctly.
+- Fixed deploy keys remove button loading state not resetting.
+- Use app host instead of asset host when rendering image blob or diff.
+- Hide log size for mobile screens.
+- Fix sending notification emails to users with the mention level set who were mentioned in an issue or merge request description.
+- Changed validation error message on wrong milestone dates. (Xurxo Méndez Pérez)
+- Fix access to the final page of todos.
+- Fixed new group milestone breadcrumbs.
+- Fix image diff notification email from showing wrong content.
+- Fixed merge request lock icon size.
+- Make sure head pippeline always corresponds with the head sha of an MR.
+- Prevent 500 error when inspecting job after trigger was removed.
+
+### Changed (14 changes, 2 of them are from the community)
+
+- Only owner or master can erase jobs. !15216
+- Allow password authentication to be disabled entirely. !15223 (Markus Koller)
+- Add the option to automatically run a pipeline after updating AutoDevOps settings. !15380
+- Add total_time_spent to the `changes` hash in issuable Webhook payloads. !15381
+- Monitor NFS shards for circuitbreaker in a separate process. !15426
+- Add inline editing to issues on mobile. !15438
+- Add custom brand text on new project pages. !15541 (Markus Koller)
+- Show only group name by default and put full namespace in tooltip in Groups tree. !15650
+- Use custom user agent header in all GCP API requests. !15705
+- Changed the deploy markers on the prometheus dashboard to be more verbose. !38032
+- Animate contextual sidebar on collapse/expand.
+- Update emojis. Add :gay_pride_flag: and :speech_left:. Remove extraneous comma in :cartwheel_tone4:.
+- When a custom header logo is present, don't show GitLab type logo.
+- Improved diff changed files dropdown design.
+
+### Performance (19 changes)
+
+- Add timeouts for Gitaly calls. !15047
+- Performance issues when loading large number of wiki pages. !15276
+- Add performance logging to UpdateMergeRequestsWorker. !15360
+- Keep track of all circuitbreaker keys in a set. !15613
+- Improve the performance for counting commits. !15628
+- Reduce requests for project forks on show page of projects that have forks. !15663
+- Perform SQL matching of Build&Runner tags to greatly speed-up job picking.
+- Only load branch names for protected branch checks.
+- Optimize API /groups/:id/projects by preloading associations.
+- Remove allocation tracking code from InfluxDB sampler for performance.
+- Throttle the number of UPDATEs triggered by touch.
+- Make finding most recent merge request diffs more efficient.
+- Fetch blobs in bulk when generating diffs.
+- Cache commits for MergeRequest diffs.
+- Use fuzzy search with minimum length of 3 characters where appropriate.
+- Add axios to common file.
+- Remove template selector from global namespace.
+- check the import_status field before doing SQL operations to check the import url.
+- Stop sending milestone and labels data over the wire for MR widget requests.
+
+### Added (22 changes, 15 of them are from the community)
+
+- Limit autocomplete menu to applied labels. !11110 (Vitaliy @blackst0ne Klachkov)
+- Make diff notes created on a commit in a merge request to persist a rebase. !12148
+- Allow creation of merge request from email. !13817 (janp)
+- Add an ability to use a custom branch name on creation from issues. !13884 (Vitaliy @blackst0ne Klachkov)
+- Add anonymous rate limit per IP, and authenticated (web or API) rate limits per user. !14708
+- Create a new form to add Existing Kubernetes Cluster. !14805
+- Add support of Mermaid (generation of diagrams and flowcharts from text). !15107 (Vitaliy @blackst0ne Klachkov)
+- Add total time spent to milestones. !15116 (George Andrinopoulos)
+- Add /groups/:id/subgroups endpoint to API. !15142 (marbemac)
+- Add administrative endpoint to list all pages domains. !15160 (Travis Miller)
+- Adds Rubocop rule for line break after guard clause. !15188 (Jacopo Beschi @jacopo-beschi)
+- Add edit button to mobile file view. !15199 (Travis Miller)
+- Add dropdown sort to group milestones. !15230 (George Andrinopoulos)
+- added support for ordering and sorting in notes api. !15342 (haseebeqx)
+- Hashed Storage migration script now supports migrating project attachments. !15352
+- New API endpoint - list jobs for a specified runner. !15432
+- Add new API endpoint - get a namespace by ID. !15442
+- Disables autocomplete in filtered searc. !15477 (Jacopo Beschi @jacopo-beschi)
+- Update empty state page of merge request 'changes' tab. !15611 (Vitaliy @blackst0ne Klachkov)
+- Allow git pull/push on group/user/project redirects. !15670
+- show status of gitlab reference links in wiki. !15694 (haseebeqx)
+- Add email confirmation parameters for user creation and update via API. (Daniel Juarez)
+
+### Other (17 changes, 7 of them are from the community)
+
+- Enable UnnecessaryMantissa in scss-lint. !15255 (Takuya Noguchi)
+- Add untracked files to uploads table. !15270
+- Move update_project_counter_caches? out of issue and merge request. !15300 (George Andrinopoulos)
+- Removed tooltip from clone dropdown. !15334
+- Clean up empty fork networks. !15373
+- Create issuable destroy service. !15604 (George Andrinopoulos)
+- Upgrade seed-fu to 2.3.7. !15607 (Takuya Noguchi)
+- Rename GKE as Kubernetes Engine. !15608 (Takuya Noguchi)
+- Prefer ci_config_path validation for leading slashes instead of sanitizing the input. !15672 (Christiaan Van den Poel)
+- Fix typo in docs about Elasticsearch. !15699 (Takuya Noguchi)
+- Add internationalization support for the prometheus integration. !33338
+- Export text utils functions as es6 module and add tests.
+- Stop reloading the page when using pagination and tabs - use API calls - in Pipelines table.
+- Clean up schema of the "issues" table.
+- Clarify wording of protected branch settings for the default branch.
+- Update svg external depencency.
+- Clean up schema of the "merge_requests" table.
+
+
## 10.2.5 (2017-12-15)
### Fixed (8 changes)
diff --git a/Gemfile b/Gemfile
index b6ffaf80f24..db86c86428c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -334,9 +334,11 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
- gem 'rubocop', '~> 0.49.1', require: false
- gem 'rubocop-rspec', '~> 1.15.1', require: false
- gem 'rubocop-gitlab-security', '~> 0.1.0', require: false
+ gem 'gitlab-styles', '~> 2.2.0', require: false
+ # Pin these dependencies, otherwise a new rule could break the CI pipelines
+ gem 'rubocop', '~> 0.52.0'
+ gem 'rubocop-rspec', '~> 1.20.1'
+
gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index a6e3c9e27cc..b11b438a29c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -303,6 +303,10 @@ GEM
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.3)
+ gitlab-styles (2.2.0)
+ rubocop (~> 0.51)
+ rubocop-gitlab-security (~> 0.1.0)
+ rubocop-rspec (~> 1.19)
gitlab_omniauth-ldap (2.0.4)
net-ldap (~> 0.16)
omniauth (~> 1.3)
@@ -777,21 +781,21 @@ GEM
pg
rails
sqlite3
- rubocop (0.49.1)
+ rubocop (0.52.0)
parallel (~> 1.10)
- parser (>= 2.3.3.1, < 3.0)
+ parser (>= 2.4.0.2, < 3.0)
powerpack (~> 0.1)
- rainbow (>= 1.99.1, < 3.0)
+ rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
- rubocop-gitlab-security (0.1.0)
- rubocop (>= 0.47.1)
- rubocop-rspec (1.15.1)
- rubocop (>= 0.42.0)
+ rubocop-gitlab-security (0.1.1)
+ rubocop (>= 0.51)
+ rubocop-rspec (1.20.1)
+ rubocop (>= 0.51.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.16.2)
- ruby-progressbar (1.8.1)
+ ruby-progressbar (1.9.0)
ruby-saml (1.4.1)
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
@@ -1046,6 +1050,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
+ gitlab-styles (~> 2.2.0)
gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
@@ -1148,9 +1153,8 @@ DEPENDENCIES
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
- rubocop (~> 0.49.1)
- rubocop-gitlab-security (~> 0.1.0)
- rubocop-rspec (~> 1.15.1)
+ rubocop (~> 0.52.0)
+ rubocop-rspec (~> 1.20.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8)
diff --git a/VERSION b/VERSION
index 73cdb768a24..80959b81ba4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-10.3.0-pre
+10.4.0-pre
diff --git a/app/assets/images/icons.json b/app/assets/images/icons.json
index 68d6528758b..38c1faccbf1 100644
--- a/app/assets/images/icons.json
+++ b/app/assets/images/icons.json
@@ -1 +1 @@
-{"iconCount":181,"spriteSize":81482,"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","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","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-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","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","spam","spinner","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_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","user","users","volume-up","warning","work"]} \ No newline at end of file
+{"iconCount":186,"spriteSize":84748,"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","spam","spinner","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_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","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
index fd8f7862911..42f5377a10e 100644
--- a/app/assets/images/icons.svg
+++ b/app/assets/images/icons.svg
@@ -1 +1 @@
-<?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="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="M7.228 5l-.475-1.335A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H7.228zM13 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="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="M8.462 2.177a.505.505 0 0 1-.038.044l.038-.044zm-.787 0l.038.043a.5.5 0 0 1-.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="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-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 7V1a1 1 0 1 1 2 0v6h6a1 1 0 0 1 0 2H9v6a1 1 0 0 1-2 0V9H1a1 1 0 1 1 0-2h6z"/></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="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.666 4.423a5 5 0 1 1-.203 6.944 1 1 0 1 0-1.478 1.347 7 7 0 1 0 .12-9.556L1.842 2.137a.5.5 0 0 0-.815.385L1 7.26a.5.5 0 0 0 .607.492l4.629-1.013a.5.5 0 0 0 .207-.877L4.666 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.494 4.423a5 5 0 1 0 .203 6.944 1 1 0 1 1 1.478 1.347 7 7 0 1 1-.12-9.556l1.262-1.021a.5.5 0 0 1 .815.385l.028 4.738a.5.5 0 0 1-.607.492L9.924 6.739a.5.5 0 0 1-.207-.877l1.777-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="ewfirst-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="ewsecond-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="ewthird-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 0h8a3 3 0 0 1 3 3v7.186a3 3 0 0 1-1.426 2.554l-4 2.465a3 3 0 0 1-3.148 0l-4-2.465A3 3 0 0 1 1 10.186V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v7.186a1 1 0 0 0 .475.852l4 2.464a1 1 0 0 0 1.05 0l4-2.464a1 1 0 0 0 .475-.852V3a1 1 0 0 0-1-1H4zm0 1.5a.5.5 0 0 1 .5-.5h4v8.837a.5.5 0 0 1-.753.431l-3.5-2.052A.5.5 0 0 1 4 9.785V3.5z"/></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="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="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 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="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.34 10.479A3 3 0 0 1 12.756 15h-9.51A3 3 0 0 1 .66 10.479l4.755-8.083a3 3 0 0 1 5.172 0l4.755 8.083zm-6.478-7.07a1 1 0 0 0-1.724 0l-4.755 8.084A1 1 0 0 0 3.245 13h9.51a1 1 0 0 0 .862-1.507L8.862 3.41zM8 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
+<?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 6H5.333a2 2 0 0 0-1.664.89l-3.333 5a2 2 0 0 0-.285.662A3.017 3.017 0 0 1 0 12V3a2 2 0 0 1 2-2h2.838a3 3 0 0 1 2.828 2H11a3 3 0 0 1 3 3zM5.333 7h9.53a1 1 0 0 1 .875 1.486l-2.492 4.485A2 2 0 0 1 11.498 14H2a1 1 0 0 1-.832-1.555l3.333-5A1 1 0 0 1 5.333 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="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="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 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="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/job_not_triggered.svg b/app/assets/images/illustrations/job_not_triggered.svg
new file mode 100644
index 00000000000..e13c1cb0a7d
--- /dev/null
+++ b/app/assets/images/illustrations/job_not_triggered.svg
@@ -0,0 +1 @@
+<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/manual_action.svg b/app/assets/images/illustrations/manual_action.svg
new file mode 100644
index 00000000000..85735855b46
--- /dev/null
+++ b/app/assets/images/illustrations/manual_action.svg
@@ -0,0 +1 @@
+<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
index 707efa736e4..40efeb2de57 100644
--- a/app/assets/images/illustrations/merge_request_changes_empty.svg
+++ b/app/assets/images/illustrations/merge_request_changes_empty.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 374 268" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="d" width="230" height="176" rx="10" fill="#fff"/><rect id="e" width="14" rx="2" height="4"/><rect id="f" width="14" x="40" rx="2" height="4"/><rect id="g" width="14" x="40" y="24" rx="2" height="4"/><rect id="h" width="7" x="20" y="12" rx="2" height="4"/><rect id="i" width="7" y="24" rx="2" height="4"/><rect id="j" width="7" x="33" y="12" rx="2" height="4"/><circle id="l" cx="31" cy="31" r="31"/><circle id="c" cx="35" cy="35" r="35"/><circle id="a" cx="44" cy="44" r="44"/><circle id="b" cx="31" cy="31" r="31"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 94)"><circle cx="57" cy="57" r="44" fill="#f9f9f9"/><g transform="rotate(-7.999 120.507 -22.508)"><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.26l6.394-4.529m2.312 3.264l-7.972 5.647A3.001 3.001 0 0 1 24 62.194v-27.2c0-5.523 4.477-10 10-10h22c5.523 0 10 4.477 10 10v14c0 5.523-4.477 10-10 10H36.706"/><path fill="#fc6d26" d="M38 40a2 2 0 1 1 .001 3.999A2 2 0 0 1 38 40m7 0a2 2 0 1 1 .001 3.999A2 2 0 0 1 45 40m7 0a2 2 0 1 1 .001 3.999A2 2 0 0 1 52 40"/></g></g><g transform="translate(48)"><circle cx="41" cy="41" r="31" fill="#f9f9f9"/><g transform="rotate(-7.999 84.554 -15.551)"><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.834 34.834 0 0 1 337 79c19.33 0 35 15.67 35 35s-15.67 35-35 35a34.831 34.831 0 0 1-17-4.399v72.4c0 6.627-5.373 12-12 12h-11.58c.381 1.941.58 3.947.58 6 0 17.12-13.879 31-31 31-17.12 0-31-13.879-31-31 0-2.053.2-4.059.58-6"/><g transform="translate(87 50)"><g transform="rotate(7.999 -44.933 1563.894)"><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"/><g fill="#6b4fbb"><path d="M19.419 6.996h-.007L16.959 4l-2.454 2.997H14.5L12.046 4 9.591 6.998h-.003L7.133 4 4.677 6.999H2.001c2.605-4.204 7.231-7 12.502-7 5.269 0 9.892 2.793 12.498 6.994h-2.676l-2.452-2.994-2.453 2.996"/><circle cx="9.5" cy="17.5" r="1.5"/><circle cx="20.5" cy="17.5" r="1.5"/></g></g></g><use 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)"><use fill="#6b4fbb" xlink:href="#e"/><rect width="14" height="4" x="20" fill="#f0edf8" rx="2"/><use fill="#fef0e9" xlink:href="#f"/><rect width="14" height="4" y="12" fill="#f0edf8" rx="2"/><use fill="#fef0e9" xlink:href="#g"/><rect width="14" height="4" y="48" fill="#e1dbf1" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#fef0e9" rx="2"/><use fill="#fee1d3" xlink:href="#h"/><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"/><use fill="#fc6d26" xlink:href="#i"/><rect width="21" height="4" x="13" y="24" fill="#e1dbf1" rx="2"/><rect width="21" height="4" y="36" fill="#eee" rx="2"/><use fill="#6b4fbb" xlink:href="#j"/><g transform="translate(98)"><use fill="#fee1d3" xlink:href="#e"/><rect width="14" height="4" x="20" fill="#f0edf8" rx="2"/><use fill="#fc6d26" xlink:href="#f"/><rect width="14" height="4" y="12" fill="#fef0e9" rx="2" id="k"/><use fill="#e1dbf1" xlink:href="#g"/><rect width="14" height="4" y="48" fill="#f0edf8" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#fee1d3" rx="2"/><use fill="#fc6d26" xlink:href="#h"/><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"/><use fill="#6b4fbb" xlink:href="#i"/><rect width="21" height="4" x="13" y="24" fill="#fee1d3" rx="2"/><rect width="21" height="4" y="36" fill="#fef0e9" rx="2"/><use fill="#6b4fbb" xlink:href="#j"/></g><g transform="translate(0 60)"><use fill="#f0edf8" xlink:href="#e"/><rect width="14" height="4" x="20" fill="#6b4fbb" rx="2"/><use fill="#e1dbf1" xlink:href="#f"/><use xlink:href="#k"/><use fill="#fee1d3" xlink:href="#g"/><use fill="#eee" xlink:href="#h"/><use fill="#6b4fbb" xlink:href="#i"/><rect width="21" height="4" x="13" y="24" fill="#fef0e9" rx="2"/><use fill="#fc6d26" xlink:href="#j"/></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="rotate(7.999 -1289.786 1797.583)"><use fill="#fff" xlink:href="#l"/><circle cx="31" cy="31" r="29" stroke="#eee" stroke-width="4"/><path fill="#fc6d26" d="M29 29h-6a2 2 0 1 0 0 4h6v6a2 2 0 1 0 4 0v-6h6a2 2 0 1 0 0-4h-6v-6a2 2 0 1 0-4 0v6"/></g></g></svg> \ No newline at end of file
+<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/service_desk_callout.svg b/app/assets/images/illustrations/service_desk_callout.svg
new file mode 100644
index 00000000000..2886388279e
--- /dev/null
+++ b/app/assets/images/illustrations/service_desk_callout.svg
@@ -0,0 +1 @@
+<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
new file mode 100644
index 00000000000..daaaeae6a17
--- /dev/null
+++ b/app/assets/images/illustrations/service_desk_empty.svg
@@ -0,0 +1 @@
+<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/javascripts/api.js b/app/assets/javascripts/api.js
index d963101028a..21d8c790e90 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
@@ -6,6 +7,7 @@ const Api = {
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
+ projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels',
groupLabelsPath: '/groups/:namespace_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
@@ -76,6 +78,14 @@ const Api = {
.done(projects => callback(projects));
},
+ // Return single project
+ project(projectPath) {
+ const url = Api.buildUrl(Api.projectPath)
+ .replace(':id', encodeURIComponent(projectPath));
+
+ return axios.get(url);
+ },
+
newLabel(namespacePath, projectPath, data, callback) {
let url;
@@ -115,7 +125,7 @@ const Api = {
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
- .replace(':id', id);
+ .replace(':id', encodeURIComponent(id));
return this.wrapAjaxCall({
url,
type: 'POST',
@@ -127,7 +137,7 @@ const Api = {
branchSingle(id, branch) {
const url = Api.buildUrl(Api.branchSinglePath)
- .replace(':id', id)
+ .replace(':id', encodeURIComponent(id))
.replace(':branch', branch);
return this.wrapAjaxCall({
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 2cfd6179a25..637d0dbde23 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -30,6 +30,7 @@ export default class Clusters {
installHelmPath,
installIngressPath,
installRunnerPath,
+ installPrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
@@ -44,6 +45,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
installRunnerEndpoint: installRunnerPath,
+ installPrometheusEndpoint: installPrometheusPath,
});
this.toggle = this.toggle.bind(this);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index e5ae439d26e..cd58b88db69 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -67,6 +67,16 @@ export default {
and send the results back to GitLab.`,
));
},
+ prometheusDescription() {
+ return sprintf(
+ _.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
+ gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
+ ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
+ </a>`,
+ },
+ false,
+ );
+ },
},
};
</script>
@@ -106,6 +116,16 @@ export default {
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
/>
+ <application-row
+ id="prometheus"
+ :title="applications.prometheus.title"
+ title-link="https://prometheus.io/docs/introduction/overview/"
+ :description="prometheusDescription"
+ :status="applications.prometheus.status"
+ :status-reason="applications.prometheus.statusReason"
+ :request-status="applications.prometheus.requestStatus"
+ :request-reason="applications.prometheus.requestReason"
+ />
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
<!-- Add GitLab Runner row, all other plumbing is complete -->
</div>
diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js
index 755c2981c2e..13468578f4f 100644
--- a/app/assets/javascripts/clusters/services/clusters_service.js
+++ b/app/assets/javascripts/clusters/services/clusters_service.js
@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
runner: this.options.installRunnerEndpoint,
+ prometheus: this.options.installPrometheusEndpoint,
};
}
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index e731cdc3042..bd4a1fb37f9 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -28,6 +28,13 @@ export default class ClusterStore {
requestStatus: null,
requestReason: null,
},
+ prometheus: {
+ title: s__('ClusterIntegration|Prometheus'),
+ status: null,
+ statusReason: null,
+ requestStatus: null,
+ requestReason: null,
+ },
},
};
}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
index f54ea7df522..cbce9205e75 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -2,6 +2,7 @@
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -12,6 +13,7 @@
userAvatarImage,
totalTime,
limitWarning,
+ icon,
},
};
</script>
@@ -52,7 +54,10 @@
</template>
<template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch">
- <i class= "fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
</span>
</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
index 5d95ddcd90e..508a411e599 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -13,6 +14,7 @@
userAvatarImage,
totalTime,
limitWarning,
+ icon,
},
computed: {
iconBranch() {
@@ -37,7 +39,10 @@
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
index 04d5440b77b..88fa6b073ca 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -12,6 +13,7 @@
components: {
totalTime,
limitWarning,
+ icon,
},
computed: {
iconBuildStatus() {
@@ -40,7 +42,10 @@
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 62867c56214..118437b82a3 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -73,7 +73,6 @@ import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges';
-import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown';
import NewGroupChild from './groups/new_group_child';
import AbuseReports from './abuse_reports';
@@ -111,6 +110,8 @@ import Activities from './activities';
return false;
}
+ const fail = () => Flash('Error loading dynamic module');
+
path = page.split(':');
shortcut_handler = null;
@@ -447,9 +448,6 @@ import Activities from './activities';
break;
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
-
- if (UserFeatureHelper.isNewRepoEnabled()) break;
-
new TreeView();
new BlobViewer();
new NewCommitForm($('.js-create-dir-form'));
@@ -468,7 +466,6 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:blob:show':
- if (UserFeatureHelper.isNewRepoEnabled()) break;
new BlobViewer();
initBlob();
break;
@@ -545,7 +542,7 @@ import Activities from './activities';
new CILintEditor();
break;
case 'users:show':
- new UserCallout();
+ import('./pages/users/show').then(m => m.default()).catch(fail);
break;
case 'admin:conversational_development_index:show':
new UserCallout();
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 6110d961609..abb04d77f8f 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -161,13 +161,16 @@ export default () => {
const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];
- sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => {
- clearTimeout(timeoutId);
-
- timeoutId = setTimeout(() => {
- if (currentOpenMenu) hideMenu(currentOpenMenu);
- }, getHideSubItemsInterval());
- });
+ const topItems = sidebar.querySelector('.sidebar-top-level-items');
+ if (topItems) {
+ sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => {
+ clearTimeout(timeoutId);
+
+ timeoutId = setTimeout(() => {
+ if (currentOpenMenu) hideMenu(currentOpenMenu);
+ }, getHideSubItemsInterval());
+ });
+ }
headerHeight = document.querySelector('.nav-sidebar').offsetTop;
diff --git a/app/assets/javascripts/helpers/user_feature_helper.js b/app/assets/javascripts/helpers/user_feature_helper.js
deleted file mode 100644
index 638118a5204..00000000000
--- a/app/assets/javascripts/helpers/user_feature_helper.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Cookies from 'js-cookie';
-
-export default {
- isNewRepoEnabled() {
- return Cookies.get('new_repo') === 'true';
- },
-};
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
new file mode 100644
index 00000000000..704dff981df
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -0,0 +1,66 @@
+<script>
+ import { mapState } from 'vuex';
+ import icon from '../../../vue_shared/components/icon.vue';
+ import listItem from './list_item.vue';
+ import listCollapsed from './list_collapsed.vue';
+
+ export default {
+ components: {
+ icon,
+ listItem,
+ listCollapsed,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ fileList: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'currentProjectId',
+ 'currentBranchId',
+ 'rightPanelCollapsed',
+ ]),
+ },
+ methods: {
+ toggleCollapsed() {
+ this.$emit('toggleCollapsed');
+ },
+ },
+
+ };
+</script>
+
+<template>
+ <div class="multi-file-commit-list">
+ <list-collapsed
+ v-if="rightPanelCollapsed"
+ />
+ <template v-else>
+ <ul
+ v-if="fileList.length"
+ class="list-unstyled append-bottom-0"
+ >
+ <li
+ v-for="file in fileList"
+ :key="file.key"
+ >
+ <list-item
+ :file="file"
+ />
+ </li>
+ </ul>
+ <div
+ v-else
+ class="help-block prepend-top-0"
+ >
+ No changes
+ </div>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index 6a0262f271b..6a0262f271b 100644
--- a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 742f746e02f..742f746e02f 100644
--- a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
new file mode 100644
index 00000000000..7f29a355eca
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -0,0 +1,73 @@
+<script>
+import { mapState, mapGetters } from 'vuex';
+import ideSidebar from './ide_side_bar.vue';
+import ideContextbar from './ide_context_bar.vue';
+import repoTabs from './repo_tabs.vue';
+import repoFileButtons from './repo_file_buttons.vue';
+import ideStatusBar from './ide_status_bar.vue';
+import repoPreview from './repo_preview.vue';
+import repoEditor from './repo_editor.vue';
+
+export default {
+ computed: {
+ ...mapState([
+ 'currentBlobView',
+ 'selectedFile',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ 'activeFile',
+ ]),
+ },
+ components: {
+ ideSidebar,
+ ideContextbar,
+ repoTabs,
+ repoFileButtons,
+ ideStatusBar,
+ repoEditor,
+ repoPreview,
+ },
+ mounted() {
+ const returnValue = 'Are you sure you want to lose unsaved changes?';
+ window.onbeforeunload = (e) => {
+ if (!this.changedFiles.length) return undefined;
+
+ Object.assign(e, {
+ returnValue,
+ });
+ return returnValue;
+ };
+ },
+};
+</script>
+
+<template>
+ <div
+ class="ide-view"
+ >
+ <ide-sidebar/>
+ <div
+ class="multi-file-edit-pane"
+ >
+ <template
+ v-if="activeFile">
+ <repo-tabs/>
+ <component
+ class="multi-file-edit-pane-content"
+ :is="currentBlobView"
+ />
+ <repo-file-buttons/>
+ <ide-status-bar
+ :file="selectedFile"/>
+ </template>
+ <template
+ v-else>
+ <div class="ide-empty-state">
+ <h2 class="clgray">Welcome to the GitLab IDE</h2>
+ </div>
+ </template>
+ </div>
+ <ide-contextbar/>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
new file mode 100644
index 00000000000..5a08718e386
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_context_bar.vue
@@ -0,0 +1,75 @@
+<script>
+import { mapGetters, mapState, mapActions } from 'vuex';
+import repoCommitSection from './repo_commit_section.vue';
+import icon from '../../vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ repoCommitSection,
+ icon,
+ },
+ computed: {
+ ...mapState([
+ 'rightPanelCollapsed',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ ]),
+ currentIcon() {
+ return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'right',
+ collapsed: !this.rightPanelCollapsed,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="multi-file-commit-panel"
+ :class="{
+ 'is-collapsed': rightPanelCollapsed,
+ }"
+ >
+ <div
+ class="multi-file-commit-panel-section">
+ <header
+ class="multi-file-commit-panel-header"
+ :class="{
+ 'is-collapsed': rightPanelCollapsed,
+ }"
+ >
+ <div
+ class="multi-file-commit-panel-header-title"
+ v-if="!rightPanelCollapsed">
+ <icon
+ name="list-bulleted"
+ :size="18"
+ />
+ Staged
+ </div>
+ <button
+ type="button"
+ class="btn btn-transparent multi-file-commit-panel-collapse-btn"
+ @click="toggleCollapsed"
+ >
+ <icon
+ :name="currentIcon"
+ :size="18"
+ />
+ </button>
+ </header>
+ <repo-commit-section
+ class=""/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
new file mode 100644
index 00000000000..bd3a521ff43
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
@@ -0,0 +1,47 @@
+<script>
+import repoTree from './ide_repo_tree.vue';
+import icon from '../../vue_shared/components/icon.vue';
+import newDropdown from './new_dropdown/index.vue';
+
+export default {
+ components: {
+ repoTree,
+ icon,
+ newDropdown,
+ },
+ props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
+ branch: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="branch-container">
+ <div class="branch-header">
+ <div class="branch-header-title">
+ <icon
+ name="branch"
+ :size="12">
+ </icon>
+ {{ branch.name }}
+ </div>
+ <div class="branch-header-btns">
+ <new-dropdown
+ :project-id="projectId"
+ :branch="branch.name"
+ path=""/>
+ </div>
+ </div>
+ <div>
+ <repo-tree
+ :treeId="branch.treeId"/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
new file mode 100644
index 00000000000..61daba6d176
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_project_tree.vue
@@ -0,0 +1,47 @@
+<script>
+import branchesTree from './ide_project_branches_tree.vue';
+import projectAvatarImage from '../../vue_shared/components/project_avatar/image.vue';
+
+export default {
+ components: {
+ branchesTree,
+ projectAvatarImage,
+ },
+ props: {
+ project: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="projects-sidebar">
+ <div class="context-header">
+ <a
+ :title="project.name"
+ :href="project.web_url">
+ <div class="avatar-container s40 project-avatar">
+ <project-avatar-image
+ class="avatar-container project-avatar"
+ :link-href="project.path"
+ :img-src="project.avatar_url"
+ :img-alt="project.name"
+ :img-size="40"
+ />
+ </div>
+ <div class="sidebar-context-title">
+ {{ project.name }}
+ </div>
+ </a>
+ </div>
+ <div class="multi-file-commit-panel-inner-scroll">
+ <branches-tree
+ v-for="(branch, index) in project.branches"
+ :key="branch.name"
+ :project-id="project.path_with_namespace"
+ :branch="branch"/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
new file mode 100644
index 00000000000..b6b089e6b25
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue
@@ -0,0 +1,66 @@
+<script>
+import { mapState } from 'vuex';
+import RepoPreviousDirectory from './repo_prev_directory.vue';
+import RepoFile from './repo_file.vue';
+import RepoLoadingFile from './repo_loading_file.vue';
+import { treeList } from '../stores/utils';
+
+export default {
+ components: {
+ 'repo-previous-directory': RepoPreviousDirectory,
+ 'repo-file': RepoFile,
+ 'repo-loading-file': RepoLoadingFile,
+ },
+ props: {
+ treeId: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'loading',
+ 'isRoot',
+ ]),
+ ...mapState({
+ projectName(state) {
+ return state.project.name;
+ },
+ }),
+ fetchedList() {
+ return treeList(this.$store.state, this.treeId);
+ },
+ hasPreviousDirectory() {
+ return !this.isRoot && this.fetchedList.length;
+ },
+ showLoading() {
+ return this.loading;
+ },
+ },
+};
+</script>
+
+<template>
+<div>
+ <div class="ide-file-list">
+ <table class="table">
+ <tbody
+ v-if="treeId">
+ <repo-previous-directory
+ v-if="hasPreviousDirectory"
+ />
+ <repo-loading-file
+ v-if="showLoading"
+ v-for="n in 5"
+ :key="n"
+ />
+ <repo-file
+ v-for="file in fetchedList"
+ :key="file.key"
+ :file="file"
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
new file mode 100644
index 00000000000..535398d98c2
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -0,0 +1,62 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import projectTree from './ide_project_tree.vue';
+import icon from '../../vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ projectTree,
+ icon,
+ },
+ computed: {
+ ...mapState([
+ 'projects',
+ 'leftPanelCollapsed',
+ ]),
+ currentIcon() {
+ return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'left',
+ collapsed: !this.leftPanelCollapsed,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="multi-file-commit-panel"
+ :class="{
+ 'is-collapsed': leftPanelCollapsed,
+ }"
+ >
+ <div class="multi-file-commit-panel-inner">
+ <project-tree
+ v-for="(project, index) in projects"
+ :key="project.id"
+ :project="project"/>
+ </div>
+ <button
+ type="button"
+ class="btn btn-transparent left-collapse-btn"
+ @click="toggleCollapsed"
+ >
+ <icon
+ :name="currentIcon"
+ :size="18"
+ />
+ <span
+ v-if="!leftPanelCollapsed"
+ class="collapse-text"
+ >Collapse sidebar</span>
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
new file mode 100644
index 00000000000..a24abadd936
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -0,0 +1,71 @@
+<script>
+import { mapState } from 'vuex';
+import icon from '../../vue_shared/components/icon.vue';
+import tooltip from '../../vue_shared/directives/tooltip';
+import timeAgoMixin from '../../vue_shared/mixins/timeago';
+
+export default {
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeAgoMixin,
+ ],
+ computed: {
+ ...mapState([
+ 'selectedFile',
+ ]),
+ },
+};
+</script>
+
+<template>
+ <div
+ class="ide-status-bar">
+ <div>
+ <icon
+ name="branch"
+ :size="12">
+ </icon>
+ {{ selectedFile.branchId }}
+ </div>
+ <div>
+ <div
+ v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
+ Last commit:
+ <a
+ v-tooltip
+ :title="selectedFile.lastCommit.message"
+ :href="selectedFile.lastCommit.url">
+ {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
+ {{ selectedFile.lastCommit.author }}
+ </a>
+ </div>
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.name }}
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.eol }}
+ </div>
+ <div
+ class="text-right">
+ {{ file.editorRow }}:{{ file.editorColumn }}
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.fileLanguage }}
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
index ba7090e4a9d..2119d373d31 100644
--- a/app/assets/javascripts/repo/components/new_branch_form.vue
+++ b/app/assets/javascripts/ide/components/new_branch_form.vue
@@ -44,7 +44,7 @@
this.branchName = '';
if (this.dropdownText) {
- this.dropdownText.textContent = this.currentBranch;
+ this.dropdownText.textContent = this.currentBranchId;
}
this.toggleDropdown();
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
new file mode 100644
index 00000000000..6e67e99a70f
--- /dev/null
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -0,0 +1,101 @@
+<script>
+ import newModal from './modal.vue';
+ import upload from './upload.vue';
+ import icon from '../../../vue_shared/components/icon.vue';
+
+ export default {
+ props: {
+ branch: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ parent: {
+ type: Object,
+ default: null,
+ },
+ },
+ components: {
+ icon,
+ newModal,
+ upload,
+ },
+ data() {
+ return {
+ openModal: false,
+ modalType: '',
+ };
+ },
+ methods: {
+ createNewItem(type) {
+ this.modalType = type;
+ this.toggleModalOpen();
+ },
+ toggleModalOpen() {
+ this.openModal = !this.openModal;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="repo-new-btn pull-right">
+ <div class="dropdown">
+ <button
+ type="button"
+ class="btn btn-sm btn-default dropdown-toggle add-to-tree"
+ data-toggle="dropdown"
+ aria-label="Create new file or directory"
+ >
+ <icon
+ name="plus"
+ :size="12"
+ css-classes="pull-left"
+ />
+ <icon
+ name="arrow-down"
+ :size="12"
+ css-classes="pull-left"
+ />
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="createNewItem('blob')"
+ >
+ {{ __('New file') }}
+ </a>
+ </li>
+ <li>
+ <upload
+ :branch-id="branch"
+ :path="path"
+ :parent="parent"
+ />
+ </li>
+ <li>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="createNewItem('tree')"
+ >
+ {{ __('New directory') }}
+ </a>
+ </li>
+ </ul>
+ </div>
+ <new-modal
+ v-if="openModal"
+ :type="modalType"
+ :branch-id="branch"
+ :path="path"
+ :parent="parent"
+ @toggle="toggleModalOpen"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index c191af7dec3..a0650d37690 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,10 +1,18 @@
<script>
- import { mapActions } from 'vuex';
+ import { mapActions, mapState } from 'vuex';
import { __ } from '../../../locale';
import modal from '../../../vue_shared/components/modal.vue';
export default {
props: {
+ branchId: {
+ type: String,
+ required: true,
+ },
+ parent: {
+ type: Object,
+ default: null,
+ },
type: {
type: String,
required: true,
@@ -28,6 +36,9 @@
]),
createEntryInStore() {
this.createTempEntry({
+ projectId: this.currentProjectId,
+ branchId: this.branchId,
+ parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
@@ -39,6 +50,9 @@
},
},
computed: {
+ ...mapState([
+ 'currentProjectId',
+ ]),
modalTitle() {
if (this.type === 'tree') {
return __('Create new directory');
diff --git a/app/assets/javascripts/repo/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index 14ad32f4ae0..2a2f2a241fc 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -1,12 +1,22 @@
<script>
- import { mapActions } from 'vuex';
+ import { mapActions, mapState } from 'vuex';
export default {
props: {
- path: {
+ branchId: {
type: String,
required: true,
},
+ parent: {
+ type: Object,
+ default: null,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'trees',
+ 'currentProjectId',
+ ]),
},
methods: {
...mapActions([
@@ -22,6 +32,9 @@
this.createTempEntry({
name,
+ projectId: this.currentProjectId,
+ branchId: this.branchId,
+ parent: this.parent,
type: 'blob',
content: result,
base64: !isText,
@@ -42,6 +55,9 @@
openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
},
+ startFileUpload() {
+ this.$refs.fileUpload.click();
+ },
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
@@ -53,16 +69,19 @@
</script>
<template>
- <label
- role="button"
- class="menu-item"
- >
- {{ __('Upload file') }}
+ <div>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="startFileUpload"
+ >
+ {{ __('Upload file') }}
+ </a>
<input
id="file-upload"
type="file"
class="hidden"
ref="fileUpload"
/>
- </label>
+ </div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 4e0178072cb..470db2c9650 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -20,12 +20,13 @@ export default {
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
- collapsed: true,
};
},
computed: {
...mapState([
- 'currentBranch',
+ 'currentProjectId',
+ 'currentBranchId',
+ 'rightPanelCollapsed',
]),
...mapGetters([
'changedFiles',
@@ -42,12 +43,13 @@ export default {
'checkCommitStatus',
'commitChanges',
'getTreeData',
+ 'setPanelCollapsedStatus',
]),
makeCommit(newBranch = false) {
const createNewBranch = newBranch || this.startNewMR;
const payload = {
- branch: createNewBranch ? `${this.currentBranch}-${new Date().getTime().toString()}` : this.currentBranch,
+ branch: createNewBranch ? `${this.currentBranchId}-${new Date().getTime().toString()}` : this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
@@ -55,7 +57,7 @@ export default {
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
})),
- start_branch: createNewBranch ? this.currentBranch : undefined,
+ start_branch: createNewBranch ? this.currentBranchId : undefined,
};
this.showNewBranchModal = false;
@@ -64,7 +66,12 @@ export default {
this.commitChanges({ payload, newMr: this.startNewMR })
.then(() => {
this.submitCommitsLoading = false;
- this.getTreeData();
+ this.$store.dispatch('getTreeData', {
+ projectId: this.currentProjectId,
+ branch: this.currentBranchId,
+ endpoint: `/tree/${this.currentBranchId}`,
+ force: true,
+ });
})
.catch(() => {
this.submitCommitsLoading = false;
@@ -86,19 +93,17 @@ export default {
});
},
toggleCollapsed() {
- this.collapsed = !this.collapsed;
+ this.setPanelCollapsedStatus({
+ side: 'right',
+ collapsed: !this.rightPanelCollapsed,
+ });
},
},
};
</script>
<template>
-<div
- class="multi-file-commit-panel"
- :class="{
- 'is-collapsed': collapsed,
- }"
->
+<div class="multi-file-commit-panel-section">
<modal
v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
@@ -108,28 +113,16 @@ export default {
@toggle="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
- <button
- v-if="collapsed"
- type="button"
- class="btn btn-transparent multi-file-commit-panel-collapse-btn is-collapsed prepend-top-10 append-bottom-10"
- @click="toggleCollapsed"
- >
- <i
- aria-hidden="true"
- class="fa fa-angle-double-left"
- >
- </i>
- </button>
<commit-files-list
title="Staged"
:file-list="changedFiles"
- :collapsed="collapsed"
+ :collapsed="rightPanelCollapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form
class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit"
- v-if="!collapsed"
+ v-if="!rightPanelCollapsed"
>
<div class="multi-file-commit-fieldset">
<textarea
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
index 37bd9003e96..37bd9003e96 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/ide/components/repo_edit_button.vue
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f37cbd1e961..221be4b9074 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -1,6 +1,6 @@
<script>
/* global monaco */
-import { mapGetters, mapActions } from 'vuex';
+import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '../../flash';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
@@ -24,6 +24,9 @@ export default {
...mapActions([
'getRawFileData',
'changeFileContent',
+ 'setFileLanguage',
+ 'setEditorPosition',
+ 'setFileEOL',
]),
initMonaco() {
if (this.shouldHideEditor) return;
@@ -43,12 +46,36 @@ export default {
const model = this.editor.createModel(this.activeFile);
this.editor.attachModel(model);
+
model.onChange((m) => {
this.changeFileContent({
file: this.activeFile,
content: m.getValue(),
});
});
+
+ // Handle Cursor Position
+ this.editor.onPositionChange((instance, e) => {
+ this.setEditorPosition({
+ editorRow: e.position.lineNumber,
+ editorColumn: e.position.column,
+ });
+ });
+
+ this.editor.setPosition({
+ lineNumber: this.activeFile.editorRow,
+ column: this.activeFile.editorColumn,
+ });
+
+ // Handle File Language
+ this.setFileLanguage({
+ fileLanguage: model.language,
+ });
+
+ // Get File eol
+ this.setFileEOL({
+ eol: model.eol,
+ });
},
},
watch: {
@@ -57,12 +84,22 @@ export default {
this.initMonaco();
}
},
+ leftPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
+ rightPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
},
computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
+ ...mapState([
+ 'leftPanelCollapsed',
+ 'rightPanelCollapsed',
+ ]),
shouldHideEditor() {
return this.activeFile.binary && !this.activeFile.raw;
},
@@ -76,13 +113,14 @@ export default {
class="blob-viewer-container blob-editor-container"
>
<div
- v-show="shouldHideEditor"
+ v-if="shouldHideEditor"
v-html="activeFile.html"
>
</div>
<div
v-show="!shouldHideEditor"
ref="editor"
+ class="multi-file-editor-holder"
>
</div>
</div>
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index 75787ad6103..09ca11531b1 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -1,7 +1,8 @@
<script>
- import { mapActions, mapGetters } from 'vuex';
+ import { mapState } from 'vuex';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
+ import newDropdown from './new_dropdown/index.vue';
export default {
mixins: [
@@ -9,20 +10,22 @@
],
components: {
skeletonLoadingContainer,
+ newDropdown,
},
props: {
file: {
type: Object,
required: true,
},
+ showExtraColumns: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
- ...mapGetters([
- 'isCollapsed',
+ ...mapState([
+ 'leftPanelCollapsed',
]),
- isSubmodule() {
- return this.file.type === 'submodule';
- },
fileIcon() {
return {
'fa-spinner fa-spin': this.file.loading,
@@ -30,6 +33,12 @@
'fa-folder-open': !this.file.loading && this.file.opened,
};
},
+ isSubmodule() {
+ return this.file.type === 'submodule';
+ },
+ isTree() {
+ return this.file.type === 'tree';
+ },
levelIndentation() {
return {
marginLeft: `${this.file.level * 16}px`,
@@ -39,13 +48,39 @@
return this.file.id.substr(0, 8);
},
submoduleColSpan() {
- return !this.isCollapsed && this.isSubmodule ? 3 : 1;
+ return !this.leftPanelCollapsed && this.isSubmodule ? 3 : 1;
+ },
+ fileClass() {
+ if (this.file.type === 'blob') {
+ if (this.file.active) {
+ return 'file-open file-active';
+ }
+ return this.file.opened ? 'file-open' : '';
+ }
+ return '';
+ },
+ changedClass() {
+ return {
+ 'fa-circle unsaved-icon': this.file.changed || this.file.tempFile,
+ };
},
},
methods: {
- ...mapActions([
- 'clickedTreeRow',
- ]),
+ clickFile(row) {
+ // Manual Action if a tree is selected/opened
+ if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
+ this.$store.dispatch('toggleTreeOpen', {
+ endpoint: this.file.url,
+ tree: this.file,
+ });
+ }
+ this.$router.push(`/project${row.url}`);
+ },
+ },
+ updated() {
+ if (this.file.type === 'blob' && this.file.active) {
+ this.$el.scrollIntoView();
+ }
},
};
</script>
@@ -53,7 +88,8 @@
<template>
<tr
class="file"
- @click.prevent="clickedTreeRow(file)">
+ :class="fileClass"
+ @click="clickFile(file)">
<td
class="multi-file-table-name"
:colspan="submoduleColSpan"
@@ -66,11 +102,23 @@
>
</i>
<a
- :href="file.url"
class="repo-file-name"
>
{{ file.name }}
</a>
+ <new-dropdown
+ v-if="isTree"
+ :project-id="file.projectId"
+ :branch="file.branchId"
+ :path="file.path"
+ :parent="file"/>
+ <i
+ class="fa"
+ v-if="changedClass"
+ :class="changedClass"
+ aria-hidden="true"
+ >
+ </i>
<template v-if="isSubmodule && file.id">
@
<span class="commit-sha">
@@ -84,7 +132,7 @@
</template>
</td>
- <template v-if="!isCollapsed && !isSubmodule">
+ <template v-if="showExtraColumns && !isSubmodule">
<td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
<a
v-if="file.lastCommit.message"
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
index 34f0d51819a..34f0d51819a 100644
--- a/app/assets/javascripts/repo/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/ide/components/repo_file_buttons.vue
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue
index 8fa637d771f..7eb840c7608 100644
--- a/app/assets/javascripts/repo/components/repo_loading_file.vue
+++ b/app/assets/javascripts/ide/components/repo_loading_file.vue
@@ -1,5 +1,5 @@
<script>
- import { mapGetters } from 'vuex';
+ import { mapState } from 'vuex';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default {
@@ -7,8 +7,8 @@
skeletonLoadingContainer,
},
computed: {
- ...mapGetters([
- 'isCollapsed',
+ ...mapState([
+ 'leftPanelCollapsed',
]),
},
};
@@ -24,7 +24,7 @@
:small="true"
/>
</td>
- <template v-if="!isCollapsed">
+ <template v-if="!leftPanelCollapsed">
<td
class="hidden-sm hidden-xs">
<skeleton-loading-container
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue
index a2b305bbd05..7cd359ea4ed 100644
--- a/app/assets/javascripts/repo/components/repo_prev_directory.vue
+++ b/app/assets/javascripts/ide/components/repo_prev_directory.vue
@@ -1,16 +1,14 @@
<script>
- import { mapGetters, mapState, mapActions } from 'vuex';
+ import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState([
'parentTreeUrl',
- ]),
- ...mapGetters([
- 'isCollapsed',
+ 'leftPanelCollapsed',
]),
colSpanCondition() {
- return this.isCollapsed ? undefined : 3;
+ return this.leftPanelCollapsed ? undefined : 3;
},
},
methods: {
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
index 3d1e0297bd5..3d1e0297bd5 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/ide/components/repo_preview.vue
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index fb29a60df66..5bd63ac9ec5 100644
--- a/app/assets/javascripts/repo/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -27,16 +27,18 @@ export default {
methods: {
...mapActions([
- 'setFileActive',
'closeFile',
]),
+ clickFile(tab) {
+ this.$router.push(`/project${tab.url}`);
+ },
},
};
</script>
<template>
<li
- @click="setFileActive(tab)"
+ @click="clickFile(tab)"
>
<button
type="button"
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue
index ab0bef4f0ac..ab0bef4f0ac 100644
--- a/app/assets/javascripts/repo/components/repo_tabs.vue
+++ b/app/assets/javascripts/ide/components/repo_tabs.vue
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
new file mode 100644
index 00000000000..a9cbf8e370f
--- /dev/null
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -0,0 +1,101 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import store from './stores';
+import flash from '../flash';
+import {
+ getTreeEntry,
+} from './stores/utils';
+
+Vue.use(VueRouter);
+
+/**
+ * Routes below /-/ide/:
+
+/project/h5bp/html5-boilerplate/blob/master
+/project/h5bp/html5-boilerplate/blob/master/app/js/test.js
+
+/project/h5bp/html5-boilerplate/mr/123
+/project/h5bp/html5-boilerplate/mr/123/app/js/test.js
+
+/workspace/123
+/workspace/project/h5bp/html5-boilerplate/blob/my-special-branch
+/workspace/project/h5bp/html5-boilerplate/mr/123
+
+/ = /workspace
+
+/settings
+*/
+
+// Unfortunately Vue Router doesn't work without at least a fake component
+// If you do only data handling
+const EmptyRouterComponent = {
+ render(createElement) {
+ return createElement('div');
+ },
+};
+
+const router = new VueRouter({
+ mode: 'history',
+ base: `${gon.relative_url_root}/-/ide/`,
+ routes: [
+ {
+ path: '/project/:namespace/:project',
+ component: EmptyRouterComponent,
+ children: [
+ {
+ path: ':targetmode/:branch/*',
+ component: EmptyRouterComponent,
+ },
+ {
+ path: 'mr/:mrid',
+ component: EmptyRouterComponent,
+ },
+ ],
+ },
+ ],
+});
+
+router.beforeEach((to, from, next) => {
+ if (to.params.namespace && to.params.project) {
+ store.dispatch('getProjectData', {
+ namespace: to.params.namespace,
+ projectId: to.params.project,
+ })
+ .then(() => {
+ const fullProjectId = `${to.params.namespace}/${to.params.project}`;
+
+ if (to.params.branch) {
+ store.dispatch('getBranchData', {
+ projectId: fullProjectId,
+ branchId: to.params.branch,
+ });
+
+ store.dispatch('getTreeData', {
+ projectId: fullProjectId,
+ branch: to.params.branch,
+ endpoint: `/tree/${to.params.branch}`,
+ })
+ .then(() => {
+ if (to.params[0]) {
+ const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]);
+ if (treeEntry) {
+ store.dispatch('handleTreeEntryAction', treeEntry);
+ }
+ }
+ })
+ .catch((e) => {
+ flash('Error while loading the branch files. Please try again.');
+ throw e;
+ });
+ }
+ })
+ .catch((e) => {
+ flash('Error while loading the project data. Please try again.');
+ throw e;
+ });
+ }
+
+ next();
+});
+
+export default router;
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
new file mode 100644
index 00000000000..a96bd339f51
--- /dev/null
+++ b/app/assets/javascripts/ide/index.js
@@ -0,0 +1,55 @@
+import Vue from 'vue';
+import { mapActions } from 'vuex';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import ide from './components/ide.vue';
+
+import store from './stores';
+import router from './ide_router';
+import Translate from '../vue_shared/translate';
+import ContextualSidebar from '../contextual_sidebar';
+
+function initIde(el) {
+ if (!el) return null;
+
+ return new Vue({
+ el,
+ store,
+ router,
+ components: {
+ ide,
+ },
+ methods: {
+ ...mapActions([
+ 'setInitialData',
+ ]),
+ },
+ created() {
+ const data = el.dataset;
+
+ this.setInitialData({
+ endpoints: {
+ rootEndpoint: data.url,
+ newMergeRequestUrl: data.newMergeRequestUrl,
+ rootUrl: data.rootUrl,
+ },
+ canCommit: convertPermissionToBoolean(data.canCommit),
+ onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch),
+ path: data.currentPath,
+ isRoot: convertPermissionToBoolean(data.root),
+ isInitialRoot: convertPermissionToBoolean(data.root),
+ });
+ },
+ render(createElement) {
+ return createElement('ide');
+ },
+ });
+}
+
+const ideElement = document.getElementById('ide');
+
+Vue.use(Translate);
+
+initIde(ideElement);
+
+const contextualSidebar = new ContextualSidebar();
+contextualSidebar.bindEvents();
diff --git a/app/assets/javascripts/repo/lib/common/disposable.js b/app/assets/javascripts/ide/lib/common/disposable.js
index 84b29bdb600..84b29bdb600 100644
--- a/app/assets/javascripts/repo/lib/common/disposable.js
+++ b/app/assets/javascripts/ide/lib/common/disposable.js
diff --git a/app/assets/javascripts/repo/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 23c4811e6c0..14d9fe4771e 100644
--- a/app/assets/javascripts/repo/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -28,6 +28,14 @@ export default class Model {
return this.model.uri.toString();
}
+ get language() {
+ return this.model.getModeId();
+ }
+
+ get eol() {
+ return this.model.getEOL() === '\n' ? 'LF' : 'CRLF';
+ }
+
get path() {
return this.file.path;
}
diff --git a/app/assets/javascripts/repo/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js
index fd462252795..fd462252795 100644
--- a/app/assets/javascripts/repo/lib/common/model_manager.js
+++ b/app/assets/javascripts/ide/lib/common/model_manager.js
diff --git a/app/assets/javascripts/repo/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js
index 0954b7973c4..0954b7973c4 100644
--- a/app/assets/javascripts/repo/lib/decorations/controller.js
+++ b/app/assets/javascripts/ide/lib/decorations/controller.js
diff --git a/app/assets/javascripts/repo/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js
index dc0b1c95e59..dc0b1c95e59 100644
--- a/app/assets/javascripts/repo/lib/diff/controller.js
+++ b/app/assets/javascripts/ide/lib/diff/controller.js
diff --git a/app/assets/javascripts/repo/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js
index 0e37f5c4704..0e37f5c4704 100644
--- a/app/assets/javascripts/repo/lib/diff/diff.js
+++ b/app/assets/javascripts/ide/lib/diff/diff.js
diff --git a/app/assets/javascripts/repo/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js
index e74c4046330..e74c4046330 100644
--- a/app/assets/javascripts/repo/lib/diff/diff_worker.js
+++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js
diff --git a/app/assets/javascripts/repo/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index db499444402..51e202b9348 100644
--- a/app/assets/javascripts/repo/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -22,6 +22,11 @@ export default class Editor {
this.modelManager = new ModelManager(this.monaco),
this.decorationsController = new DecorationsController(this),
);
+
+ this.debouncedUpdate = _.debounce(() => {
+ this.updateDimensions();
+ }, 200);
+ window.addEventListener('resize', this.debouncedUpdate, false);
}
createInstance(domElement) {
@@ -32,6 +37,9 @@ export default class Editor {
readOnly: false,
contextmenu: true,
scrollBeyondLastLine: false,
+ minimap: {
+ enabled: false,
+ },
}),
this.dirtyDiffController = new DirtyDiffController(
this.modelManager, this.decorationsController,
@@ -70,10 +78,32 @@ export default class Editor {
dispose() {
this.disposable.dispose();
+ window.removeEventListener('resize', this.debouncedUpdate);
// dispose main monaco instance
if (this.instance) {
this.instance = null;
}
}
+
+ updateDimensions() {
+ this.instance.layout();
+ }
+
+ setPosition({ lineNumber, column }) {
+ this.instance.revealPositionInCenter({
+ lineNumber,
+ column,
+ });
+ this.instance.setPosition({
+ lineNumber,
+ column,
+ });
+ }
+
+ onPositionChange(cb) {
+ this.disposable.add(
+ this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
+ );
+ }
}
diff --git a/app/assets/javascripts/repo/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index 701affc466e..701affc466e 100644
--- a/app/assets/javascripts/repo/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
diff --git a/app/assets/javascripts/repo/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js
index af83a1ec0b4..af83a1ec0b4 100644
--- a/app/assets/javascripts/repo/monaco_loader.js
+++ b/app/assets/javascripts/ide/monaco_loader.js
diff --git a/app/assets/javascripts/repo/services/index.js b/app/assets/javascripts/ide/services/index.js
index 994d325e991..1fb24e93f2e 100644
--- a/app/assets/javascripts/repo/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -23,8 +23,11 @@ export default {
return Vue.http.get(file.rawPath, { params: { format: 'json' } })
.then(res => res.text());
},
- getBranchData(projectId, currentBranch) {
- return Api.branchSingle(projectId, currentBranch);
+ getProjectData(namespace, project) {
+ return Api.project(`${namespace}/${project}`);
+ },
+ getBranchData(projectId, currentBranchId) {
+ return Api.branchSingle(projectId, currentBranchId);
},
createBranch(projectId, payload) {
const url = Api.buildUrl(Api.createBranchPath).replace(':id', projectId);
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
new file mode 100644
index 00000000000..c01046c8c76
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -0,0 +1,179 @@
+import Vue from 'vue';
+import { visitUrl } from '../../lib/utils/url_utility';
+import flash from '../../flash';
+import service from '../services';
+import * as types from './mutation_types';
+
+export const redirectToUrl = (_, url) => visitUrl(url);
+
+export const setInitialData = ({ commit }, data) =>
+ commit(types.SET_INITIAL_DATA, data);
+
+export const closeDiscardPopup = ({ commit }) =>
+ commit(types.TOGGLE_DISCARD_POPUP, false);
+
+export const discardAllChanges = ({ commit, getters, dispatch }) => {
+ const changedFiles = getters.changedFiles;
+
+ changedFiles.forEach((file) => {
+ commit(types.DISCARD_FILE_CHANGES, file);
+
+ if (file.tempFile) {
+ dispatch('closeFile', { file, force: true });
+ }
+ });
+};
+
+export const closeAllFiles = ({ state, dispatch }) => {
+ state.openFiles.forEach(file => dispatch('closeFile', { file }));
+};
+
+export const toggleEditMode = (
+ { state, commit, getters, dispatch },
+ force = false,
+) => {
+ const changedFiles = getters.changedFiles;
+
+ if (changedFiles.length && !force) {
+ commit(types.TOGGLE_DISCARD_POPUP, true);
+ } else {
+ commit(types.TOGGLE_EDIT_MODE);
+ commit(types.TOGGLE_DISCARD_POPUP, false);
+ dispatch('toggleBlobView');
+
+ if (!state.editMode) {
+ dispatch('discardAllChanges');
+ }
+ }
+};
+
+export const toggleBlobView = ({ commit, state }) => {
+ if (state.editMode) {
+ commit(types.SET_EDIT_MODE);
+ } else {
+ commit(types.SET_PREVIEW_MODE);
+ }
+};
+
+export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
+ if (side === 'left') {
+ commit(types.SET_LEFT_PANEL_COLLAPSED, collapsed);
+ } else {
+ commit(types.SET_RIGHT_PANEL_COLLAPSED, collapsed);
+ }
+};
+
+export const checkCommitStatus = ({ state }) =>
+ service
+ .getBranchData(state.currentProjectId, state.currentBranchId)
+ .then((data) => {
+ const { id } = data.commit;
+ const selectedBranch =
+ state.projects[state.currentProjectId].branches[state.currentBranchId];
+
+ if (selectedBranch.workingReference !== id) {
+ return true;
+ }
+
+ return false;
+ })
+ .catch(() => flash('Error checking branch data. Please try again.'));
+
+export const commitChanges = (
+ { commit, state, dispatch, getters },
+ { payload, newMr },
+) =>
+ service
+ .commit(state.currentProjectId, payload)
+ .then((data) => {
+ const { branch } = payload;
+ if (!data.short_id) {
+ flash(data.message);
+ return;
+ }
+
+ const selectedProject = state.projects[state.currentProjectId];
+ const lastCommit = {
+ commit_path: `${selectedProject.web_url}/commit/${data.id}`,
+ commit: {
+ message: data.message,
+ authored_date: data.committed_date,
+ },
+ };
+
+ flash(
+ `Your changes have been committed. Commit ${data.short_id} with ${
+ data.stats.additions
+ } additions, ${data.stats.deletions} deletions.`,
+ 'notice',
+ );
+
+ if (newMr) {
+ dispatch(
+ 'redirectToUrl',
+ `${
+ selectedProject.web_url
+ }/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
+ );
+ } else {
+ commit(types.SET_BRANCH_WORKING_REFERENCE, {
+ projectId: state.currentProjectId,
+ branchId: state.currentBranchId,
+ reference: data.id,
+ });
+
+ getters.changedFiles.forEach((entry) => {
+ commit(types.SET_LAST_COMMIT_DATA, {
+ entry,
+ lastCommit,
+ });
+ });
+
+ dispatch('discardAllChanges');
+ dispatch('closeAllFiles');
+
+ window.scrollTo(0, 0);
+ }
+ })
+ .catch(() => flash('Error committing changes. Please try again.'));
+
+export const createTempEntry = (
+ { state, dispatch },
+ { projectId, branchId, parent, name, type, content = '', base64 = false },
+) => {
+ const selectedParent = parent || state.trees[`${projectId}/${branchId}`];
+ if (type === 'tree') {
+ dispatch('createTempTree', {
+ projectId,
+ branchId,
+ parent: selectedParent,
+ name,
+ });
+ } else if (type === 'blob') {
+ dispatch('createTempFile', {
+ projectId,
+ branchId,
+ parent: selectedParent,
+ name,
+ base64,
+ content,
+ });
+ }
+};
+
+export const scrollToTab = () => {
+ Vue.nextTick(() => {
+ const tabs = document.getElementById('tabs');
+
+ if (tabs) {
+ const tabEl = tabs.querySelector('.active .repo-tab');
+
+ tabEl.focus();
+ }
+ });
+};
+
+export * from './actions/tree';
+export * from './actions/file';
+export * from './actions/project';
+export * from './actions/branch';
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
new file mode 100644
index 00000000000..32bdf7fec22
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/branch.js
@@ -0,0 +1,43 @@
+import service from '../../services';
+import flash from '../../../flash';
+import * as types from '../mutation_types';
+
+export const getBranchData = (
+ { commit, state, dispatch },
+ { projectId, branchId, force = false } = {},
+) => new Promise((resolve, reject) => {
+ if ((typeof state.projects[`${projectId}`] === 'undefined' ||
+ !state.projects[`${projectId}`].branches[branchId])
+ || force) {
+ service.getBranchData(`${projectId}`, branchId)
+ .then((data) => {
+ const { id } = data.commit;
+ commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
+ commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+ resolve(data);
+ })
+ .catch(() => {
+ flash('Error loading branch data. Please try again.');
+ reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
+ });
+ } else {
+ resolve(state.projects[`${projectId}`].branches[branchId]);
+ }
+});
+
+export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
+ state.currentProjectId,
+ {
+ branch,
+ ref: state.currentBranchId,
+ },
+)
+.then(res => res.json())
+.then((data) => {
+ const branchName = data.name;
+ const url = location.href.replace(state.currentBranchId, branchName);
+
+ if (this.$router) this.$router.push(url);
+
+ commit(types.SET_CURRENT_BRANCH, branchName);
+});
diff --git a/app/assets/javascripts/repo/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 5bae4fa826a..0f27d5bf1c3 100644
--- a/app/assets/javascripts/repo/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -2,9 +2,9 @@ import { normalizeHeaders } from '../../../lib/utils/common_utils';
import flash from '../../../flash';
import service from '../../services';
import * as types from '../mutation_types';
+import router from '../../ide_router';
import {
findEntry,
- pushState,
setPageTitle,
createTemp,
findIndexOfFile,
@@ -25,7 +25,7 @@ export const closeFile = ({ commit, state, dispatch }, { file, force = false })
dispatch('setFileActive', nextFileToOpen);
} else if (!state.openFiles.length) {
- pushState(file.parentTreeUrl);
+ router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
}
dispatch('getLastCommitData');
@@ -45,6 +45,9 @@ export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
// reset hash for line highlighting
location.hash = '';
+
+ commit(types.SET_CURRENT_PROJECT, file.projectId);
+ commit(types.SET_CURRENT_BRANCH, file.branchId);
};
export const getFileData = ({ state, commit, dispatch }, file) => {
@@ -63,8 +66,6 @@ export const getFileData = ({ state, commit, dispatch }, file) => {
commit(types.TOGGLE_FILE_OPEN, file);
dispatch('setFileActive', file);
commit(types.TOGGLE_LOADING, file);
-
- pushState(file.url);
})
.catch(() => {
commit(types.TOGGLE_LOADING, file);
@@ -82,21 +83,39 @@ export const changeFileContent = ({ commit }, { file, content }) => {
commit(types.UPDATE_FILE_CONTENT, { file, content });
};
-export const createTempFile = ({ state, commit, dispatch }, { tree, name, content = '', base64 = '' }) => {
+export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
+ commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
+};
+
+export const setFileEOL = ({ state, commit }, { eol }) => {
+ commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
+};
+
+export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
+ commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
+};
+
+export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
+ const path = parent.path !== undefined ? parent.path : '';
+ // We need to do the replacement otherwise the web_url + file.url duplicate
+ const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
const file = createTemp({
- name: name.replace(`${state.path}/`, ''),
- path: tree.path,
+ projectId,
+ branchId,
+ name: name.replace(`${path}/`, ''),
+ path,
type: 'blob',
- level: tree.level !== undefined ? tree.level + 1 : 0,
+ level: parent.level !== undefined ? parent.level + 1 : 0,
changed: true,
content,
base64,
+ url: newUrl,
});
- if (findEntry(tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`);
+ if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`);
commit(types.CREATE_TMP_FILE, {
- parent: tree,
+ parent,
file,
});
commit(types.TOGGLE_FILE_OPEN, file);
@@ -106,5 +125,7 @@ export const createTempFile = ({ state, commit, dispatch }, { tree, name, conten
dispatch('toggleEditMode', true);
}
+ router.push(`/project${file.url}`);
+
return Promise.resolve(file);
};
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
new file mode 100644
index 00000000000..75e332090cb
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -0,0 +1,25 @@
+import service from '../../services';
+import flash from '../../../flash';
+import * as types from '../mutation_types';
+
+// eslint-disable-next-line import/prefer-default-export
+export const getProjectData = (
+ { commit, state, dispatch },
+ { namespace, projectId, force = false } = {},
+) => new Promise((resolve, reject) => {
+ if (!state.projects[`${namespace}/${projectId}`] || force) {
+ service.getProjectData(namespace, projectId)
+ .then(res => res.data)
+ .then((data) => {
+ commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
+ if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
+ resolve(data);
+ })
+ .catch(() => {
+ flash('Error loading project data. Please try again.');
+ reject(new Error(`Project not loaded ${namespace}/${projectId}`));
+ });
+ } else {
+ resolve(state.projects[`${namespace}/${projectId}`]);
+ }
+});
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
new file mode 100644
index 00000000000..25909400a75
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -0,0 +1,188 @@
+import { visitUrl } from '../../../lib/utils/url_utility';
+import { normalizeHeaders } from '../../../lib/utils/common_utils';
+import flash from '../../../flash';
+import service from '../../services';
+import * as types from '../mutation_types';
+import router from '../../ide_router';
+import {
+ setPageTitle,
+ findEntry,
+ createTemp,
+ createOrMergeEntry,
+} from '../utils';
+
+export const getTreeData = (
+ { commit, state, dispatch },
+ { endpoint, tree = null, projectId, branch, force = false } = {},
+) => new Promise((resolve, reject) => {
+ // We already have the base tree so we resolve immediately
+ if (!tree && state.trees[`${projectId}/${branch}`] && !force) {
+ resolve();
+ } else {
+ if (tree) commit(types.TOGGLE_LOADING, tree);
+ const selectedProject = state.projects[projectId];
+ // We are merging the web_url that we got on the project info with the endpoint
+ // we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint
+ const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, '');
+ if (completeEndpoint && (!tree || !tree.tempFile)) {
+ service.getTreeData(completeEndpoint)
+ .then((res) => {
+ const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
+
+ setPageTitle(pageTitle);
+
+ return res.json();
+ })
+ .then((data) => {
+ if (!state.isInitialRoot) {
+ commit(types.SET_ROOT, data.path === '/');
+ }
+
+ dispatch('updateDirectoryData', { data, tree, projectId, branch });
+ const selectedTree = tree || state.trees[`${projectId}/${branch}`];
+
+ commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
+ commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path });
+ if (tree) commit(types.TOGGLE_LOADING, selectedTree);
+
+ const prevLastCommitPath = selectedTree.lastCommitPath;
+ if (prevLastCommitPath !== null) {
+ dispatch('getLastCommitData', selectedTree);
+ }
+ resolve(data);
+ })
+ .catch((e) => {
+ flash('Error loading tree data. Please try again.');
+ if (tree) commit(types.TOGGLE_LOADING, tree);
+ reject(e);
+ });
+ } else {
+ resolve();
+ }
+ }
+});
+
+export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
+ if (tree.opened) {
+ // send empty data to clear the tree
+ const data = { trees: [], blobs: [], submodules: [] };
+
+ dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId });
+ } else {
+ dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId });
+ }
+
+ commit(types.TOGGLE_TREE_OPEN, tree);
+};
+
+export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
+ if (row.type === 'tree') {
+ dispatch('toggleTreeOpen', {
+ endpoint: row.url,
+ tree: row,
+ });
+ } else if (row.type === 'submodule') {
+ commit(types.TOGGLE_LOADING, row);
+ visitUrl(row.url);
+ } else if (row.type === 'blob' && row.opened) {
+ dispatch('setFileActive', row);
+ } else {
+ dispatch('getFileData', row);
+ }
+};
+
+export const createTempTree = (
+ { state, commit, dispatch },
+ { projectId, branchId, parent, name },
+) => {
+ let selectedTree = parent;
+ const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
+
+ dirNames.forEach((dirName) => {
+ const foundEntry = findEntry(selectedTree.tree, 'tree', dirName);
+
+ if (!foundEntry) {
+ const path = selectedTree.path !== undefined ? selectedTree.path : '';
+ const tmpEntry = createTemp({
+ projectId,
+ branchId,
+ name: dirName,
+ path,
+ type: 'tree',
+ level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
+ tree: [],
+ url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
+ });
+
+ commit(types.CREATE_TMP_TREE, {
+ parent: selectedTree,
+ tmpEntry,
+ });
+ commit(types.TOGGLE_TREE_OPEN, tmpEntry);
+
+ router.push(`/project${tmpEntry.url}`);
+
+ selectedTree = tmpEntry;
+ } else {
+ selectedTree = foundEntry;
+ }
+ });
+};
+
+export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
+ if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
+
+ service.getTreeLastCommit(tree.lastCommitPath)
+ .then((res) => {
+ const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
+
+ commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
+
+ return res.json();
+ })
+ .then((data) => {
+ data.forEach((lastCommit) => {
+ const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
+
+ if (entry) {
+ commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
+ }
+ });
+
+ dispatch('getLastCommitData', tree);
+ })
+ .catch(() => flash('Error fetching log data.'));
+};
+
+export const updateDirectoryData = (
+ { commit, state },
+ { data, tree, projectId, branch },
+) => {
+ if (!tree) {
+ const existingTree = state.trees[`${projectId}/${branch}`];
+ if (!existingTree) {
+ commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` });
+ }
+ }
+
+ const selectedTree = tree || state.trees[`${projectId}/${branch}`];
+ const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0;
+ const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
+ const createEntry = (entry, type) => createOrMergeEntry({
+ tree: selectedTree,
+ projectId: `${projectId}`,
+ branchId: branch,
+ entry,
+ level,
+ type,
+ parentTreeUrl,
+ });
+
+ const formattedData = [
+ ...data.trees.map(t => createEntry(t, 'tree')),
+ ...data.submodules.map(m => createEntry(m, 'submodule')),
+ ...data.blobs.map(b => createEntry(b, 'blob')),
+ ];
+
+ commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData });
+};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
new file mode 100644
index 00000000000..6b51ccff817
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -0,0 +1,19 @@
+export const changedFiles = state => state.openFiles.filter(file => file.changed);
+
+export const activeFile = state => state.openFiles.find(file => file.active) || null;
+
+export const activeFileExtension = (state) => {
+ const file = activeFile(state);
+ return file ? `.${file.path.split('.').pop()}` : '';
+};
+
+export const canEditFile = (state) => {
+ const currentActiveFile = activeFile(state);
+
+ return state.canCommit &&
+ (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
+};
+
+export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
+
+export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
diff --git a/app/assets/javascripts/repo/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index 6ac9bfd8189..6ac9bfd8189 100644
--- a/app/assets/javascripts/repo/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
diff --git a/app/assets/javascripts/repo/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index bc3390f1506..4e3c10972ba 100644
--- a/app/assets/javascripts/repo/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -1,16 +1,27 @@
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_COMMIT_REF = 'SET_COMMIT_REF';
export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
export const SET_ROOT = 'SET_ROOT';
-export const SET_PREVIOUS_URL = 'SET_PREVIOUS_URL';
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
+export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
+export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
+
+// Project Mutation Types
+export const SET_PROJECT = 'SET_PROJECT';
+export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
+export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
+
+// Branch Mutation Types
+export const SET_BRANCH = 'SET_BRANCH';
+export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
+export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
// Tree mutation types
export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
export const CREATE_TMP_TREE = 'CREATE_TMP_TREE';
export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
+export const CREATE_TREE = 'CREATE_TREE';
// File mutation types
export const SET_FILE_DATA = 'SET_FILE_DATA';
@@ -18,6 +29,9 @@ export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
+export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
+export const SET_FILE_POSITION = 'SET_FILE_POSITION';
+export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
@@ -28,3 +42,4 @@ export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP';
export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
+
diff --git a/app/assets/javascripts/repo/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index ae2ba5bedf7..2fed9019cb6 100644
--- a/app/assets/javascripts/repo/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,4 +1,5 @@
import * as types from './mutation_types';
+import projectMutations from './mutations/project';
import fileMutations from './mutations/file';
import treeMutations from './mutations/tree';
import branchMutations from './mutations/branch';
@@ -32,29 +33,32 @@ export default {
discardPopupOpen,
});
},
- [types.SET_COMMIT_REF](state, ref) {
- Object.assign(state, {
- currentRef: ref,
- });
- },
[types.SET_ROOT](state, isRoot) {
Object.assign(state, {
isRoot,
isInitialRoot: isRoot,
});
},
- [types.SET_PREVIOUS_URL](state, previousUrl) {
+ [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
+ Object.assign(state, {
+ leftPanelCollapsed: collapsed,
+ });
+ },
+ [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
Object.assign(state, {
- previousUrl,
+ rightPanelCollapsed: collapsed,
});
},
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
Object.assign(entry.lastCommit, {
+ id: lastCommit.commit.id,
url: lastCommit.commit_path,
message: lastCommit.commit.message,
+ author: lastCommit.commit.author_name,
updatedAt: lastCommit.commit.authored_date,
});
},
+ ...projectMutations,
...fileMutations,
...treeMutations,
...branchMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
new file mode 100644
index 00000000000..04b9582c5bb
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/mutations/branch.js
@@ -0,0 +1,28 @@
+import * as types from '../mutation_types';
+
+export default {
+ [types.SET_CURRENT_BRANCH](state, currentBranchId) {
+ Object.assign(state, {
+ currentBranchId,
+ });
+ },
+ [types.SET_BRANCH](state, { projectPath, branchName, branch }) {
+ // Add client side properties
+ Object.assign(branch, {
+ treeId: `${projectPath}/${branchName}`,
+ active: true,
+ workingReference: '',
+ });
+
+ Object.assign(state.projects[projectPath], {
+ branches: {
+ [branchName]: branch,
+ },
+ });
+ },
+ [types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
+ Object.assign(state.projects[projectId].branches[branchId], {
+ workingReference: reference,
+ });
+ },
+};
diff --git a/app/assets/javascripts/repo/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index f9ba80b9dc2..5f3655b0092 100644
--- a/app/assets/javascripts/repo/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -6,6 +6,10 @@ export default {
Object.assign(file, {
active,
});
+
+ Object.assign(state, {
+ selectedFile: file,
+ });
},
[types.TOGGLE_FILE_OPEN](state, file) {
Object.assign(file, {
@@ -42,6 +46,22 @@ export default {
changed,
});
},
+ [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
+ Object.assign(file, {
+ fileLanguage,
+ });
+ },
+ [types.SET_FILE_EOL](state, { file, eol }) {
+ Object.assign(file, {
+ eol,
+ });
+ },
+ [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
+ Object.assign(file, {
+ editorRow,
+ editorColumn,
+ });
+ },
[types.DISCARD_FILE_CHANGES](state, file) {
Object.assign(file, {
content: '',
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
new file mode 100644
index 00000000000..2816562a919
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/mutations/project.js
@@ -0,0 +1,23 @@
+import * as types from '../mutation_types';
+
+export default {
+ [types.SET_CURRENT_PROJECT](state, currentProjectId) {
+ Object.assign(state, {
+ currentProjectId,
+ });
+ },
+ [types.SET_PROJECT](state, { projectPath, project }) {
+ // Add client side properties
+ Object.assign(project, {
+ tree: [],
+ branches: {},
+ active: true,
+ });
+
+ Object.assign(state, {
+ projects: Object.assign({}, state.projects, {
+ [projectPath]: project,
+ }),
+ });
+ },
+};
diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
index 130221c9fda..4fe438ab465 100644
--- a/app/assets/javascripts/repo/stores/mutations/tree.js
+++ b/app/assets/javascripts/ide/stores/mutations/tree.js
@@ -6,6 +6,15 @@ export default {
opened: !tree.opened,
});
},
+ [types.CREATE_TREE](state, { treePath }) {
+ Object.assign(state, {
+ trees: Object.assign({}, state.trees, {
+ [treePath]: {
+ tree: [],
+ },
+ }),
+ });
+ },
[types.SET_DIRECTORY_DATA](state, { data, tree }) {
Object.assign(tree, {
tree: data,
diff --git a/app/assets/javascripts/repo/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index 0068834831e..539e382830f 100644
--- a/app/assets/javascripts/repo/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -1,10 +1,10 @@
export default () => ({
canCommit: false,
- currentBranch: '',
- currentBlobView: 'repo-preview',
- currentRef: '',
+ currentProjectId: '',
+ currentBranchId: '',
+ currentBlobView: 'repo-editor',
discardPopupOpen: false,
- editMode: false,
+ editMode: true,
endpoints: {},
isRoot: false,
isInitialRoot: false,
@@ -12,13 +12,11 @@ export default () => ({
loading: false,
onTopOfBranch: false,
openFiles: [],
+ selectedFile: null,
path: '',
- project: {
- id: 0,
- name: '',
- url: '',
- },
parentTreeUrl: '',
- previousUrl: '',
- tree: [],
+ trees: {},
+ projects: {},
+ leftPanelCollapsed: false,
+ rightPanelCollapsed: true,
});
diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index fae1f4439a9..29e3ab5d040 100644
--- a/app/assets/javascripts/repo/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -2,6 +2,8 @@ export const dataStructure = () => ({
id: '',
key: '',
type: '',
+ projectId: '',
+ branchId: '',
name: '',
url: '',
path: '',
@@ -15,9 +17,11 @@ export const dataStructure = () => ({
changed: false,
lastCommitPath: '',
lastCommit: {
+ id: '',
url: '',
message: '',
updatedAt: '',
+ author: '',
},
tree_url: '',
blamePath: '',
@@ -31,11 +35,17 @@ export const dataStructure = () => ({
parentTreeUrl: '',
renderError: false,
base64: false,
+ editorRow: 1,
+ editorColumn: 1,
+ fileLanguage: '',
+ eol: '',
});
export const decorateData = (entity) => {
const {
id,
+ projectId,
+ branchId,
type,
url,
name,
@@ -56,6 +66,8 @@ export const decorateData = (entity) => {
return {
...dataStructure(),
id,
+ projectId,
+ branchId,
key: `${name}-${type}-${id}`,
type,
name,
@@ -75,24 +87,51 @@ export const decorateData = (entity) => {
};
};
-export const findEntry = (state, type, name) => state.tree.find(
+/*
+ Takes the multi-dimensional tree and returns a flattened array.
+ This allows for the table to recursively render the table rows but keeps the data
+ structure nested to make it easier to add new files/directories.
+*/
+export const treeList = (state, treeId) => {
+ const baseTree = state.trees[treeId];
+ if (baseTree) {
+ const mapTree = arr => (!arr.tree || !arr.tree.length ?
+ [] : _.map(arr.tree, a => [a, mapTree(a)]));
+
+ return _.chain(baseTree.tree)
+ .map(arr => [arr, mapTree(arr)])
+ .flatten()
+ .value();
+ }
+ return [];
+};
+
+export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
+
+export const getTreeEntry = (store, treeId, path) => {
+ const fileList = treeList(store.state, treeId);
+ return fileList ? fileList.find(file => file.path === path) : null;
+};
+
+export const findEntry = (tree, type, name) => tree.find(
f => f.type === type && f.name === name,
);
+
export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
export const setPageTitle = (title) => {
document.title = title;
};
-export const pushState = (url) => {
- history.pushState({ url }, '', url);
-};
-
-export const createTemp = ({ name, path, type, level, changed, content, base64 }) => {
+export const createTemp = ({
+ projectId, branchId, name, path, type, level, changed, content, base64, url,
+}) => {
const treePath = path ? `${path}/${name}` : name;
return decorateData({
id: new Date().getTime().toString(),
+ projectId,
+ branchId,
name,
type,
tempFile: true,
@@ -104,11 +143,18 @@ export const createTemp = ({ name, path, type, level, changed, content, base64 }
level,
base64,
renderError: base64,
+ url,
});
};
-export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level }) => {
- const found = findEntry(tree, type, entry.name);
+export const createOrMergeEntry = ({ tree,
+ projectId,
+ branchId,
+ entry,
+ type,
+ parentTreeUrl,
+ level }) => {
+ const found = findEntry(tree.tree || tree, type, entry.name);
if (found) {
return Object.assign({}, found, {
@@ -120,6 +166,8 @@ export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level })
return decorateData({
...entry,
+ projectId,
+ branchId,
type,
parentTreeUrl,
level,
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 198b5164c92..1fa6715180e 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -2,7 +2,7 @@ import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
import {
- lang,
+ languageCode,
s__,
} from '../../locale';
@@ -24,7 +24,15 @@ export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', '
*/
export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+/**
+ * Timeago uses underscores instead of dashes to separate language from country code.
+ *
+ * see https://github.com/hustcc/timeago.js/tree/v3.0.0/locales
+ */
+const timeagoLanguageCode = languageCode().replace(/-/g, '_');
+
let timeagoInstance;
+
/**
* Sets a timeago Instance
*/
@@ -67,8 +75,8 @@ export function getTimeago() {
][index];
};
- timeago.register(lang, locale);
- timeago.register(`${lang}-remaining`, localeRemaining);
+ timeago.register(timeagoLanguageCode, locale);
+ timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
@@ -83,7 +91,7 @@ export const renderTimeago = ($els) => {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
// timeago.js sets timeouts internally for each timeago value to be updated in real time
- getTimeago().render(timeagoEls, lang);
+ getTimeago().render(timeagoEls, timeagoLanguageCode);
};
/**
@@ -118,7 +126,7 @@ export const timeFor = (time, expiredLabel) => {
if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due');
}
- return getTimeago().format(time, `${lang}-remaining`).trim();
+ return getTimeago().format(time, `${timeagoLanguageCode}-remaining`).trim();
};
export const getDayDifference = (a, b) => {
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 6e152497d20..a2f0a44863f 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -6,11 +6,12 @@ export default class NewCommitForm {
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
- this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
+ this.createMergeRequestContainer = form.find(
+ '.js-create-merge-request-container',
+ );
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
-
renderDestination() {
var different;
different = this.branchName.val() !== this.originalBranch.val();
@@ -23,6 +24,6 @@ export default class NewCommitForm {
this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
}
- return this.wasDifferent = different;
+ return (this.wasDifferent = different);
}
}
diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js
new file mode 100644
index 00000000000..f18f98b4e9a
--- /dev/null
+++ b/app/assets/javascripts/pages/users/show/index.js
@@ -0,0 +1,3 @@
+import UserCallout from '~/user_callout';
+
+export default () => new UserCallout();
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index 751a20991af..831aa92ac4f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,5 +1,6 @@
<script>
import tooltip from '../../vue_shared/directives/tooltip';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -11,6 +12,9 @@
directives: {
tooltip,
},
+ components: {
+ icon,
+ },
};
</script>
<template>
@@ -24,10 +28,9 @@
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts">
- <i
- class="fa fa-download"
- aria-hidden="true">
- </i>
+ <icon
+ name="download">
+ </icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 141333b2b4d..ffaafb3ee9e 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -117,12 +117,10 @@
}());
markdownPreview = new window.MarkdownPreview();
-
previewButtonSelector = '.js-md-preview-button';
-
writeButtonSelector = '.js-md-write-button';
-
lastTextareaPreviewed = null;
+ const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function () {
var $form = $(this);
@@ -146,6 +144,7 @@
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
+ markdownToolbar.removeClass('active');
markdownPreview.showPreview($form);
});
@@ -167,6 +166,7 @@
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
$form.find('.md-preview-holder').hide();
+ markdownToolbar.addClass('active');
markdownPreview.hideReferencedCommands($form);
});
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list.vue b/app/assets/javascripts/repo/components/commit_sidebar/list.vue
deleted file mode 100644
index fb862e7bf01..00000000000
--- a/app/assets/javascripts/repo/components/commit_sidebar/list.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script>
- import icon from '../../../vue_shared/components/icon.vue';
- import listItem from './list_item.vue';
- import listCollapsed from './list_collapsed.vue';
-
- export default {
- components: {
- icon,
- listItem,
- listCollapsed,
- },
- props: {
- title: {
- type: String,
- required: true,
- },
- fileList: {
- type: Array,
- required: true,
- },
- collapsed: {
- type: Boolean,
- required: true,
- },
- },
- methods: {
- toggleCollapsed() {
- this.$emit('toggleCollapsed');
- },
- },
- };
-</script>
-
-<template>
- <div class="multi-file-commit-panel-section">
- <header
- class="multi-file-commit-panel-header"
- :class="{
- 'is-collapsed': collapsed,
- }"
- >
- <icon
- name="list-bulleted"
- :size="18"
- css-classes="append-right-default"
- />
- <template v-if="!collapsed">
- {{ title }}
- <button
- type="button"
- class="btn btn-transparent multi-file-commit-panel-collapse-btn"
- @click="toggleCollapsed"
- >
- <i
- aria-hidden="true"
- class="fa fa-angle-double-right"
- >
- </i>
- </button>
- </template>
- </header>
- <div class="multi-file-commit-list">
- <list-collapsed
- v-if="collapsed"
- />
- <template v-else>
- <ul
- v-if="fileList.length"
- class="list-unstyled append-bottom-0"
- >
- <li
- v-for="file in fileList"
- :key="file.key"
- >
- <list-item
- :file="file"
- />
- </li>
- </ul>
- <div
- v-else
- class="help-block prepend-top-0"
- >
- No changes
- </div>
- </template>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/new_dropdown/index.vue b/app/assets/javascripts/repo/components/new_dropdown/index.vue
deleted file mode 100644
index 781404cf8ca..00000000000
--- a/app/assets/javascripts/repo/components/new_dropdown/index.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import newModal from './modal.vue';
- import upload from './upload.vue';
- import icon from '../../../vue_shared/components/icon.vue';
-
- export default {
- components: {
- icon,
- newModal,
- upload,
- },
- data() {
- return {
- openModal: false,
- modalType: '',
- };
- },
- computed: {
- ...mapState([
- 'path',
- ]),
- },
- methods: {
- createNewItem(type) {
- this.modalType = type;
- this.toggleModalOpen();
- },
- toggleModalOpen() {
- this.openModal = !this.openModal;
- },
- },
- };
-</script>
-
-<template>
- <div>
- <ul class="breadcrumb repo-breadcrumb">
- <li class="dropdown">
- <button
- type="button"
- class="btn btn-default dropdown-toggle add-to-tree"
- data-toggle="dropdown"
- aria-label="Create new file or directory"
- >
- <icon
- name="plus"
- css-classes="pull-left"
- />
- <icon
- name="arrow-down"
- css-classes="pull-left"
- />
- </button>
- <ul class="dropdown-menu">
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('blob')"
- >
- {{ __('New file') }}
- </a>
- </li>
- <li>
- <upload
- :path="path"
- />
- </li>
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('tree')"
- >
- {{ __('New directory') }}
- </a>
- </li>
- </ul>
- </li>
- </ul>
- <new-modal
- v-if="openModal"
- :type="modalType"
- :path="path"
- @toggle="toggleModalOpen"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
deleted file mode 100644
index a00e1e9d809..00000000000
--- a/app/assets/javascripts/repo/components/repo.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-<script>
-import { mapState, mapGetters } from 'vuex';
-import RepoSidebar from './repo_sidebar.vue';
-import RepoCommitSection from './repo_commit_section.vue';
-import RepoTabs from './repo_tabs.vue';
-import RepoFileButtons from './repo_file_buttons.vue';
-import RepoPreview from './repo_preview.vue';
-import repoEditor from './repo_editor.vue';
-
-export default {
- computed: {
- ...mapState([
- 'currentBlobView',
- ]),
- ...mapGetters([
- 'isCollapsed',
- 'changedFiles',
- ]),
- },
- components: {
- RepoSidebar,
- RepoTabs,
- RepoFileButtons,
- repoEditor,
- RepoCommitSection,
- RepoPreview,
- },
- mounted() {
- const returnValue = 'Are you sure you want to lose unsaved changes?';
- window.onbeforeunload = (e) => {
- if (!this.changedFiles.length) return undefined;
-
- Object.assign(e, {
- returnValue,
- });
- return returnValue;
- };
- },
-};
-</script>
-
-<template>
- <div
- class="multi-file"
- :class="{
- 'is-collapsed': isCollapsed
- }"
- >
- <repo-sidebar/>
- <div
- v-if="isCollapsed"
- class="multi-file-edit-pane"
- >
- <repo-tabs />
- <component
- class="multi-file-edit-pane-content"
- :is="currentBlobView"
- />
- <repo-file-buttons />
- </div>
- <repo-commit-section />
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
deleted file mode 100644
index 4ea21913129..00000000000
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script>
-import { mapState, mapGetters, mapActions } from 'vuex';
-import RepoPreviousDirectory from './repo_prev_directory.vue';
-import RepoFile from './repo_file.vue';
-import RepoLoadingFile from './repo_loading_file.vue';
-
-export default {
- components: {
- 'repo-previous-directory': RepoPreviousDirectory,
- 'repo-file': RepoFile,
- 'repo-loading-file': RepoLoadingFile,
- },
- created() {
- window.addEventListener('popstate', this.popHistoryState);
- },
- destroyed() {
- window.removeEventListener('popstate', this.popHistoryState);
- },
- mounted() {
- this.getTreeData();
- },
- computed: {
- ...mapState([
- 'loading',
- 'isRoot',
- ]),
- ...mapState({
- projectName(state) {
- return state.project.name;
- },
- }),
- ...mapGetters([
- 'treeList',
- 'isCollapsed',
- ]),
- },
- methods: {
- ...mapActions([
- 'getTreeData',
- 'popHistoryState',
- ]),
- },
-};
-</script>
-
-<template>
-<div class="ide-file-list">
- <table class="table">
- <thead>
- <tr>
- <th
- v-if="isCollapsed"
- >
- </th>
- <template v-else>
- <th class="name multi-file-table-name">
- Name
- </th>
- <th class="hidden-sm hidden-xs last-commit">
- Last commit
- </th>
- <th class="hidden-xs last-update text-right">
- Last update
- </th>
- </template>
- </tr>
- </thead>
- <tbody>
- <repo-previous-directory
- v-if="!isRoot && treeList.length"
- />
- <repo-loading-file
- v-if="!treeList.length && loading"
- v-for="n in 5"
- :key="n"
- />
- <repo-file
- v-for="file in treeList"
- :key="file.key"
- :file="file"
- />
- </tbody>
- </table>
-</div>
-</template>
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
deleted file mode 100644
index b6801af7fcb..00000000000
--- a/app/assets/javascripts/repo/index.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import Vue from 'vue';
-import { mapActions } from 'vuex';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
-import Repo from './components/repo.vue';
-import RepoEditButton from './components/repo_edit_button.vue';
-import newBranchForm from './components/new_branch_form.vue';
-import newDropdown from './components/new_dropdown/index.vue';
-import store from './stores';
-import Translate from '../vue_shared/translate';
-
-function initRepo(el) {
- if (!el) return null;
-
- return new Vue({
- el,
- store,
- components: {
- repo: Repo,
- },
- methods: {
- ...mapActions([
- 'setInitialData',
- ]),
- },
- created() {
- const data = el.dataset;
-
- this.setInitialData({
- project: {
- id: data.projectId,
- name: data.projectName,
- url: data.projectUrl,
- },
- endpoints: {
- rootEndpoint: data.url,
- newMergeRequestUrl: data.newMergeRequestUrl,
- rootUrl: data.rootUrl,
- },
- canCommit: convertPermissionToBoolean(data.canCommit),
- onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch),
- currentRef: data.ref,
- path: data.currentPath,
- currentBranch: data.currentBranch,
- isRoot: convertPermissionToBoolean(data.root),
- isInitialRoot: convertPermissionToBoolean(data.root),
- });
- },
- render(createElement) {
- return createElement('repo');
- },
- });
-}
-
-function initRepoEditButton(el) {
- return new Vue({
- el,
- store,
- components: {
- repoEditButton: RepoEditButton,
- },
- render(createElement) {
- return createElement('repo-edit-button');
- },
- });
-}
-
-function initNewDropdown(el) {
- return new Vue({
- el,
- store,
- components: {
- newDropdown,
- },
- render(createElement) {
- return createElement('new-dropdown');
- },
- });
-}
-
-function initNewBranchForm() {
- const el = document.querySelector('.js-new-branch-dropdown');
-
- if (!el) return null;
-
- return new Vue({
- el,
- components: {
- newBranchForm,
- },
- store,
- render(createElement) {
- return createElement('new-branch-form');
- },
- });
-}
-
-const repo = document.getElementById('repo');
-const editButton = document.querySelector('.editable-mode');
-const newDropdownHolder = document.querySelector('.js-new-dropdown');
-
-Vue.use(Translate);
-
-initRepo(repo);
-initRepoEditButton(editButton);
-initNewBranchForm();
-initNewDropdown(newDropdownHolder);
diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js
deleted file mode 100644
index af5dcf054ef..00000000000
--- a/app/assets/javascripts/repo/stores/actions.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import Vue from 'vue';
-import { visitUrl } from '../../lib/utils/url_utility';
-import flash from '../../flash';
-import service from '../services';
-import * as types from './mutation_types';
-
-export const redirectToUrl = (_, url) => visitUrl(url);
-
-export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
-
-export const closeDiscardPopup = ({ commit }) => commit(types.TOGGLE_DISCARD_POPUP, false);
-
-export const discardAllChanges = ({ commit, getters, dispatch }) => {
- const changedFiles = getters.changedFiles;
-
- changedFiles.forEach((file) => {
- commit(types.DISCARD_FILE_CHANGES, file);
-
- if (file.tempFile) {
- dispatch('closeFile', { file, force: true });
- }
- });
-};
-
-export const closeAllFiles = ({ state, dispatch }) => {
- state.openFiles.forEach(file => dispatch('closeFile', { file }));
-};
-
-export const toggleEditMode = ({ state, commit, getters, dispatch }, force = false) => {
- const changedFiles = getters.changedFiles;
-
- if (changedFiles.length && !force) {
- commit(types.TOGGLE_DISCARD_POPUP, true);
- } else {
- commit(types.TOGGLE_EDIT_MODE);
- commit(types.TOGGLE_DISCARD_POPUP, false);
- dispatch('toggleBlobView');
-
- if (!state.editMode) {
- dispatch('discardAllChanges');
- }
- }
-};
-
-export const toggleBlobView = ({ commit, state }) => {
- if (state.editMode) {
- commit(types.SET_EDIT_MODE);
- } else {
- commit(types.SET_PREVIEW_MODE);
- }
-};
-
-export const checkCommitStatus = ({ state }) => service.getBranchData(
- state.project.id,
- state.currentBranch,
-)
- .then((data) => {
- const { id } = data.commit;
-
- if (state.currentRef !== id) {
- return true;
- }
-
- return false;
- })
- .catch(() => flash('Error checking branch data. Please try again.'));
-
-export const commitChanges = ({ commit, state, dispatch, getters }, { payload, newMr }) =>
- service.commit(state.project.id, payload)
- .then((data) => {
- const { branch } = payload;
- if (!data.short_id) {
- flash(data.message);
- return;
- }
-
- const lastCommit = {
- commit_path: `${state.project.url}/commit/${data.id}`,
- commit: {
- message: data.message,
- authored_date: data.committed_date,
- },
- };
-
- flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
-
- if (newMr) {
- dispatch('redirectToUrl', `${state.endpoints.newMergeRequestUrl}${branch}`);
- } else {
- commit(types.SET_COMMIT_REF, data.id);
-
- getters.changedFiles.forEach((entry) => {
- commit(types.SET_LAST_COMMIT_DATA, {
- entry,
- lastCommit,
- });
- });
-
- dispatch('discardAllChanges');
- dispatch('closeAllFiles');
- dispatch('toggleEditMode');
-
- window.scrollTo(0, 0);
- }
- })
- .catch(() => flash('Error committing changes. Please try again.'));
-
-export const createTempEntry = ({ state, dispatch }, { name, type, content = '', base64 = false }) => {
- if (type === 'tree') {
- dispatch('createTempTree', name);
- } else if (type === 'blob') {
- dispatch('createTempFile', {
- tree: state,
- name,
- base64,
- content,
- });
- }
-};
-
-export const popHistoryState = ({ state, dispatch, getters }) => {
- const treeList = getters.treeList;
- const tree = treeList.find(file => file.url === state.previousUrl);
-
- if (!tree) return;
-
- if (tree.type === 'tree') {
- dispatch('toggleTreeOpen', { endpoint: tree.url, tree });
- }
-};
-
-export const scrollToTab = () => {
- Vue.nextTick(() => {
- const tabs = document.getElementById('tabs');
-
- if (tabs) {
- const tabEl = tabs.querySelector('.active .repo-tab');
-
- tabEl.focus();
- }
- });
-};
-
-export * from './actions/tree';
-export * from './actions/file';
-export * from './actions/branch';
diff --git a/app/assets/javascripts/repo/stores/actions/branch.js b/app/assets/javascripts/repo/stores/actions/branch.js
deleted file mode 100644
index 61d9a5af3e3..00000000000
--- a/app/assets/javascripts/repo/stores/actions/branch.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import service from '../../services';
-import * as types from '../mutation_types';
-import { pushState } from '../utils';
-
-// eslint-disable-next-line import/prefer-default-export
-export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
- state.project.id,
- {
- branch,
- ref: state.currentBranch,
- },
-).then(res => res.json())
-.then((data) => {
- const branchName = data.name;
- const url = location.href.replace(state.currentBranch, branchName);
-
- pushState(url);
-
- commit(types.SET_CURRENT_BRANCH, branchName);
-});
diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js
deleted file mode 100644
index 7c251e26bed..00000000000
--- a/app/assets/javascripts/repo/stores/actions/tree.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import { visitUrl } from '../../../lib/utils/url_utility';
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
-import service from '../../services';
-import * as types from '../mutation_types';
-import {
- pushState,
- setPageTitle,
- findEntry,
- createTemp,
- createOrMergeEntry,
-} from '../utils';
-
-export const getTreeData = (
- { commit, state, dispatch },
- { endpoint = state.endpoints.rootEndpoint, tree = state } = {},
-) => {
- commit(types.TOGGLE_LOADING, tree);
-
- service.getTreeData(endpoint)
- .then((res) => {
- const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
- setPageTitle(pageTitle);
-
- return res.json();
- })
- .then((data) => {
- const prevLastCommitPath = tree.lastCommitPath;
- if (!state.isInitialRoot) {
- commit(types.SET_ROOT, data.path === '/');
- }
-
- dispatch('updateDirectoryData', { data, tree });
- commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
- commit(types.SET_LAST_COMMIT_URL, { tree, url: data.last_commit_path });
- commit(types.TOGGLE_LOADING, tree);
-
- if (prevLastCommitPath !== null) {
- dispatch('getLastCommitData', tree);
- }
-
- pushState(endpoint);
- })
- .catch(() => {
- flash('Error loading tree data. Please try again.');
- commit(types.TOGGLE_LOADING, tree);
- });
-};
-
-export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
- if (tree.opened) {
- // send empty data to clear the tree
- const data = { trees: [], blobs: [], submodules: [] };
-
- pushState(tree.parentTreeUrl);
-
- commit(types.SET_PREVIOUS_URL, tree.parentTreeUrl);
- dispatch('updateDirectoryData', { data, tree });
- } else {
- commit(types.SET_PREVIOUS_URL, endpoint);
- dispatch('getTreeData', { endpoint, tree });
- }
-
- commit(types.TOGGLE_TREE_OPEN, tree);
-};
-
-export const clickedTreeRow = ({ commit, dispatch }, row) => {
- if (row.type === 'tree') {
- dispatch('toggleTreeOpen', {
- endpoint: row.url,
- tree: row,
- });
- } else if (row.type === 'submodule') {
- commit(types.TOGGLE_LOADING, row);
-
- visitUrl(row.url);
- } else if (row.type === 'blob' && row.opened) {
- dispatch('setFileActive', row);
- } else {
- dispatch('getFileData', row);
- }
-};
-
-export const createTempTree = ({ state, commit, dispatch }, name) => {
- let tree = state;
- const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
-
- dirNames.forEach((dirName) => {
- const foundEntry = findEntry(tree, 'tree', dirName);
-
- if (!foundEntry) {
- const tmpEntry = createTemp({
- name: dirName,
- path: tree.path,
- type: 'tree',
- level: tree.level !== undefined ? tree.level + 1 : 0,
- });
-
- commit(types.CREATE_TMP_TREE, {
- parent: tree,
- tmpEntry,
- });
- commit(types.TOGGLE_TREE_OPEN, tmpEntry);
-
- tree = tmpEntry;
- } else {
- tree = foundEntry;
- }
- });
-
- if (tree.tempFile) {
- dispatch('createTempFile', {
- tree,
- name: '.gitkeep',
- });
- }
-};
-
-export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
- if (tree.lastCommitPath === null || getters.isCollapsed) return;
-
- service.getTreeLastCommit(tree.lastCommitPath)
- .then((res) => {
- const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
-
- commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
-
- return res.json();
- })
- .then((data) => {
- data.forEach((lastCommit) => {
- const entry = findEntry(tree, lastCommit.type, lastCommit.file_name);
-
- if (entry) {
- commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
- }
- });
-
- dispatch('getLastCommitData', tree);
- })
- .catch(() => flash('Error fetching log data.'));
-};
-
-export const updateDirectoryData = ({ commit, state }, { data, tree }) => {
- const level = tree.level !== undefined ? tree.level + 1 : 0;
- const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
- const createEntry = (entry, type) => createOrMergeEntry({
- tree,
- entry,
- level,
- type,
- parentTreeUrl,
- });
-
- const formattedData = [
- ...data.trees.map(t => createEntry(t, 'tree')),
- ...data.submodules.map(m => createEntry(m, 'submodule')),
- ...data.blobs.map(b => createEntry(b, 'blob')),
- ];
-
- commit(types.SET_DIRECTORY_DATA, { tree, data: formattedData });
-};
diff --git a/app/assets/javascripts/repo/stores/getters.js b/app/assets/javascripts/repo/stores/getters.js
deleted file mode 100644
index 5ce9f449905..00000000000
--- a/app/assets/javascripts/repo/stores/getters.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import _ from 'underscore';
-
-/*
- Takes the multi-dimensional tree and returns a flattened array.
- This allows for the table to recursively render the table rows but keeps the data
- structure nested to make it easier to add new files/directories.
-*/
-export const treeList = (state) => {
- const mapTree = arr => (!arr.tree.length ? [] : _.map(arr.tree, a => [a, mapTree(a)]));
-
- return _.chain(state.tree)
- .map(arr => [arr, mapTree(arr)])
- .flatten()
- .value();
-};
-
-export const changedFiles = state => state.openFiles.filter(file => file.changed);
-
-export const activeFile = state => state.openFiles.find(file => file.active);
-
-export const activeFileExtension = (state) => {
- const file = activeFile(state);
- return file ? `.${file.path.split('.').pop()}` : '';
-};
-
-export const isCollapsed = state => !!state.openFiles.length;
-
-export const canEditFile = (state) => {
- const currentActiveFile = activeFile(state);
- const openedFiles = state.openFiles;
-
- return state.canCommit &&
- state.onTopOfBranch &&
- openedFiles.length &&
- (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
-};
-
-export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
-
-export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
diff --git a/app/assets/javascripts/repo/stores/mutations/branch.js b/app/assets/javascripts/repo/stores/mutations/branch.js
deleted file mode 100644
index d8229e8a620..00000000000
--- a/app/assets/javascripts/repo/stores/mutations/branch.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as types from '../mutation_types';
-
-export default {
- [types.SET_CURRENT_BRANCH](state, currentBranch) {
- Object.assign(state, {
- currentBranch,
- });
- },
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index 13e4cb5717e..85bfd03a3cf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -1,5 +1,6 @@
import tooltip from '../../vue_shared/directives/tooltip';
import { pluralize } from '../../lib/utils/text_utility';
+import icon from '../../vue_shared/components/icon.vue';
export default {
name: 'MRWidgetHeader',
@@ -9,6 +10,9 @@ export default {
directives: {
tooltip,
},
+ components: {
+ icon,
+ },
computed: {
shouldShowCommitsBehindText() {
return this.mr.divergedCommitsCount > 0;
@@ -81,10 +85,9 @@ export default {
data-toggle="dropdown"
aria-label="Download as"
role="button">
- <i
- class="fa fa-download"
- aria-hidden="true">
- </i>
+ <icon
+ name="download">
+ </icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 52814de8b2d..59ca9a0a6d4 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -2,13 +2,14 @@
import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip';
+ import Icon from '../../vue_shared/components/icon.vue';
export default {
props: {
/**
* Indicates the existance of a tag.
* Used to render the correct icon, if true will render `fa-tag` icon,
- * if false will render `fa-code-fork` icon.
+ * if false will render a svg sprite fork icon
*/
tag: {
type: Boolean,
@@ -107,6 +108,7 @@
},
components: {
userAvatarLink,
+ Icon,
},
created() {
this.commitIconSvg = commitIconSvg;
@@ -123,11 +125,10 @@
class="fa fa-tag"
aria-hidden="true">
</i>
- <i
+ <icon
v-if="!tag"
- class="fa fa-code-fork"
- aria-hidden="true">
- </i>
+ name="fork">
+ </icon>
</div>
<a
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 6c575d8eb49..36d2d1dc164 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -72,7 +72,9 @@
Preview
</a>
</li>
- <li class="md-header-toolbar">
+ <li
+ class="md-header-toolbar"
+ :class="{ active: !previewMarkdown }">
<toolbar-button
tag="**"
button-title="Add bold text"
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
new file mode 100644
index 00000000000..dce23bd65f6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -0,0 +1,103 @@
+<script>
+
+/* This is a re-usable vue component for rendering a project avatar that
+ does not need to link to the project's profile. The image and an optional
+ tooltip can be configured by props passed to this component.
+
+ Sample configuration:
+
+ <project-avatar-image
+ :lazy="true"
+ :img-src="projectAvatarSrc"
+ :img-alt="tooltipText"
+ :tooltip-text="tooltipText"
+ tooltip-placement="top"
+ />
+
+*/
+
+import defaultAvatarUrl from 'images/no_avatar.png';
+import { placeholderImage } from '../../../lazy_loader';
+import tooltip from '../../directives/tooltip';
+
+export default {
+ name: 'ProjectAvatarImage',
+ props: {
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ imgSrc: {
+ type: String,
+ required: false,
+ default: defaultAvatarUrl,
+ },
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ imgAlt: {
+ type: String,
+ required: false,
+ default: 'project avatar',
+ },
+ size: {
+ type: Number,
+ required: false,
+ default: 20,
+ },
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ },
+ directives: {
+ tooltip,
+ },
+ computed: {
+ // API response sends null when gravatar is disabled and
+ // we provide an empty string when we use it inside project avatar link.
+ // In both cases we should render the defaultAvatarUrl
+ sanitizedSource() {
+ return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ },
+ resultantSrcAttribute() {
+ return this.lazy ? placeholderImage : this.sanitizedSource;
+ },
+ tooltipContainer() {
+ return this.tooltipText ? 'body' : null;
+ },
+ avatarSizeClass() {
+ return `s${this.size}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <img
+ v-tooltip
+ class="avatar"
+ :class="{
+ lazy,
+ [avatarSizeClass]: true,
+ [cssClasses]: true
+ }"
+ :src="resultantSrcAttribute"
+ :width="size"
+ :height="size"
+ :alt="imgAlt"
+ :data-src="sanitizedSource"
+ :data-container="tooltipContainer"
+ :data-placement="tooltipPlacement"
+ :title="tooltipText"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 710452bb3d3..33096b53cf8 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -122,11 +122,17 @@ export default {
return items;
},
+ showPagination() {
+ return this.pageInfo.totalPages > 1;
+ },
},
};
</script>
<template>
- <div class="gl-pagination">
+ <div
+ v-if="showPagination"
+ class="gl-pagination"
+ >
<ul class="pagination clearfix">
<li
v-for="item in getItems"
diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss
index 2e417315ed7..5da06b90113 100644
--- a/app/assets/stylesheets/framework/contextual-sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual-sidebar.scss
@@ -23,7 +23,6 @@
.context-header {
position: relative;
margin-right: 2px;
- width: $contextual-sidebar-width;
a {
transition: padding $sidebar-transition-duration;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 5389eb0a5f2..6b07ffdbd61 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -74,7 +74,7 @@
}
.md-header-tab {
- @media(max-width: $screen-xs-max) {
+ @media (max-width: $screen-xs-max) {
flex: 1;
width: 100%;
border-bottom: 1px solid $border-color;
@@ -82,16 +82,23 @@
}
}
-.md-header-toolbar {
- margin-left: auto;
+.nav-links {
+ li.md-header-toolbar {
+ margin-left: auto;
+ display: none;
- @media(max-width: $screen-xs-max) {
- flex: none;
- display: flex;
- justify-content: center;
- width: 100%;
- padding-top: $gl-padding-top;
- padding-bottom: $gl-padding-top;
+ &.active {
+ display: block;
+
+ @media (max-width: $screen-xs-max) {
+ flex: none;
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ padding-top: $gl-padding-top;
+ padding-bottom: $gl-padding-top;
+ }
+ }
}
}
@@ -175,7 +182,7 @@
margin-left: $gl-padding;
margin-right: -5px;
- @media(max-width: $screen-xs-max) {
+ @media (max-width: $screen-xs-max) {
margin-left: 0;
margin-right: 0;
}
@@ -239,7 +246,7 @@
}
}
-@media(max-width: $screen-xs-max) {
+@media (max-width: $screen-xs-max) {
.atwho-view-ul {
width: 350px;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b84d6c140be..1d6c7a5c472 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -219,6 +219,7 @@ $gl-input-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
+$gl-bar-padding: 3px;
/*
* Misc
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 735fc4babd7..2f3a80daa90 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -16,6 +16,10 @@
.commit-sha,
.commit-info {
margin-left: 4px;
+
+ .fork-svg {
+ margin-right: 4px;
+ }
}
.ref-name {
@@ -79,7 +83,7 @@
}
.limit-icon {
- margin: 0 8px;
+ margin: 0 4px;
}
.limit-message {
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 88d44131d5b..7b8ee026357 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -6,7 +6,7 @@
.cluster-applications-table {
// Wait for the Vue to kick-in and render the applications block
- min-height: 302px;
+ min-height: 400px;
}
.clusters-dropdown-menu {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index e75a35d78ad..f887a11004f 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -723,3 +723,7 @@
font-size: 16px;
}
}
+
+.fork-sprite {
+ margin-right: -5px;
+}
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 7e2297c283f..b698a4f9afa 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -39,6 +39,10 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+
+ svg {
+ vertical-align: middle;
+ }
}
.next-run-cell {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 9805fc4f882..05c1033c5f7 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -143,6 +143,12 @@
fill: $gl-text-color-secondary;
}
+ .sprite {
+ width: 12px;
+ height: 12px;
+ fill: $gl-text-color;
+ }
+
.fa {
font-size: 12px;
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 6eb92c7baee..da3c2d7fa5d 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -22,9 +22,10 @@
}
}
-.multi-file {
+.ide-view {
display: flex;
- height: calc(100vh - 145px);
+ height: calc(100vh - #{$header-height});
+ color: $almost-black;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
@@ -35,12 +36,47 @@
}
}
+.with-performance-bar .ide-view {
+ height: calc(100vh - #{$header-height});
+}
+
.ide-file-list {
flex: 1;
- overflow: scroll;
.file {
cursor: pointer;
+
+ &.file-open {
+ background: $white-normal;
+ }
+
+ .repo-file-name {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .unsaved-icon {
+ color: $indigo-700;
+ float: right;
+ font-size: smaller;
+ line-height: 20px;
+ }
+
+ .repo-new-btn {
+ display: none;
+ margin-top: -4px;
+ margin-bottom: -4px;
+ }
+
+ &:hover {
+ .repo-new-btn {
+ display: block;
+ }
+
+ .unsaved-icon {
+ display: none;
+ }
+ }
}
a {
@@ -55,10 +91,9 @@
.multi-file-table-name,
.multi-file-table-col-commit-message {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ overflow: visible;
max-width: 0;
+ padding: 6px 12px;
}
.multi-file-table-name {
@@ -66,6 +101,7 @@
}
.multi-file-table-col-commit-message {
+ white-space: nowrap;
width: 50%;
}
@@ -79,7 +115,7 @@
.multi-file-tabs {
display: flex;
- overflow: scroll;
+ overflow-x: auto;
background-color: $white-normal;
box-shadow: inset 0 -1px $white-dark;
@@ -128,9 +164,38 @@
height: 0;
}
+.blob-editor-container {
+ flex: 1;
+ height: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ .vertical-center {
+ min-height: auto;
+ }
+}
+
+.multi-file-editor-holder {
+ height: 100%;
+}
+
.multi-file-editor-btn-group {
- padding: $grid-size;
+ padding: $gl-bar-padding $gl-padding;
border-top: 1px solid $white-dark;
+ border-bottom: 1px solid $white-dark;
+ background: $white-light;
+}
+
+.ide-status-bar {
+ padding: $gl-bar-padding $gl-padding;
+ background: $white-light;
+ display: flex;
+ justify-content: space-between;
+
+ svg {
+ vertical-align: middle;
+ }
}
// Not great, but this is to deal with our current output
@@ -138,10 +203,6 @@
height: 100%;
overflow: scroll;
- .blob-viewer {
- height: 100%;
- }
-
.file-content.code {
display: flex;
@@ -162,18 +223,101 @@
}
}
+.file-content.blob-no-preview {
+ a {
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
.multi-file-commit-panel {
display: flex;
flex-direction: column;
height: 100%;
width: 290px;
- padding: $gl-padding;
+ padding: 0;
background-color: $gray-light;
border-left: 1px solid $white-dark;
+ .projects-sidebar {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .multi-file-commit-panel-inner {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ }
+
+ .multi-file-commit-panel-inner-scroll {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ overflow: auto;
+ }
+
&.is-collapsed {
width: 60px;
- padding: 0;
+
+ .multi-file-commit-list {
+ padding-top: $gl-padding;
+ overflow: hidden;
+ }
+
+ .multi-file-context-bar-icon {
+ align-items: center;
+
+ svg {
+ float: none;
+ margin: 0;
+ }
+ }
+ }
+
+ .branch-container {
+ border-left: 4px solid $indigo-700;
+ margin-bottom: $gl-bar-padding;
+ }
+
+ .branch-header {
+ background: $white-dark;
+ display: flex;
+ }
+
+ .branch-header-title {
+ flex: 1;
+ padding: $grid-size $gl-padding;
+ color: $indigo-700;
+ font-weight: $gl-font-weight-bold;
+
+ svg {
+ vertical-align: middle;
+ }
+ }
+
+ .branch-header-btns {
+ padding: $gl-vert-padding $gl-padding;
+ }
+
+ .left-collapse-btn {
+ display: none;
+ background: $gray-light;
+ text-align: left;
+ border-top: 1px solid $white-dark;
+
+ svg {
+ vertical-align: middle;
+ }
+ }
+}
+
+.multi-file-context-bar-icon {
+ padding: 10px;
+
+ svg {
+ margin-right: 10px;
+ float: left;
}
}
@@ -186,9 +330,9 @@
.multi-file-commit-panel-header {
display: flex;
align-items: center;
- padding: 0 0 12px;
margin-bottom: 12px;
border-bottom: 1px solid $white-dark;
+ padding: $gl-btn-padding 0;
&.is-collapsed {
border-bottom: 1px solid $white-dark;
@@ -197,23 +341,33 @@
margin-left: auto;
margin-right: auto;
}
+
+ .multi-file-commit-panel-collapse-btn {
+ margin-right: auto;
+ margin-left: auto;
+ border-left: 0;
+ }
}
}
-.multi-file-commit-panel-collapse-btn {
- padding-top: 0;
- padding-bottom: 0;
- margin-left: auto;
- font-size: 20px;
+.multi-file-commit-panel-header-title {
+ display: flex;
+ flex: 1;
+ padding: $gl-btn-padding;
- &.is-collapsed {
- margin-right: auto;
+ svg {
+ margin-right: $gl-btn-padding;
}
}
+.multi-file-commit-panel-collapse-btn {
+ border-left: 1px solid $white-dark;
+}
+
.multi-file-commit-list {
flex: 1;
- overflow: scroll;
+ overflow: auto;
+ padding: $gl-padding;
}
.multi-file-commit-list-item {
@@ -244,7 +398,7 @@
}
.multi-file-commit-form {
- padding-top: 12px;
+ padding: $gl-padding;
border-top: 1px solid $white-dark;
}
@@ -295,3 +449,40 @@
}
}
}
+
+.ide-loading {
+ display: flex;
+ height: 100vh;
+ align-items: center;
+ justify-content: center;
+}
+
+.ide-empty-state {
+ display: flex;
+ height: 100vh;
+ align-items: center;
+ justify-content: center;
+}
+
+.repo-new-btn {
+ .dropdown-toggle svg {
+ margin-top: -2px;
+ margin-bottom: 2px;
+ }
+
+ .dropdown-menu {
+ left: auto;
+ right: 0;
+
+ label {
+ font-weight: $gl-font-weight-normal;
+ padding: 5px 8px;
+ margin-bottom: 0;
+ }
+ }
+}
+
+.ide-flash-container.flash-container {
+ margin-top: $header-height;
+ margin-bottom: 0;
+}
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
new file mode 100644
index 00000000000..1ff25a45398
--- /dev/null
+++ b/app/controllers/ide_controller.rb
@@ -0,0 +1,6 @@
+class IdeController < ApplicationController
+ layout 'nav_only'
+
+ def index
+ end
+end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 770381472c5..d838b8dc29e 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -5,9 +5,6 @@ class Projects::BlobController < Projects::ApplicationController
include RendersBlob
include ActionView::Helpers::SanitizeHelper
- # Raised when given an invalid file path
- InvalidPathError = Class.new(StandardError)
-
prepend_before_action :authenticate_user!, only: [:edit]
before_action :require_non_empty_project, except: [:new, :create]
@@ -61,7 +58,6 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
failure_path: project_blob_path(@project, @id))
-
rescue Files::UpdateService::FileChangedError
@conflict = true
render :edit
@@ -132,7 +128,6 @@ class Projects::BlobController < Projects::ApplicationController
def assign_blob_vars
@id = params[:id]
@ref, @path = extract_ref(@id)
-
rescue InvalidPathError
render_404
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 4754a67450f..d13407a06c8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -306,7 +306,7 @@ module ApplicationHelper
cookies["sidebar_collapsed"] == "true"
end
- def show_new_repo?
+ def show_new_ide?
cookies["new_repo"] == "true" && body_data_page != 'projects:show'
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 556ed233ccf..f9dcb32f7c4 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -8,7 +8,7 @@ module BlobHelper
%w(credits changelog news copying copyright license authors)
end
- def edit_path(project = @project, ref = @ref, path = @path, options = {})
+ def edit_blob_path(project = @project, ref = @ref, path = @path, options = {})
project_edit_blob_path(project,
tree_join(ref, path),
options[:link_opts])
@@ -26,10 +26,10 @@ module BlobHelper
button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
# This condition applies to anonymous or users who can edit directly
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
- link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
+ link_to 'Edit', edit_blob_path(project, ref, path, options), class: "#{common_classes} btn-sm"
elsif current_user && can?(current_user, :fork_project, project)
continue_params = {
- to: edit_path(project, ref, path, options),
+ to: edit_blob_path(project, ref, path, options),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
@@ -41,6 +41,43 @@ module BlobHelper
end
end
+ def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
+ "#{ide_path}/project#{edit_blob_path(project, ref, path, options)}"
+ end
+
+ def ide_edit_text
+ "#{_('Multi Edit')} <span class='label label-primary'>#{_('Beta')}</span>".html_safe
+ end
+
+ def ide_blob_link(project = @project, ref = @ref, path = @path, options = {})
+ return unless show_new_ide?
+
+ blob = options.delete(:blob)
+ blob ||= project.repository.blob_at(ref, path) rescue nil
+
+ return unless blob && blob.readable_text?
+
+ common_classes = "btn js-edit-ide #{options[:extra_class]}"
+
+ if !on_top_of_branch?(project, ref)
+ button_tag ide_edit_text, class: "#{common_classes} disabled has-tooltip", title: _('You can only edit files when you are on a branch'), data: { container: 'body' }
+ # This condition applies to anonymous or users who can edit directly
+ elsif current_user && can_modify_blob?(blob, project, ref)
+ link_to ide_edit_text, ide_edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
+ elsif current_user && can?(current_user, :fork_project, project)
+ continue_params = {
+ to: ide_edit_path(project, ref, path, options),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
+
+ button_tag ide_edit_text,
+ class: common_classes,
+ data: { fork_path: fork_path }
+ end
+ end
+
def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
return unless current_user
@@ -232,7 +269,7 @@ module BlobHelper
return if blob.empty?
if blob.raw_binary? || blob.stored_externally?
- icon = icon('download')
+ icon = sprite_icon('download')
title = 'Download'
else
icon = icon('file-code-o')
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 2d304f7eb91..0333c29e2fd 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link
def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do
- icon('code-fork', class: 'append-right-5') + "#{text}"
+ sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}"
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index b05eb93b465..36a311dfa8a 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -43,14 +43,20 @@ module SortingHelper
end
def groups_sort_options_hash
- options = {
+ {
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated
}
+ end
- options
+ def admin_groups_sort_options_hash
+ groups_sort_options_hash.merge(
+ sort_value_largest_group => sort_title_largest_group
+ )
end
def member_sort_options_hash
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index c7949d11ef8..193bb48e54d 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -3,32 +3,19 @@ module Clusters
class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm'
+ include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
- belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
-
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
- validates :cluster, presence: true
-
- after_initialize :set_initial_status
-
- def self.application_name
- self.to_s.demodulize.underscore
- end
-
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.platform_kubernetes_active?
end
- def name
- self.class.application_name
- end
-
def install_command
- Gitlab::Kubernetes::Helm::InstallCommand.new(name, true)
+ Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
end
end
end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 44bd979741e..9024f1df1cd 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -3,41 +3,22 @@ module Clusters
class Ingress < ActiveRecord::Base
self.table_name = 'clusters_applications_ingress'
+ include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
- belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
-
- validates :cluster, presence: true
-
default_value_for :ingress_type, :nginx
default_value_for :version, :nginx
- after_initialize :set_initial_status
-
enum ingress_type: {
nginx: 1
}
- def self.application_name
- self.to_s.demodulize.underscore
- end
-
- def set_initial_status
- return unless not_installable?
-
- self.status = 'installable' if cluster&.application_helm_installed?
- end
-
- def name
- self.class.application_name
- end
-
def chart
'stable/nginx-ingress'
end
def install_command
- Gitlab::Kubernetes::Helm::InstallCommand.new(name, false, chart)
+ Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
end
end
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
new file mode 100644
index 00000000000..9b0787ee6ca
--- /dev/null
+++ b/app/models/clusters/applications/prometheus.rb
@@ -0,0 +1,26 @@
+module Clusters
+ module Applications
+ class Prometheus < ActiveRecord::Base
+ VERSION = "2.0.0".freeze
+
+ self.table_name = 'clusters_applications_prometheus'
+
+ include ::Clusters::Concerns::ApplicationCore
+ include ::Clusters::Concerns::ApplicationStatus
+
+ default_value_for :version, VERSION
+
+ def chart
+ 'stable/prometheus'
+ end
+
+ def chart_values_file
+ "#{Rails.root}/vendor/#{name}/values.yaml"
+ end
+
+ def install_command
+ Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
+ end
+ end
+ end
+end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 55419189282..5ecbd4cbceb 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -6,7 +6,8 @@ module Clusters
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
- Applications::Ingress.application_name => Applications::Ingress
+ Applications::Ingress.application_name => Applications::Ingress,
+ Applications::Prometheus.application_name => Applications::Prometheus
}.freeze
belongs_to :user
@@ -21,6 +22,7 @@ module Clusters
has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
+ has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
@@ -62,7 +64,8 @@ module Clusters
def applications
[
application_helm || build_application_helm,
- application_ingress || build_application_ingress
+ application_ingress || build_application_ingress,
+ application_prometheus || build_application_prometheus
]
end
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
new file mode 100644
index 00000000000..a98fa85a5ff
--- /dev/null
+++ b/app/models/clusters/concerns/application_core.rb
@@ -0,0 +1,29 @@
+module Clusters
+ module Concerns
+ module ApplicationCore
+ extend ActiveSupport::Concern
+
+ included do
+ belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
+
+ validates :cluster, presence: true
+
+ after_initialize :set_initial_status
+
+ def set_initial_status
+ return unless not_installable?
+
+ self.status = 'installable' if cluster&.application_helm_installed?
+ end
+
+ def self.application_name
+ self.to_s.demodulize.underscore
+ end
+
+ def name
+ self.class.application_name
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 90ad644ce34..4ae5dd8c677 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,7 +11,7 @@ module CacheMarkdownField
extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output
- CACHE_VERSION = 2
+ CACHE_VERSION = 3
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index 4b4d519f3df..db9770fabf4 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -9,7 +9,6 @@ module DiscussionOnDiff
:original_line_code,
:diff_file,
:diff_line,
- :for_line?,
:active?,
:created_at_diff?,
@@ -39,17 +38,16 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
+
+ initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
+
prev_lines = []
- lines.each do |line|
+ lines[initial_line_index..diff_line.index].each do |line|
if line.meta?
prev_lines.clear
else
prev_lines << line
-
- break if for_line?(line)
-
- prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index f734952fa6c..510b8868462 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -14,10 +14,6 @@ module NoteOnDiff
raise NotImplementedError
end
- def for_line?(line)
- raise NotImplementedError
- end
-
def original_line_code
raise NotImplementedError
end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index cb59b4da3d7..b3fec99c816 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -2,6 +2,7 @@
#
# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
# fields to a new table "project_features", support for the old fields is still needed in the API.
+require 'gitlab/utils'
module ProjectFeaturesCompatibility
extend ActiveSupport::Concern
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index b53d44cda95..15122cbc693 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -21,7 +21,7 @@ class DiffNote < Note
before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text?
- before_validation :set_line_code
+ before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
def discussion_class(*)
@@ -61,10 +61,6 @@ class DiffNote < Note
@diff_line ||= diff_file&.line_for_position(self.original_position)
end
- def for_line?(line)
- diff_file.position(line) == self.original_position
- end
-
def original_line_code
return unless on_text?
diff --git a/app/models/event.rb b/app/models/event.rb
index 6053594fab5..0997b056c6a 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
includes(:author, :project, project: :namespace)
- .preload(:push_event_payload, target: :author)
+ .preload(:target, :push_event_payload)
end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
diff --git a/app/models/identity.rb b/app/models/identity.rb
index 99d99bc6deb..b3fa7d8176a 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -8,6 +8,8 @@ class Identity < ActiveRecord::Base
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false }
validates :user_id, uniqueness: { scope: :provider }
+ before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
+
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
@@ -24,4 +26,12 @@ class Identity < ActiveRecord::Base
uid.to_s
end
end
+
+ private
+
+ def ensure_normalized_extern_uid
+ return if extern_uid.nil?
+
+ self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
+ end
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index c36be956ff0..d90cafd14b4 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -38,11 +38,7 @@ class LegacyDiffNote < Note
end
def diff_line
- @diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file
- end
-
- def for_line?(line)
- line.discussable? && diff_file.line_code(line) == self.line_code
+ @diff_line ||= diff_file&.line_for_line_code(self.line_code)
end
def original_line_code
diff --git a/app/models/project.rb b/app/models/project.rb
index 3440c01b356..f9c640300ff 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -951,7 +951,9 @@ class Project < ActiveRecord::Base
def send_move_instructions(old_path_with_namespace)
# New project path needs to be committed to the DB or notification will
# retrieve stale information
- run_after_commit { NotificationService.new.project_was_moved(self, old_path_with_namespace) }
+ run_after_commit do
+ NotificationService.new.project_was_moved(self, old_path_with_namespace)
+ end
end
def owner
@@ -963,15 +965,19 @@ class Project < ActiveRecord::Base
end
def execute_hooks(data, hooks_scope = :push_hooks)
- hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
- hook.async_execute(data, hooks_scope.to_s)
+ run_after_commit_or_now do
+ hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
+ hook.async_execute(data, hooks_scope.to_s)
+ end
end
end
def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
- services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
- service.async_execute(data)
+ run_after_commit_or_now do
+ services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
+ service.async_execute(data)
+ end
end
end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index 52eb30d688a..caf193bdae3 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -7,7 +7,7 @@ class MergeRequestSerializer < BaseSerializer
case opts[:serializer]
when 'basic', 'sidebar'
MergeRequestBasicEntity
- when 'widget'
+ else # It's 'widget'
MergeRequestWidgetEntity
end
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index 9a4ce31cb39..cba1b920f7c 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -18,7 +18,7 @@ module Clusters
end
def helm_api
- @helm_api ||= Gitlab::Kubernetes::Helm.new(kubeclient)
+ @helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient)
end
def install_command
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 38231f66009..8d4b9f14780 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,11 +1,14 @@
module Files
class BaseService < Commits::CreateService
+ FileChangedError = Class.new(StandardError)
+
def initialize(*args)
super
@author_email = params[:author_email]
@author_name = params[:author_name]
@commit_message = params[:commit_message]
+ @last_commit_sha = params[:last_commit_sha]
@file_path = params[:file_path]
@previous_path = params[:previous_path]
@@ -13,5 +16,16 @@ module Files
@file_content = params[:file_content]
@file_content = Base64.decode64(@file_content) if params[:file_content_encoding] == 'base64'
end
+
+ def file_has_changed?(path, commit_id)
+ return false unless commit_id
+
+ last_commit = Gitlab::Git::Commit
+ .last_for_path(@start_project.repository, @start_branch, path)
+
+ return false unless last_commit
+
+ last_commit.sha != commit_id
+ end
end
end
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 7952e5c95d4..32a57484d4e 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -11,5 +11,15 @@ module Files
start_project: @start_project,
start_branch_name: @start_branch)
end
+
+ private
+
+ def validate!
+ super
+
+ if file_has_changed?(@file_path, @last_commit_sha)
+ raise FileChangedError, "You are attempting to delete a file that has been previously updated."
+ end
+ end
end
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index bfacc462847..98a3e83c130 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -1,5 +1,7 @@
module Files
class MultiService < Files::BaseService
+ UPDATE_FILE_ACTIONS = %w(update move delete).freeze
+
def create_commit!
repository.multi_action(
user: current_user,
@@ -20,6 +22,7 @@ module Files
params[:actions].each do |action|
validate_action!(action)
+ validate_file_status!(action)
end
end
@@ -28,5 +31,15 @@ module Files
raise_error("Unknown action '#{action[:action]}'")
end
end
+
+ def validate_file_status!(action)
+ return unless UPDATE_FILE_ACTIONS.include?(action[:action])
+
+ file_path = action[:previous_path] || action[:file_path]
+
+ if file_has_changed?(file_path, action[:last_commit_id])
+ raise_error("The file has changed since you started editing it: #{file_path}")
+ end
+ end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index bcca1386bed..1902d1cea72 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,13 +1,5 @@
module Files
class UpdateService < Files::BaseService
- FileChangedError = Class.new(StandardError)
-
- def initialize(*args)
- super
-
- @last_commit_sha = params[:last_commit_sha]
- end
-
def create_commit!
repository.update_file(current_user, @file_path, @file_content,
message: @commit_message,
@@ -21,21 +13,10 @@ module Files
private
- def file_has_changed?
- return false unless @last_commit_sha && last_commit
-
- @last_commit_sha != last_commit.sha
- end
-
- def last_commit
- @last_commit ||= Gitlab::Git::Commit
- .last_for_path(@start_project.repository, @start_branch, @file_path)
- end
-
def validate!
super
- if file_has_changed?
+ if file_has_changed?(@file_path, @last_commit_sha)
raise FileChangedError, "You are attempting to update a file that has changed since you started editing it."
end
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index ff188102b62..92a32e703af 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -1,13 +1,16 @@
module Search
class GlobalService
attr_accessor :current_user, :params
+ attr_reader :default_project_filter
def initialize(user, params)
@current_user, @params = user, params.dup
+ @default_project_filter = true
end
def execute
- Gitlab::SearchResults.new(current_user, projects, params[:search])
+ Gitlab::SearchResults.new(current_user, projects, params[:search],
+ default_project_filter: default_project_filter)
end
def projects
diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb
index 29478e3251f..b4efba68715 100644
--- a/app/services/search/group_service.rb
+++ b/app/services/search/group_service.rb
@@ -5,6 +5,7 @@ module Search
def initialize(user, group, params)
super(user, params)
+ @default_project_filter = false
@group = group
end
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 535251fef5e..25946ba6eaf 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -11,23 +11,7 @@
.search-field-holder
= search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
= icon("search", class: "search-icon")
- .dropdown
- - toggle_text = @sort.present? ? sort_options_hash[@sort] : sort_title_recently_created
- = dropdown_toggle(toggle_text, { toggle: 'dropdown' })
- %ul.dropdown-menu.dropdown-menu-align-right
- %li.dropdown-header
- Sort by
- %li
- = link_to admin_groups_path(sort: sort_value_recently_created, name: project_name) do
- = sort_title_recently_created
- = link_to admin_groups_path(sort: sort_value_oldest_created, name: project_name) do
- = sort_title_oldest_created
- = link_to admin_groups_path(sort: sort_value_recently_updated, name: project_name) do
- = sort_title_recently_updated
- = link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do
- = sort_title_oldest_updated
- = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
- = sort_title_largest_group
+ = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
= link_to new_admin_group_path, class: "btn btn-new" do
New group
%ul.content-list
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
new file mode 100644
index 00000000000..8368e7a4563
--- /dev/null
+++ b/app/views/ide/index.html.haml
@@ -0,0 +1,12 @@
+- page_title 'IDE'
+
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'ide'
+
+.ide-flash-container.flash-container
+
+#ide.ide-loading
+ .text-center
+ = icon('spinner spin 2x')
+ %h2.clgray= _('IDE Loading ...')
diff --git a/app/views/layouts/nav_only.html.haml b/app/views/layouts/nav_only.html.haml
new file mode 100644
index 00000000000..6fa4b39dc10
--- /dev/null
+++ b/app/views/layouts/nav_only.html.haml
@@ -0,0 +1,13 @@
+!!! 5
+%html{ lang: I18n.locale, class: page_class }
+ = render "layouts/head"
+ %body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page } }
+ = render 'peek/bar'
+ = render "layouts/header/default"
+ = render 'shared/outdated_browser'
+ .mobile-overlay
+ .alert-wrapper
+ = render "layouts/broadcast"
+ = yield :flash_message
+ = render "layouts/flash"
+ = yield
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 3a7a99462a6..79530e78154 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -7,7 +7,7 @@
.nav-block
= render 'projects/tree/tree_header', tree: @tree
- - if !show_new_repo? && commit
+ - if commit
= render 'shared/commit_well', commit: commit, ref: ref, project: project
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 56eecece54c..b68eb47c6b4 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,18 +1,19 @@
- event = last_push_event
- if event && show_last_push_widget?(event)
- .row-content-block.top-block.hidden-xs.white
- .event-last-push
- .event-last-push-text
- %span= s_("LastPushEvent|You pushed to")
- %strong
- = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
+ %div{ class: container_class }
+ .row-content-block.top-block.hidden-xs.white
+ .event-last-push
+ .event-last-push-text
+ %span= s_("LastPushEvent|You pushed to")
+ %strong
+ = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- - if event.project != @project
- %span= s_("LastPushEvent|at")
- %strong= link_to_project event.project
+ - if event.project != @project
+ %span= s_("LastPushEvent|at")
+ %strong= link_to_project event.project
- #{time_ago_with_tooltip(event.created_at)}
+ #{time_ago_with_tooltip(event.created_at)}
- .pull-right
- = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
- #{ _('Create merge request') }
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
+ #{ _('Create merge request') }
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index c5e3a7945bd..8212ab9a31e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -17,7 +17,7 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
- %li.md-header-toolbar
+ %li.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic text" })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index fe02cbcbf95..7ff7466e561 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -18,7 +18,7 @@
.tree-controls
= link_to download_project_job_artifacts_path(@project, @build),
rel: 'nofollow', download: '', class: 'btn btn-default download' do
- = icon('download')
+ = sprite_icon('download')
Download artifacts archive
.tree-content-holder
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 7777f55ddd7..c9fa90acd11 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -3,7 +3,7 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
.editor-ref
- = icon('code-fork')
+ = sprite_icon('fork', size: 12)
= ref
%span.editor-file-name
- if current_action?(:edit) || current_action?(:update)
diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml
index 281363d2e01..2a77dedd9a2 100644
--- a/app/views/projects/blob/_header.html.haml
+++ b/app/views/projects/blob/_header.html.haml
@@ -12,6 +12,7 @@
.btn-group{ role: "group" }<
= edit_blob_link
+ = ide_blob_link
- if current_user
= replace_blob_link
= delete_blob_link
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index c4712bf3736..4d358052d43 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,21 +6,14 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'blob'
- - if show_new_repo?
- = webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'repo'
-
= render 'projects/last_push'
%div{ class: container_class }
- - if show_new_repo?
- = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_blob_path(@project, @id)
- - else
- #tree-holder.tree-holder
- = render 'blob', blob: @blob
+ #tree-holder.tree-holder
+ = render 'blob', blob: @blob
- if can_modify_blob?(@blob)
= render 'projects/blob/remove'
- - title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
+ - title = "Replace #{@blob.name}"
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/blob/viewers/_download.html.haml b/app/views/projects/blob/viewers/_download.html.haml
index 253566c43be..f9b1da05a00 100644
--- a/app/views/projects/blob/viewers/_download.html.haml
+++ b/app/views/projects/blob/viewers/_download.html.haml
@@ -2,6 +2,6 @@
.center.render-error.vertical-center
= link_to blob_raw_path do
%h1.light
- = icon('download')
+ = sprite_icon('download')
%h4
Download (#{number_to_human_size(viewer.blob.raw_size)})
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 573050e597d..acf67b83890 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -8,7 +8,8 @@
%li{ class: "js-branch-#{branch.name}" }
%div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
- = icon('code-fork', class: 'append-right-5') + "#{branch.name}"
+ = sprite_icon('fork', size: 12)
+ = branch.name
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index fa6f6d0b588..fa9a9bfc8f7 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -3,7 +3,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
- = icon('download')
+ = sprite_icon('download')
= icon("caret-down")
%span.sr-only= _('Select Archive Format')
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 86510b8ab93..0cd2d45c74b 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -22,7 +22,7 @@
- if ref
- if job.ref
.icon-container
- = job.tag? ? icon('tag') : icon('code-fork')
+ = job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else
.light none
@@ -96,7 +96,7 @@
.pull-right
- if can?(current_user, :read_build, job) && job.artifacts?
= link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
- = icon('download')
+ = sprite_icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
= link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index fe6dacf1f0d..0115c64c076 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -9,6 +9,7 @@
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
+ install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
diff --git a/app/views/projects/commit/_limit_exceeded_message.html.haml b/app/views/projects/commit/_limit_exceeded_message.html.haml
index 84a52d49487..a264f3517c4 100644
--- a/app/views/projects/commit/_limit_exceeded_message.html.haml
+++ b/app/views/projects/commit/_limit_exceeded_message.html.haml
@@ -1,7 +1,7 @@
.has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: "Project has too many #{label_for_message} to search"} }
.limit-icon
- if objects == :branch
- = icon('code-fork')
+ = sprite_icon('fork', size: 12)
- else
= icon('tag')
.limit-message
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 014486be868..c7ac687e4a6 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -2,7 +2,7 @@
.branch-commit
- if deployment.ref
%span.icon-container
- = deployment.tag? ? icon('tag') : icon('code-fork')
+ = deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index d365bcd4ecc..e8a89b8c6fc 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -2,7 +2,7 @@
- if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block
%h4
- %i.fa.fa-code-fork
+ = sprite_icon('fork', size: 16)
Fork Error!
%p
You tried to fork
@@ -21,5 +21,4 @@
%p
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
- %i.fa.fa-code-fork
Try to fork again
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index b98dc09534f..2599ce5c4b8 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -19,7 +19,7 @@
- if ref
- if generic_commit_status.ref
.icon-container
- = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
+ = generic_commit_status.tags.any? ? icon('tag') : sprite_icon('fork', size: 10)
= link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
- else
.light none
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 2b5e8711b0a..f45a000833b 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -30,7 +30,7 @@
%span.project-ref-path
&nbsp;
= link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
- = icon('code-fork')
+ = sprite_icon('fork', size: 12, css_class: 'fork-sprite')
= merge_request.target_branch
- if merge_request.labels.any?
&nbsp;
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index f8c4005a9e0..800e234275c 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -3,7 +3,7 @@
%td
= pipeline_schedule.description
%td.branch-name-cell
- = icon('code-fork')
+ = sprite_icon('fork', size: 12)
- if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td
diff --git a/app/views/projects/tree/_old_tree_content.html.haml b/app/views/projects/tree/_old_tree_content.html.haml
deleted file mode 100644
index 6ea78851b8d..00000000000
--- a/app/views/projects/tree/_old_tree_content.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
- .table-holder
- %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
- %thead
- %tr
- %th= s_('ProjectFileTree|Name')
- %th.hidden-xs
- .pull-left= _('Last commit')
- %th.text-right= _('Last update')
- - if @path.present?
- %tr.tree-item
- %td.tree-item-file-name
- = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
- %td
- %td.hidden-xs
-
- = render_tree(tree)
-
- - if tree.readme
- = render "projects/tree/readme", readme: tree.readme
-
-- if can_edit_tree?
- = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
- = render 'projects/blob/new_dir'
diff --git a/app/views/projects/tree/_old_tree_header.html.haml b/app/views/projects/tree/_old_tree_header.html.haml
deleted file mode 100644
index 7f636b7e0e8..00000000000
--- a/app/views/projects/tree/_old_tree_header.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-- if on_top_of_branch?
- - addtotree_toggle_attributes = { href: '#', 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown' }
-- else
- - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
-
-%ul.breadcrumb.repo-breadcrumb
- %li
- = link_to project_tree_path(@project, @ref) do
- = @project.path
- - path_breadcrumbs do |title, path|
- %li
- = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
-
- - if current_user
- %li
- %a.btn.add-to-tree{ addtotree_toggle_attributes }
- = sprite_icon('plus', size: 16, css_class: 'pull-left')
- = sprite_icon('arrow-down', size: 16, css_class: 'pull-left')
- - if on_top_of_branch?
- .add-to-tree-dropdown
- %ul.dropdown-menu
- - if can_edit_tree?
- %li
- = link_to project_new_blob_path(@project, @id) do
- #{ _('New file') }
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
- #{ _('Upload file') }
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
- #{ _('New directory') }
- - elsif can?(current_user, :fork_project, @project)
- %li
- - continue_params = { to: project_new_blob_path(@project, @id),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to upload a file again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('Upload file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to create a new directory again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New directory') }
-
- %li.divider
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index a4bdd67209d..6ea78851b8d 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,5 +1,24 @@
-- content_url = local_assigns.fetch(:content_url, nil)
-- if show_new_repo?
- = render 'shared/repo/repo', project: @project, content_url: content_url
-- else
- = render 'projects/tree/old_tree_content', tree: tree
+.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
+ .table-holder
+ %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
+ %thead
+ %tr
+ %th= s_('ProjectFileTree|Name')
+ %th.hidden-xs
+ .pull-left= _('Last commit')
+ %th.text-right= _('Last update')
+ - if @path.present?
+ %tr.tree-item
+ %td.tree-item-file-name
+ = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
+ %td
+ %td.hidden-xs
+
+ = render_tree(tree)
+
+ - if tree.readme
+ = render "projects/tree/readme", readme: tree.readme
+
+- if can_edit_tree?
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
+ = render 'projects/blob/new_dir'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index c02f7ee37ed..d1ecef39475 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -2,16 +2,78 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- - if show_new_repo? && can_push_branch?(@project, @ref)
- .js-new-dropdown
- - else
- = render 'projects/tree/old_tree_header'
+ - if on_top_of_branch?
+ - addtotree_toggle_attributes = { href: '#', 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown' }
+ - else
+ - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
+
+ %ul.breadcrumb.repo-breadcrumb
+ %li
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - path_breadcrumbs do |title, path|
+ %li
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
+
+ - if current_user
+ %li
+ %a.btn.add-to-tree{ addtotree_toggle_attributes }
+ = sprite_icon('plus', size: 16, css_class: 'pull-left')
+ = sprite_icon('arrow-down', size: 16, css_class: 'pull-left')
+ - if on_top_of_branch?
+ .add-to-tree-dropdown
+ %ul.dropdown-menu
+ - if can_edit_tree?
+ %li
+ = link_to project_new_blob_path(@project, @id) do
+ #{ _('New file') }
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
+ #{ _('Upload file') }
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
+ #{ _('New directory') }
+ - elsif can?(current_user, :fork_project, @project)
+ %li
+ - continue_params = { to: project_new_blob_path(@project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('New file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to upload a file again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('Upload file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to create a new directory again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('New directory') }
+
+ %li.divider
+ %li
+ = link_to new_project_branch_path(@project) do
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ #{ _('New tag') }
.tree-controls
- - if show_new_repo?
- .editable-mode
- - else
- = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+ - if show_new_ide?
+ = succeed " " do
+ = link_to ide_edit_path(@project, @id), class: 'btn btn-default' do
+ = ide_edit_text
+
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 64cc70053ef..709be20e00f 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -6,11 +6,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-- if show_new_repo?
- - content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'repo'
+= render 'projects/last_push'
-%div{ class: [(container_class unless show_new_repo?), ("limit-container-width" unless fluid_layout)] }
- = render 'projects/last_push'
+%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] }
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index f4a4bfaec54..479bd2cdb38 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,6 +1,6 @@
- show_create = local_assigns.fetch(:show_create, false)
-- show_new_branch_form = show_new_repo? && show_create && can?(current_user, :push_code, @project)
+- show_new_branch_form = show_new_ide? && show_create && can?(current_user, :push_code, @project)
- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml
index 8e6747ca740..1a259b679c7 100644
--- a/app/views/shared/groups/_dropdown.html.haml
+++ b/app/views/shared/groups/_dropdown.html.haml
@@ -1,3 +1,4 @@
+- options_hash = local_assigns.fetch(:options_hash, groups_sort_options_hash)
- show_archive_options = local_assigns.fetch(:show_archive_options, false)
- if @sort.present?
- default_sort_by = @sort
@@ -10,12 +11,12 @@
.dropdown.inline.js-group-filter-dropdown-wrap.append-right-10
%button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-label
- = sort_options_hash[default_sort_by]
+ = options_hash[default_sort_by]
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
= _("Sort by")
- - groups_sort_options_hash.each do |value, title|
+ - options_hash.each do |value, title|
%li.js-filter-sort-order
= link_to filter_groups_path(sort: value), class: ("is-active" if default_sort_by == value) do
= title
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 003f5fa52eb..63aa4e29ec9 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -52,7 +52,7 @@
= render_project_pipeline_status(project.pipeline_status)
- if forks
%span.prepend-left-10
- = icon('code-fork')
+ = sprite_icon('fork')
= number_with_delimiter(project.forks_count)
- if stars
%span.prepend-left-10
diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml
deleted file mode 100644
index 87e8c416194..00000000000
--- a/app/views/shared/repo/_repo.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- @no_container = true;
-#repo{ data: { root: @path.empty?.to_s,
- root_url: project_tree_path(project),
- url: content_url,
- current_branch: @ref,
- ref: @commit.id,
- project_name: project.name,
- project_url: project_path(project),
- project_id: project.id,
- new_merge_request_url: namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_branch: '' }),
- can_commit: (!!can_push_branch?(project, @ref)).to_s,
- on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s,
- current_path: @path } }
diff --git a/changelogs/unreleased/13634-broadcast-message.yml b/changelogs/unreleased/13634-broadcast-message.yml
deleted file mode 100644
index 26c4c133443..00000000000
--- a/changelogs/unreleased/13634-broadcast-message.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broadcast message not showing up on login page
-merge_request: 15578
-author:
-type: fixed
diff --git a/changelogs/unreleased/15588-fix-empty-dropdown-on-create-new-pipeline-in-case-of-validation-errors.yml b/changelogs/unreleased/15588-fix-empty-dropdown-on-create-new-pipeline-in-case-of-validation-errors.yml
deleted file mode 100644
index a4934c8896d..00000000000
--- a/changelogs/unreleased/15588-fix-empty-dropdown-on-create-new-pipeline-in-case-of-validation-errors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Initializes the branches dropdown when the 'Start new pipeline' failed due to validation errors
-merge_request: 15588
-author: Christiaan Van den Poel
-type: fixed
diff --git a/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml b/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml
deleted file mode 100644
index 1179b3f20e6..00000000000
--- a/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'fix #39233 - 500 in merge request'
-merge_request: 15774
-author: Martin Nowak
-type: fixed
diff --git a/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml b/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml
new file mode 100644
index 00000000000..db2bd6e692b
--- /dev/null
+++ b/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml
@@ -0,0 +1,5 @@
+---
+title: 'Validate file status when commiting multiple files'
+merge_request: 15922
+author:
+type: added
diff --git a/changelogs/unreleased/15955-improve-search-query.yml b/changelogs/unreleased/15955-improve-search-query.yml
new file mode 100644
index 00000000000..80cb8af617f
--- /dev/null
+++ b/changelogs/unreleased/15955-improve-search-query.yml
@@ -0,0 +1,5 @@
+---
+title: Improve search query for merge requests.
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/18040-rubocop-line-break-after-guard-clause.yml b/changelogs/unreleased/18040-rubocop-line-break-after-guard-clause.yml
deleted file mode 100644
index e3c7ffc8046..00000000000
--- a/changelogs/unreleased/18040-rubocop-line-break-after-guard-clause.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds Rubocop rule for line break after guard clause
-merge_request: 15188
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml b/changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml
deleted file mode 100644
index d4c209aaf2b..00000000000
--- a/changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add an ability to use a custom branch name on creation from issues
-merge_request: 13884
-author: Vitaliy @blackst0ne Klachkov
-type: added
diff --git a/changelogs/unreleased/22680-unlabel-slash-command-limit-autocomplete-to-applied-labels.yml b/changelogs/unreleased/22680-unlabel-slash-command-limit-autocomplete-to-applied-labels.yml
deleted file mode 100644
index 6d7f8655282..00000000000
--- a/changelogs/unreleased/22680-unlabel-slash-command-limit-autocomplete-to-applied-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Limit autocomplete menu to applied labels
-merge_request: 11110
-author: Vitaliy @blackst0ne Klachkov
-type: added
diff --git a/changelogs/unreleased/28377-add-edit-button-to-mobile-file-view.yml b/changelogs/unreleased/28377-add-edit-button-to-mobile-file-view.yml
deleted file mode 100644
index b6646379b8d..00000000000
--- a/changelogs/unreleased/28377-add-edit-button-to-mobile-file-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add edit button to mobile file view
-merge_request: 15199
-author: Travis Miller
-type: added
diff --git a/changelogs/unreleased/29483-no-feedback-when-checking-on-checklist-if-potential-spam-was-detected.yml b/changelogs/unreleased/29483-no-feedback-when-checking-on-checklist-if-potential-spam-was-detected.yml
deleted file mode 100644
index 6bfcc5e70de..00000000000
--- a/changelogs/unreleased/29483-no-feedback-when-checking-on-checklist-if-potential-spam-was-detected.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add recaptcha modal to issue updates detected as spam
-merge_request: 15408
-author:
-type: fixed
diff --git a/changelogs/unreleased/32057_support_ordering_project_notes_in_notes_api.yml b/changelogs/unreleased/32057_support_ordering_project_notes_in_notes_api.yml
deleted file mode 100644
index cd19d24e035..00000000000
--- a/changelogs/unreleased/32057_support_ordering_project_notes_in_notes_api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: added support for ordering and sorting in notes api
-merge_request: 15342
-author: haseebeqx
-type: added
diff --git a/changelogs/unreleased/32098-pipelines-navigation.yml b/changelogs/unreleased/32098-pipelines-navigation.yml
deleted file mode 100644
index 925c92b6be8..00000000000
--- a/changelogs/unreleased/32098-pipelines-navigation.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Stop reloading the page when using pagination and tabs - use API calls - in
- Pipelines table
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/32878-merge-request-from-email.yml b/changelogs/unreleased/32878-merge-request-from-email.yml
deleted file mode 100644
index 2df148d5a81..00000000000
--- a/changelogs/unreleased/32878-merge-request-from-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow creation of merge request from email
-merge_request: 13817
-author: janp
-type: added
diff --git a/changelogs/unreleased/33338-internationalization-support-for-prometheus-service-configuration.yml b/changelogs/unreleased/33338-internationalization-support-for-prometheus-service-configuration.yml
deleted file mode 100644
index d61bbf2e355..00000000000
--- a/changelogs/unreleased/33338-internationalization-support-for-prometheus-service-configuration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add internationalization support for the prometheus integration
-merge_request: 33338
-author:
-type: other
diff --git a/changelogs/unreleased/33609-hide-pagination.yml b/changelogs/unreleased/33609-hide-pagination.yml
new file mode 100644
index 00000000000..3586b091cb1
--- /dev/null
+++ b/changelogs/unreleased/33609-hide-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Disable Vue pagination when only one page of content is available
+merge_request: 15999
+author: Mario de la Ossa
+type: fixed
diff --git a/changelogs/unreleased/34600-performance-wiki-pages.yml b/changelogs/unreleased/34600-performance-wiki-pages.yml
deleted file mode 100644
index 541ae8f8e60..00000000000
--- a/changelogs/unreleased/34600-performance-wiki-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Performance issues when loading large number of wiki pages
-merge_request: 15276
-author:
-type: performance
diff --git a/changelogs/unreleased/35385-allow-git-pull-push-on-project-redirects.yml b/changelogs/unreleased/35385-allow-git-pull-push-on-project-redirects.yml
deleted file mode 100644
index 31450287caf..00000000000
--- a/changelogs/unreleased/35385-allow-git-pull-push-on-project-redirects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow git pull/push on group/user/project redirects
-merge_request: 15670
-author:
-type: added
diff --git a/changelogs/unreleased/35616-move-k8-to-cluster-page.yml b/changelogs/unreleased/35616-move-k8-to-cluster-page.yml
deleted file mode 100644
index 032a39608ce..00000000000
--- a/changelogs/unreleased/35616-move-k8-to-cluster-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create a new form to add Existing Kubernetes Cluster
-merge_request: 14805
-author:
-type: added
diff --git a/changelogs/unreleased/35724-animate-sidebar.yml b/changelogs/unreleased/35724-animate-sidebar.yml
deleted file mode 100644
index 5d0b46a23c8..00000000000
--- a/changelogs/unreleased/35724-animate-sidebar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Animate contextual sidebar on collapse/expand
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/36400-trigger-job.yml b/changelogs/unreleased/36400-trigger-job.yml
deleted file mode 100644
index 27243813dc8..00000000000
--- a/changelogs/unreleased/36400-trigger-job.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent 500 error when inspecting job after trigger was removed
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml b/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml
new file mode 100644
index 00000000000..8348e3e8ceb
--- /dev/null
+++ b/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml
@@ -0,0 +1,5 @@
+---
+title: Enable ordering of groups and their children by name
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/37354-pipelines-update.yml b/changelogs/unreleased/37354-pipelines-update.yml
deleted file mode 100644
index 2b6ddfe95ed..00000000000
--- a/changelogs/unreleased/37354-pipelines-update.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure head pippeline always corresponds with the head sha of an MR
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/38032-deploy-markers-should-be-more-verbose.yml b/changelogs/unreleased/38032-deploy-markers-should-be-more-verbose.yml
deleted file mode 100644
index a1f28b3ba0f..00000000000
--- a/changelogs/unreleased/38032-deploy-markers-should-be-more-verbose.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Changed the deploy markers on the prometheus dashboard to be more verbose
-merge_request: 38032
-author:
-type: changed
diff --git a/changelogs/unreleased/38075_allow_refernce_integer_labels.yml b/changelogs/unreleased/38075_allow_refernce_integer_labels.yml
deleted file mode 100644
index b5342d4adf8..00000000000
--- a/changelogs/unreleased/38075_allow_refernce_integer_labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix errors when selecting numeric-only labels in the labels autocomplete selector
-merge_request: 14607
-author: haseebeqx
-type: fixed
diff --git a/changelogs/unreleased/38393-Milestone-duration-error-message-is-not-accurate-enough.yml b/changelogs/unreleased/38393-Milestone-duration-error-message-is-not-accurate-enough.yml
deleted file mode 100644
index c73cf8bf60b..00000000000
--- a/changelogs/unreleased/38393-Milestone-duration-error-message-is-not-accurate-enough.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Changed validation error message on wrong milestone dates
-merge_request:
-author: Xurxo Méndez Pérez
-type: fixed
diff --git a/changelogs/unreleased/38822-oauth-search-case-insensitive.yml b/changelogs/unreleased/38822-oauth-search-case-insensitive.yml
deleted file mode 100644
index d84360b4c5c..00000000000
--- a/changelogs/unreleased/38822-oauth-search-case-insensitive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: OAuth identity lookups case-insensitive
-merge_request: 15312
-author:
-type: fixed
diff --git a/changelogs/unreleased/38862-email-notifications-not-sent-as-expected.yml b/changelogs/unreleased/38862-email-notifications-not-sent-as-expected.yml
deleted file mode 100644
index 6b1b309ab14..00000000000
--- a/changelogs/unreleased/38862-email-notifications-not-sent-as-expected.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix sending notification emails to users with the mention level set who were
- mentioned in an issue or merge request description
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/38869-templates.yml b/changelogs/unreleased/38869-templates.yml
deleted file mode 100644
index 957b5f27bd0..00000000000
--- a/changelogs/unreleased/38869-templates.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove template selector from global namespace
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/38877-disable-autocomplete-in-filtered-search.yml b/changelogs/unreleased/38877-disable-autocomplete-in-filtered-search.yml
deleted file mode 100644
index 07439a860ec..00000000000
--- a/changelogs/unreleased/38877-disable-autocomplete-in-filtered-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disables autocomplete in filtered searc
-merge_request: 15477
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml b/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml
new file mode 100644
index 00000000000..9ab0a0159e9
--- /dev/null
+++ b/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml
@@ -0,0 +1,5 @@
+---
+title: Use relative URLs when linking to uploaded files
+merge_request: 15751
+author:
+type: other
diff --git a/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml b/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml
deleted file mode 100644
index a4d703bc69f..00000000000
--- a/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add the option to automatically run a pipeline after updating AutoDevOps settings
-merge_request: 15380
-author:
-type: changed
diff --git a/changelogs/unreleased/39335-add-time-spend-to-milestones.yml b/changelogs/unreleased/39335-add-time-spend-to-milestones.yml
deleted file mode 100644
index 41a43418cbf..00000000000
--- a/changelogs/unreleased/39335-add-time-spend-to-milestones.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add total time spent to milestones
-merge_request: 15116
-author: George Andrinopoulos
-type: added
diff --git a/changelogs/unreleased/39364-in-issue-board-url-doesn-t-take-in-account-hostname-settings.yml b/changelogs/unreleased/39364-in-issue-board-url-doesn-t-take-in-account-hostname-settings.yml
deleted file mode 100644
index 9793c6a8e9c..00000000000
--- a/changelogs/unreleased/39364-in-issue-board-url-doesn-t-take-in-account-hostname-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change boards page boards_data absolute urls to paths
-merge_request: 15703
-author:
-type: fixed
diff --git a/changelogs/unreleased/39367-fix-new-email-session-path.yml b/changelogs/unreleased/39367-fix-new-email-session-path.yml
deleted file mode 100644
index 73485d9d1a9..00000000000
--- a/changelogs/unreleased/39367-fix-new-email-session-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Confirming email with invalid token should no longer generate an error
-merge_request: 15726
-author:
-type: fixed
diff --git a/changelogs/unreleased/39436-pages-api-administrative.yml b/changelogs/unreleased/39436-pages-api-administrative.yml
deleted file mode 100644
index f38bbbd479c..00000000000
--- a/changelogs/unreleased/39436-pages-api-administrative.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add administrative endpoint to list all pages domains
-merge_request: 15160
-author: Travis Miller
-type: added
diff --git a/changelogs/unreleased/39455-clone-dropdown-should-not-have-a-tooltip.yml b/changelogs/unreleased/39455-clone-dropdown-should-not-have-a-tooltip.yml
deleted file mode 100644
index cb522cb7611..00000000000
--- a/changelogs/unreleased/39455-clone-dropdown-should-not-have-a-tooltip.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removed tooltip from clone dropdown
-merge_request: 15334
-author:
-type: other
diff --git a/changelogs/unreleased/39497-inline-edit-issue-on-mobile.yml b/changelogs/unreleased/39497-inline-edit-issue-on-mobile.yml
deleted file mode 100644
index fc7c024f95a..00000000000
--- a/changelogs/unreleased/39497-inline-edit-issue-on-mobile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add inline editing to issues on mobile
-merge_request: 15438
-author:
-type: changed
diff --git a/changelogs/unreleased/39601-create-issuable-destroy-service.yml b/changelogs/unreleased/39601-create-issuable-destroy-service.yml
deleted file mode 100644
index b0463f02eba..00000000000
--- a/changelogs/unreleased/39601-create-issuable-destroy-service.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create issuable destroy service
-merge_request: 15604
-author: George Andrinopoulos
-type: other
diff --git a/changelogs/unreleased/39602-move-update-project-counter-caches-out-of-issues-merge-requests.yml b/changelogs/unreleased/39602-move-update-project-counter-caches-out-of-issues-merge-requests.yml
deleted file mode 100644
index 056afe43010..00000000000
--- a/changelogs/unreleased/39602-move-update-project-counter-caches-out-of-issues-merge-requests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move update_project_counter_caches? out of issue and merge request
-merge_request: 15300
-author: George Andrinopoulos
-type: other
diff --git a/changelogs/unreleased/39720-group-milestone-sorting.yml b/changelogs/unreleased/39720-group-milestone-sorting.yml
deleted file mode 100644
index 15ef87fa567..00000000000
--- a/changelogs/unreleased/39720-group-milestone-sorting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add dropdown sort to group milestones
-merge_request: 15230
-author: George Andrinopoulos
-type: added
diff --git a/changelogs/unreleased/39727-add-axios-to-common.yml b/changelogs/unreleased/39727-add-axios-to-common.yml
deleted file mode 100644
index 688757d2486..00000000000
--- a/changelogs/unreleased/39727-add-axios-to-common.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add axios to common file
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/39821-fix-commits-list-with-multi-file-editor.yml b/changelogs/unreleased/39821-fix-commits-list-with-multi-file-editor.yml
deleted file mode 100644
index 8b27c43d15b..00000000000
--- a/changelogs/unreleased/39821-fix-commits-list-with-multi-file-editor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix commits page throwing 500 when the multi-file editor was enabled
-merge_request: 15502
-author:
-type: fixed
diff --git a/changelogs/unreleased/39827-fix-projects-dropdown-overflow.yml b/changelogs/unreleased/39827-fix-projects-dropdown-overflow.yml
deleted file mode 100644
index ebd7b582e8a..00000000000
--- a/changelogs/unreleased/39827-fix-projects-dropdown-overflow.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix item name and namespace text overflow in Projects dropdown
-merge_request: 15451
-author:
-type: fixed
diff --git a/changelogs/unreleased/39869_show_closed_status_of_links_to_issues_on_wiki_pages.yml b/changelogs/unreleased/39869_show_closed_status_of_links_to_issues_on_wiki_pages.yml
deleted file mode 100644
index cd31ed463a2..00000000000
--- a/changelogs/unreleased/39869_show_closed_status_of_links_to_issues_on_wiki_pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: show status of gitlab reference links in wiki
-merge_request: 15694
-author: haseebeqx
-type: added
diff --git a/changelogs/unreleased/39884-fix-pipeline-transition-with-single-manual-action.yml b/changelogs/unreleased/39884-fix-pipeline-transition-with-single-manual-action.yml
deleted file mode 100644
index 580b97241e7..00000000000
--- a/changelogs/unreleased/39884-fix-pipeline-transition-with-single-manual-action.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix pipeline status transition for single manual job. This would also fix pipeline
- duration becuse it is depending on status transition
-merge_request: 15251
-author:
-type: fixed
diff --git a/changelogs/unreleased/39895-cant-set-mattermost-username-channel-from-api.yml b/changelogs/unreleased/39895-cant-set-mattermost-username-channel-from-api.yml
deleted file mode 100644
index 358c007387e..00000000000
--- a/changelogs/unreleased/39895-cant-set-mattermost-username-channel-from-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix acceptance of username for Mattermost service update
-merge_request: 15275
-author:
-type: fixed
diff --git a/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml b/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml
deleted file mode 100644
index b7a974fd8d9..00000000000
--- a/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set the default gitlab-shell timeout to 3 hours
-merge_request: 15292
-author:
-type: fixed
diff --git a/changelogs/unreleased/40016-log-header.yml b/changelogs/unreleased/40016-log-header.yml
deleted file mode 100644
index f52c2d2a0d5..00000000000
--- a/changelogs/unreleased/40016-log-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide log size for mobile screens
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml b/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml
new file mode 100644
index 00000000000..e2fade2bfd9
--- /dev/null
+++ b/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml
@@ -0,0 +1,5 @@
+---
+title: Adds the multi file editor as a new beta feature
+merge_request: 15430
+author:
+type: feature
diff --git a/changelogs/unreleased/40063-markdown-editor-improvements.yml b/changelogs/unreleased/40063-markdown-editor-improvements.yml
new file mode 100644
index 00000000000..fa2f09408b4
--- /dev/null
+++ b/changelogs/unreleased/40063-markdown-editor-improvements.yml
@@ -0,0 +1,5 @@
+---
+title: Hide markdown toolbar in preview mode
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/40122-only-one-note-webhook-is-triggered-when-a-comment-with-time-spent-is-added.yml b/changelogs/unreleased/40122-only-one-note-webhook-is-triggered-when-a-comment-with-time-spent-is-added.yml
deleted file mode 100644
index a2ae2059c47..00000000000
--- a/changelogs/unreleased/40122-only-one-note-webhook-is-triggered-when-a-comment-with-time-spent-is-added.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add total_time_spent to the `changes` hash in issuable Webhook payloads
-merge_request: 15381
-author:
-type: changed
diff --git a/changelogs/unreleased/40146_fix_special_charecter_search_in_filenames.yml b/changelogs/unreleased/40146_fix_special_charecter_search_in_filenames.yml
deleted file mode 100644
index 00f7dd7c0f0..00000000000
--- a/changelogs/unreleased/40146_fix_special_charecter_search_in_filenames.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix search results when a filename would contain a special character.
-merge_request: 15606
-author: haseebeqx
-type: fixed
diff --git a/changelogs/unreleased/40161-extra-margin-on-svg-logo-in-header.yml b/changelogs/unreleased/40161-extra-margin-on-svg-logo-in-header.yml
deleted file mode 100644
index fdaa90f0d5d..00000000000
--- a/changelogs/unreleased/40161-extra-margin-on-svg-logo-in-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove extra margin from wordmark in header
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/40286-hide-full-namespace-groups-tree.yml b/changelogs/unreleased/40286-hide-full-namespace-groups-tree.yml
deleted file mode 100644
index cae02d0c2f6..00000000000
--- a/changelogs/unreleased/40286-hide-full-namespace-groups-tree.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Show only group name by default and put full namespace in tooltip in Groups
- tree
-merge_request: 15650
-author:
-type: changed
diff --git a/changelogs/unreleased/40290-remove-rake-gitlab-sidekiq-drop-post-receive.yml b/changelogs/unreleased/40290-remove-rake-gitlab-sidekiq-drop-post-receive.yml
deleted file mode 100644
index 9c308321a19..00000000000
--- a/changelogs/unreleased/40290-remove-rake-gitlab-sidekiq-drop-post-receive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removed unused rake task, 'rake gitlab:sidekiq:drop_post_receive'
-merge_request: 15493
-author:
-type: fixed
diff --git a/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml b/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml
deleted file mode 100644
index e683e60397e..00000000000
--- a/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix Issue comment submit button being disabled when pasting content from another
- GFM note
-merge_request: 15530
-author:
-type: fixed
diff --git a/changelogs/unreleased/40481-bump-jquery-to-2-2-4.yml b/changelogs/unreleased/40481-bump-jquery-to-2-2-4.yml
deleted file mode 100644
index e275c65e8c8..00000000000
--- a/changelogs/unreleased/40481-bump-jquery-to-2-2-4.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade jQuery to 2.2.4
-merge_request: 15570
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/40508-snippets-zen-mode.yml b/changelogs/unreleased/40508-snippets-zen-mode.yml
deleted file mode 100644
index c205218575b..00000000000
--- a/changelogs/unreleased/40508-snippets-zen-mode.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Init zen mode in snippets pages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/40530-merge-request-generates-wrong-diff-when-branch-and-tag-have-the-same-name.yml b/changelogs/unreleased/40530-merge-request-generates-wrong-diff-when-branch-and-tag-have-the-same-name.yml
deleted file mode 100644
index e9fae6fe0d7..00000000000
--- a/changelogs/unreleased/40530-merge-request-generates-wrong-diff-when-branch-and-tag-have-the-same-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix merge requests where the source or target branch name matches a tag name
-merge_request: 15591
-author:
-type: fixed
diff --git a/changelogs/unreleased/40561-environment-scope-value-is-not-trimmed.yml b/changelogs/unreleased/40561-environment-scope-value-is-not-trimmed.yml
deleted file mode 100644
index e0e3ddbdaa8..00000000000
--- a/changelogs/unreleased/40561-environment-scope-value-is-not-trimmed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Strip leading & trailing whitespaces in CI/CD secret variable keys
-merge_request: 15615
-author:
-type: fixed
diff --git a/changelogs/unreleased/40568-bump-seed-fu-to-2-3-7.yml b/changelogs/unreleased/40568-bump-seed-fu-to-2-3-7.yml
deleted file mode 100644
index 708269d5c83..00000000000
--- a/changelogs/unreleased/40568-bump-seed-fu-to-2-3-7.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade seed-fu to 2.3.7
-merge_request: 15607
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml b/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml
deleted file mode 100644
index afbb869bdbb..00000000000
--- a/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename GKE as Kubernetes Engine
-merge_request: 15608
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/40770-doc-elasticsearch.yml b/changelogs/unreleased/40770-doc-elasticsearch.yml
deleted file mode 100644
index b770fb7548e..00000000000
--- a/changelogs/unreleased/40770-doc-elasticsearch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo in docs about Elasticsearch
-merge_request: 15699
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/4080-align-retry-btn.yml b/changelogs/unreleased/4080-align-retry-btn.yml
deleted file mode 100644
index c7d3997839c..00000000000
--- a/changelogs/unreleased/4080-align-retry-btn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Align retry button with job title with new grid size
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml b/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml
new file mode 100644
index 00000000000..ffb79d7d79f
--- /dev/null
+++ b/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml
@@ -0,0 +1,5 @@
+---
+title: Add Prometheus to available Cluster applications
+merge_request: 15895
+author:
+type: added
diff --git a/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml b/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml
new file mode 100644
index 00000000000..188a854ebee
--- /dev/null
+++ b/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Ruby to 2.3.6 to include security patches
+merge_request: 16016
+author:
+type: security
diff --git a/changelogs/unreleased/add_project_ci_config_path_leading_slash_validation.yml b/changelogs/unreleased/add_project_ci_config_path_leading_slash_validation.yml
deleted file mode 100644
index 1c96bd66ed4..00000000000
--- a/changelogs/unreleased/add_project_ci_config_path_leading_slash_validation.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Prefer ci_config_path validation for leading slashes instead of sanitizing
- the input
-merge_request: 15672
-author: Christiaan Van den Poel
-type: other
diff --git a/changelogs/unreleased/admin-welcome-new-group-link.yml b/changelogs/unreleased/admin-welcome-new-group-link.yml
deleted file mode 100644
index 9c939a0a3ad..00000000000
--- a/changelogs/unreleased/admin-welcome-new-group-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed admin welcome screen new group path
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/an-gitaly-timeouts.yml b/changelogs/unreleased/an-gitaly-timeouts.yml
deleted file mode 100644
index e18d82b2704..00000000000
--- a/changelogs/unreleased/an-gitaly-timeouts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add timeouts for Gitaly calls
-merge_request: 15047
-author:
-type: performance
diff --git a/changelogs/unreleased/brand_header_change.yml b/changelogs/unreleased/brand_header_change.yml
deleted file mode 100644
index 6ea6e8192a4..00000000000
--- a/changelogs/unreleased/brand_header_change.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: When a custom header logo is present, don't show GitLab type logo
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/bvl-circuitbreaker-process.yml b/changelogs/unreleased/bvl-circuitbreaker-process.yml
deleted file mode 100644
index 595dd13f724..00000000000
--- a/changelogs/unreleased/bvl-circuitbreaker-process.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Monitor NFS shards for circuitbreaker in a separate process
-merge_request: 15426
-author:
-type: changed
diff --git a/changelogs/unreleased/bvl-delete-empty-fork-networks.yml b/changelogs/unreleased/bvl-delete-empty-fork-networks.yml
deleted file mode 100644
index 3bbb4cf6e3c..00000000000
--- a/changelogs/unreleased/bvl-delete-empty-fork-networks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clean up empty fork networks
-merge_request: 15373
-author:
-type: other
diff --git a/changelogs/unreleased/bvl-limit-fork-queries-on-project-show.yml b/changelogs/unreleased/bvl-limit-fork-queries-on-project-show.yml
deleted file mode 100644
index 299d9bf6b9c..00000000000
--- a/changelogs/unreleased/bvl-limit-fork-queries-on-project-show.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reduce requests for project forks on show page of projects that have forks
-merge_request: 15663
-author:
-type: performance
diff --git a/changelogs/unreleased/bvl-subgroup-in-dropdowns.yml b/changelogs/unreleased/bvl-subgroup-in-dropdowns.yml
deleted file mode 100644
index 1114d429dec..00000000000
--- a/changelogs/unreleased/bvl-subgroup-in-dropdowns.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure a user can add projects to subgroups they have access to
-merge_request: 15294
-author:
-type: fixed
diff --git a/changelogs/unreleased/cleanup-issues-schema.yml b/changelogs/unreleased/cleanup-issues-schema.yml
deleted file mode 100644
index 9f5fb0bdf82..00000000000
--- a/changelogs/unreleased/cleanup-issues-schema.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clean up schema of the "issues" table
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/commit-title-wrapping.yml b/changelogs/unreleased/commit-title-wrapping.yml
deleted file mode 100644
index 65c28b82b8b..00000000000
--- a/changelogs/unreleased/commit-title-wrapping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed long commit links not wrapping correctly
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/deploy-keys-loading-icon.yml b/changelogs/unreleased/deploy-keys-loading-icon.yml
deleted file mode 100644
index e3cb5bc6924..00000000000
--- a/changelogs/unreleased/deploy-keys-loading-icon.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed deploy keys remove button loading state not resetting
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml b/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml
deleted file mode 100644
index 1f8b42ea21f..00000000000
--- a/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make diff notes created on a commit in a merge request to persist a rebase.
-merge_request: 12148
-author:
-type: added
diff --git a/changelogs/unreleased/dm-diff-note-for-line-performance.yml b/changelogs/unreleased/dm-diff-note-for-line-performance.yml
new file mode 100644
index 00000000000..cbc418ab103
--- /dev/null
+++ b/changelogs/unreleased/dm-diff-note-for-line-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of MR discussions on large diffs
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/dm-image-blob-diff-full-url.yml b/changelogs/unreleased/dm-image-blob-diff-full-url.yml
deleted file mode 100644
index db44a5a16b5..00000000000
--- a/changelogs/unreleased/dm-image-blob-diff-full-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use app host instead of asset host when rendering image blob or diff
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-issue-move-transaction-error.yml b/changelogs/unreleased/dm-issue-move-transaction-error.yml
new file mode 100644
index 00000000000..0892169894a
--- /dev/null
+++ b/changelogs/unreleased/dm-issue-move-transaction-error.yml
@@ -0,0 +1,5 @@
+---
+title: Execute project hooks and services after commit when moving an issue
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-ldap-email-readonly.yml b/changelogs/unreleased/dm-ldap-email-readonly.yml
deleted file mode 100644
index 744b21f901e..00000000000
--- a/changelogs/unreleased/dm-ldap-email-readonly.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure user email is read only when synced with LDAP
-merge_request: 15915
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-search-pattern.yml b/changelogs/unreleased/dm-search-pattern.yml
deleted file mode 100644
index 1670d8c4b9a..00000000000
--- a/changelogs/unreleased/dm-search-pattern.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use fuzzy search with minimum length of 3 characters where appropriate
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/enable-scss-lint-unnecessary-mantissa.yml b/changelogs/unreleased/enable-scss-lint-unnecessary-mantissa.yml
deleted file mode 100644
index 1049e94f312..00000000000
--- a/changelogs/unreleased/enable-scss-lint-unnecessary-mantissa.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable UnnecessaryMantissa in scss-lint
-merge_request: 15255
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/feature-custom-text-for-new-projects.yml b/changelogs/unreleased/feature-custom-text-for-new-projects.yml
deleted file mode 100644
index 905d76dab33..00000000000
--- a/changelogs/unreleased/feature-custom-text-for-new-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add custom brand text on new project pages
-merge_request: 15541
-author: Markus Koller
-type: changed
diff --git a/changelogs/unreleased/feature-disable-password-authentication.yml b/changelogs/unreleased/feature-disable-password-authentication.yml
deleted file mode 100644
index 999203f12ce..00000000000
--- a/changelogs/unreleased/feature-disable-password-authentication.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow password authentication to be disabled entirely
-merge_request: 15223
-author: Markus Koller
-type: changed
diff --git a/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml b/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml
deleted file mode 100644
index ab85b8ee515..00000000000
--- a/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fail jobs if its dependency is missing
-merge_request: 14009
-author:
-type: fixed
diff --git a/changelogs/unreleased/feature_add_mermaid.yml b/changelogs/unreleased/feature_add_mermaid.yml
deleted file mode 100644
index caeb5d3470d..00000000000
--- a/changelogs/unreleased/feature_add_mermaid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Add support of Mermaid (generation of diagrams and flowcharts from text)'
-merge_request: 15107
-author: Vitaliy @blackst0ne Klachkov
-type: added
diff --git a/changelogs/unreleased/fix-event-target-author-preloading.yml b/changelogs/unreleased/fix-event-target-author-preloading.yml
deleted file mode 100644
index c6154cc0835..00000000000
--- a/changelogs/unreleased/fix-event-target-author-preloading.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix N+1 query when displaying events
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/fix-filter-by-my-reaction.yml b/changelogs/unreleased/fix-filter-by-my-reaction.yml
deleted file mode 100644
index 8bf91ddf893..00000000000
--- a/changelogs/unreleased/fix-filter-by-my-reaction.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix filter by my reaction is not working
-merge_request: 15345
-author: Hiroyuki Sato
-type: fixed
diff --git a/changelogs/unreleased/fix-last-push-event-widget-layout.yml b/changelogs/unreleased/fix-last-push-event-widget-layout.yml
new file mode 100644
index 00000000000..ba5b115ca19
--- /dev/null
+++ b/changelogs/unreleased/fix-last-push-event-widget-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Last push event widget width for fixed layout
+merge_request: 15862
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fix-new-project-guidelines-styling.yml b/changelogs/unreleased/fix-new-project-guidelines-styling.yml
deleted file mode 100644
index a97f5c485d4..00000000000
--- a/changelogs/unreleased/fix-new-project-guidelines-styling.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Markdown styling for new project guidelines
-merge_request: 15785
-author: Markus Koller
-type: fixed
diff --git a/changelogs/unreleased/fix-protected-branches-descriptions.yml b/changelogs/unreleased/fix-protected-branches-descriptions.yml
deleted file mode 100644
index 8e233d9defd..00000000000
--- a/changelogs/unreleased/fix-protected-branches-descriptions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clarify wording of protected branch settings for the default branch
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/fix-sm-31771-do-not-allow-jobs-to-be-erased-new.yml b/changelogs/unreleased/fix-sm-31771-do-not-allow-jobs-to-be-erased-new.yml
deleted file mode 100644
index 198116f34aa..00000000000
--- a/changelogs/unreleased/fix-sm-31771-do-not-allow-jobs-to-be-erased-new.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only owner or master can erase jobs
-merge_request: 15216
-author:
-type: changed
diff --git a/changelogs/unreleased/fix-sm-37991-avoid-deactivation-when-pipeline-schedules-execute-a-commit-includes-ci-skip.yml b/changelogs/unreleased/fix-sm-37991-avoid-deactivation-when-pipeline-schedules-execute-a-commit-includes-ci-skip.yml
deleted file mode 100644
index 4e525c875ca..00000000000
--- a/changelogs/unreleased/fix-sm-37991-avoid-deactivation-when-pipeline-schedules-execute-a-commit-includes-ci-skip.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Avoid deactivation when pipeline schedules execute a branch includes `[ci skip]`
- comment
-merge_request: 15405
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-todos-last-page.yml b/changelogs/unreleased/fix-todos-last-page.yml
deleted file mode 100644
index efcdbb75e6e..00000000000
--- a/changelogs/unreleased/fix-todos-last-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix access to the final page of todos
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml b/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml
new file mode 100644
index 00000000000..3fd8b0eb988
--- /dev/null
+++ b/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml
@@ -0,0 +1,5 @@
+---
+title: Normalizing Identity extern_uid when saving the record
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-40407-missing-order-paginate.yml b/changelogs/unreleased/fj-40407-missing-order-paginate.yml
deleted file mode 100644
index 27471dc2c52..00000000000
--- a/changelogs/unreleased/fj-40407-missing-order-paginate.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added default order to UsersFinder
-merge_request: 15679
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml b/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml
deleted file mode 100644
index cd7b87596e6..00000000000
--- a/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Using appropiate services in the API for managing forks
-merge_request: 15709
-author:
-type: fixed
diff --git a/changelogs/unreleased/fl-upgrade-svg.yml b/changelogs/unreleased/fl-upgrade-svg.yml
deleted file mode 100644
index caf73881d0f..00000000000
--- a/changelogs/unreleased/fl-upgrade-svg.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update svg external depencency
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/group-new-miletone-breadcrumb.yml b/changelogs/unreleased/group-new-miletone-breadcrumb.yml
deleted file mode 100644
index b82c5b604e8..00000000000
--- a/changelogs/unreleased/group-new-miletone-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed new group milestone breadcrumbs
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/hashed-storage-attachments-migration-path.yml b/changelogs/unreleased/hashed-storage-attachments-migration-path.yml
deleted file mode 100644
index 32535437046..00000000000
--- a/changelogs/unreleased/hashed-storage-attachments-migration-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hashed Storage migration script now supports migrating project attachments
-merge_request: 15352
-author:
-type: added
diff --git a/changelogs/unreleased/improved-changes-dropdown.yml b/changelogs/unreleased/improved-changes-dropdown.yml
deleted file mode 100644
index f305cbe573b..00000000000
--- a/changelogs/unreleased/improved-changes-dropdown.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improved diff changed files dropdown design
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/index-namespaces-lower-name.yml b/changelogs/unreleased/index-namespaces-lower-name.yml
new file mode 100644
index 00000000000..ef08b6d6755
--- /dev/null
+++ b/changelogs/unreleased/index-namespaces-lower-name.yml
@@ -0,0 +1,5 @@
+---
+title: Add index on namespaces lower(name) for UsersController#exists
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/issue_39238.yml b/changelogs/unreleased/issue_39238.yml
deleted file mode 100644
index 75a4969ca9e..00000000000
--- a/changelogs/unreleased/issue_39238.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix image diff notification email from showing wrong content
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/merge-request-lock-icon-size-fix.yml b/changelogs/unreleased/merge-request-lock-icon-size-fix.yml
deleted file mode 100644
index 09c059a3011..00000000000
--- a/changelogs/unreleased/merge-request-lock-icon-size-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed merge request lock icon size
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/merge-requests-schema-cleanup.yml b/changelogs/unreleased/merge-requests-schema-cleanup.yml
deleted file mode 100644
index ccce9b1436c..00000000000
--- a/changelogs/unreleased/merge-requests-schema-cleanup.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clean up schema of the "merge_requests" table
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/mk-add-old-attachments-to-uploads-table.yml b/changelogs/unreleased/mk-add-old-attachments-to-uploads-table.yml
deleted file mode 100644
index 499543ef883..00000000000
--- a/changelogs/unreleased/mk-add-old-attachments-to-uploads-table.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add untracked files to uploads table
-merge_request: 15270
-author:
-type: other
diff --git a/changelogs/unreleased/mk-add-user-rate-limits.yml b/changelogs/unreleased/mk-add-user-rate-limits.yml
deleted file mode 100644
index 512757da5fc..00000000000
--- a/changelogs/unreleased/mk-add-user-rate-limits.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Add anonymous rate limit per IP, and authenticated (web or API) rate limits
- per user
-merge_request: 14708
-author:
-type: added
diff --git a/changelogs/unreleased/mk-fix-schema-dump-of-untracked-files-for-uploads.yml b/changelogs/unreleased/mk-fix-schema-dump-of-untracked-files-for-uploads.yml
deleted file mode 100644
index 2691e85320c..00000000000
--- a/changelogs/unreleased/mk-fix-schema-dump-of-untracked-files-for-uploads.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix error during schema dump.
-merge_request: 15866
-author:
-type: fixed
diff --git a/changelogs/unreleased/optimize-projects-for-imported-projects.yml b/changelogs/unreleased/optimize-projects-for-imported-projects.yml
deleted file mode 100644
index 13186fa36d5..00000000000
--- a/changelogs/unreleased/optimize-projects-for-imported-projects.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: check the import_status field before doing SQL operations to check the import
- url
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml b/changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml
deleted file mode 100644
index 6b05713d1a1..00000000000
--- a/changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Stop sending milestone and labels data over the wire for MR widget requests
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/outdated-browser-position-fix.yml b/changelogs/unreleased/outdated-browser-position-fix.yml
deleted file mode 100644
index 801e45a28b3..00000000000
--- a/changelogs/unreleased/outdated-browser-position-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed outdated browser flash positioning
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/patch-24.yml b/changelogs/unreleased/patch-24.yml
deleted file mode 100644
index a670eb3ab56..00000000000
--- a/changelogs/unreleased/patch-24.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix graph notes number duplication.
-merge_request: 15696
-author: Vladislav Kaverin
-type: fixed
diff --git a/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml b/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml
new file mode 100644
index 00000000000..0cee0b634d6
--- /dev/null
+++ b/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric
+merge_request: 15881
+author:
+type: changed
diff --git a/changelogs/unreleased/pawel-update_prometheus_gem_to_well_tested_version.yml b/changelogs/unreleased/pawel-update_prometheus_gem_to_well_tested_version.yml
deleted file mode 100644
index a4133ae5cec..00000000000
--- a/changelogs/unreleased/pawel-update_prometheus_gem_to_well_tested_version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reenable Prometheus metrics, add more control over Prometheus method instrumentation
-merge_request: 15558
-author:
-type: fixed
diff --git a/changelogs/unreleased/perform-sql-matching-of-tags.yml b/changelogs/unreleased/perform-sql-matching-of-tags.yml
deleted file mode 100644
index 39f8a867a4d..00000000000
--- a/changelogs/unreleased/perform-sql-matching-of-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Perform SQL matching of Build&Runner tags to greatly speed-up job picking
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml b/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml
deleted file mode 100644
index b98573df303..00000000000
--- a/changelogs/unreleased/sh-remove-allocation-tracking-influxdb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove allocation tracking code from InfluxDB sampler for performance
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/skip_confirmation_user_API.yml b/changelogs/unreleased/skip_confirmation_user_API.yml
deleted file mode 100644
index 144ccd69e68..00000000000
--- a/changelogs/unreleased/skip_confirmation_user_API.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Add email confirmation parameters for user creation and update via API
-merge_request:
-author: Daniel Juarez
-type: added
-
-
diff --git a/changelogs/unreleased/text-utils.yml b/changelogs/unreleased/text-utils.yml
deleted file mode 100644
index b95bb82fe01..00000000000
--- a/changelogs/unreleased/text-utils.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Export text utils functions as es6 module and add tests
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/throttle-touching-of-objects.yml b/changelogs/unreleased/throttle-touching-of-objects.yml
deleted file mode 100644
index 0a57bea7c83..00000000000
--- a/changelogs/unreleased/throttle-touching-of-objects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Throttle the number of UPDATEs triggered by touch
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/tm-feature-list-runners-jobs-api.yml b/changelogs/unreleased/tm-feature-list-runners-jobs-api.yml
deleted file mode 100644
index d75a2b68c30..00000000000
--- a/changelogs/unreleased/tm-feature-list-runners-jobs-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: New API endpoint - list jobs for a specified runner
-merge_request: 15432
-author:
-type: added
diff --git a/changelogs/unreleased/tm-feature-namespace-by-id-api.yml b/changelogs/unreleased/tm-feature-namespace-by-id-api.yml
deleted file mode 100644
index bc4a8949d28..00000000000
--- a/changelogs/unreleased/tm-feature-namespace-by-id-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new API endpoint - get a namespace by ID
-merge_request: 15442
-author:
-type: added
diff --git a/changelogs/unreleased/update-emoji-digests-with-latest-from-gemojione.yml b/changelogs/unreleased/update-emoji-digests-with-latest-from-gemojione.yml
deleted file mode 100644
index e509a8df6bc..00000000000
--- a/changelogs/unreleased/update-emoji-digests-with-latest-from-gemojione.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Update emojis. Add :gay_pride_flag: and :speech_left:. Remove extraneous comma
- in :cartwheel_tone4:'
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/update-merge-worker-metrics.yml b/changelogs/unreleased/update-merge-worker-metrics.yml
deleted file mode 100644
index c733675926a..00000000000
--- a/changelogs/unreleased/update-merge-worker-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add performance logging to UpdateMergeRequestsWorker.
-merge_request: 15360
-author:
-type: performance
diff --git a/changelogs/unreleased/update_mr_changes_empty_page.yml b/changelogs/unreleased/update_mr_changes_empty_page.yml
deleted file mode 100644
index bae73c21e8f..00000000000
--- a/changelogs/unreleased/update_mr_changes_empty_page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update empty state page of merge request 'changes' tab
-merge_request: 15611
-author: Vitaliy @blackst0ne Klachkov
-type: added
diff --git a/changelogs/unreleased/use-count_commits-directly.yml b/changelogs/unreleased/use-count_commits-directly.yml
deleted file mode 100644
index 549e0744ea4..00000000000
--- a/changelogs/unreleased/use-count_commits-directly.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve the performance for counting commits
-merge_request: 15628
-author:
-type: performance
diff --git a/changelogs/unreleased/use-merge-requests-diff-id-column.yml b/changelogs/unreleased/use-merge-requests-diff-id-column.yml
deleted file mode 100644
index da4106ec8cf..00000000000
--- a/changelogs/unreleased/use-merge-requests-diff-id-column.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make finding most recent merge request diffs more efficient
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/user-agent-gke-api.yml b/changelogs/unreleased/user-agent-gke-api.yml
deleted file mode 100644
index 1abdbadd53b..00000000000
--- a/changelogs/unreleased/user-agent-gke-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use custom user agent header in all GCP API requests.
-merge_request: 15705
-author:
-type: changed
diff --git a/changelogs/unreleased/winh-subgroups-api.yml b/changelogs/unreleased/winh-subgroups-api.yml
deleted file mode 100644
index c49e3621e9c..00000000000
--- a/changelogs/unreleased/winh-subgroups-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add /groups/:id/subgroups endpoint to API
-merge_request: 15142
-author: marbemac
-type: added
diff --git a/changelogs/unreleased/zj-commit-show-n-1.yml b/changelogs/unreleased/zj-commit-show-n-1.yml
deleted file mode 100644
index e536434f74a..00000000000
--- a/changelogs/unreleased/zj-commit-show-n-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fetch blobs in bulk when generating diffs
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/zj-empty-repo-importer.yml b/changelogs/unreleased/zj-empty-repo-importer.yml
deleted file mode 100644
index 71d50af9a04..00000000000
--- a/changelogs/unreleased/zj-empty-repo-importer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix GitHub importer using removed interface
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/zj-memoization-mr-commits.yml b/changelogs/unreleased/zj-memoization-mr-commits.yml
deleted file mode 100644
index 59dfc6d6049..00000000000
--- a/changelogs/unreleased/zj-memoization-mr-commits.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache commits for MergeRequest diffs
-merge_request:
-author:
-type: performance
diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb
index bedd57ede04..cb611aa21df 100644
--- a/config/initializers/forbid_sidekiq_in_transactions.rb
+++ b/config/initializers/forbid_sidekiq_in_transactions.rb
@@ -1,5 +1,7 @@
module Sidekiq
module Worker
+ EnqueueFromTransactionError = Class.new(StandardError)
+
mattr_accessor :skip_transaction_check
self.skip_transaction_check = false
@@ -12,11 +14,11 @@ module Sidekiq
end
module ClassMethods
- module NoSchedulingFromTransactions
+ module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
- raise <<-MSG.strip_heredoc
+ raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
race conditions when the worker runs before the transaction is committed and
tries to access a model that has not been saved yet.
@@ -30,7 +32,7 @@ module Sidekiq
end
end
- prepend NoSchedulingFromTransactions
+ prepend NoEnqueueingFromTransactions
end
end
end
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index 1cff355346c..581397b26f8 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -18,7 +18,7 @@ Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::GC
Peek.into Peek::Views::Gitaly
-# rubocop:disable Style/ClassAndModuleCamelCase
+# rubocop:disable Naming/ClassAndModuleCamelCase
class PEEK_DB_CLIENT
class << self
attr_accessor :query_details
diff --git a/config/routes.rb b/config/routes.rb
index 016140e0ede..f162043dd5e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -43,6 +43,8 @@ Rails.application.routes.draw do
get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness'
post 'storage_check' => 'health#storage_check'
+ get 'ide' => 'ide#index'
+ get 'ide/*vueroute' => 'ide#index', format: false
resources :metrics, only: [:index]
mount Peek::Railtie => '/peek'
diff --git a/config/webpack.config.js b/config/webpack.config.js
index d8797bbf4d3..5f95255334c 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -70,7 +70,7 @@ var config = {
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
- repo: './repo/index.js',
+ ide: './ide/index.js',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
@@ -118,7 +118,10 @@ var config = {
},
{
test: /\_worker\.js$/,
- loader: 'worker-loader',
+ use: [
+ { loader: 'worker-loader' },
+ { loader: 'babel-loader' },
+ ],
},
{
test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
@@ -138,6 +141,7 @@ var config = {
],
noParse: [/monaco-editor\/\w+\/vs\//],
+ strictExportPresence: true,
},
plugins: [
@@ -176,8 +180,13 @@ var config = {
return chunk.name;
}
return chunk.mapModules((m) => {
- var chunkPath = m.request.split('!').pop();
- return path.relative(m.context, chunkPath);
+ const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
+ if (m.resource.indexOf(pagesBase) === 0) {
+ return path.relative(pagesBase, m.resource)
+ .replace(/\/index\.[a-z]+$/, '')
+ .replace(/\//g, '__');
+ }
+ return path.relative(m.context, m.resource);
}).join('_');
}),
@@ -204,7 +213,7 @@ var config = {
'pipelines',
'pipelines_details',
'registry_list',
- 'repo',
+ 'ide',
'schedule_form',
'schedules_index',
'sidebar',
diff --git a/db/migrate/20171212203433_create_clusters_applications_prometheus.rb b/db/migrate/20171212203433_create_clusters_applications_prometheus.rb
new file mode 100644
index 00000000000..dc2531d2691
--- /dev/null
+++ b/db/migrate/20171212203433_create_clusters_applications_prometheus.rb
@@ -0,0 +1,18 @@
+class CreateClustersApplicationsPrometheus < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :clusters_applications_prometheus do |t|
+ t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
+
+ t.integer :status, null: false
+ t.string :version, null: false
+
+ t.text :status_reason
+
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb b/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb
new file mode 100644
index 00000000000..7cf1d0cec68
--- /dev/null
+++ b/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb
@@ -0,0 +1,30 @@
+class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+ INDEX_NAME = 'index_on_namespaces_lower_name'
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ disable_statement_timeout
+ if Gitlab::Database.version.to_f >= 9.5
+ # Allow us to hot-patch the index manually ahead of the migration
+ execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
+ else
+ execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON namespaces (lower(name));"
+ end
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ disable_statement_timeout
+ if Gitlab::Database.version.to_f >= 9.2
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ else
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
+ end
+ end
+end
diff --git a/db/post_migrate/20171219121201_normalize_extern_uid_from_identities.rb b/db/post_migrate/20171219121201_normalize_extern_uid_from_identities.rb
new file mode 100644
index 00000000000..286721a0894
--- /dev/null
+++ b/db/post_migrate/20171219121201_normalize_extern_uid_from_identities.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class NormalizeExternUidFromIdentities < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'NormalizeLdapExternUidsRange'.freeze
+ DELAY_INTERVAL = 10.seconds
+
+ disable_ddl_transaction!
+
+ class Identity < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'identities'
+ end
+
+ def up
+ ldap_identities = Identity.where("provider like 'ldap%'")
+
+ if ldap_identities.any?
+ queue_background_migration_jobs_by_range_at_intervals(Identity, MIGRATION, DELAY_INTERVAL)
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2048c50f892..aa5db5da4f0 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: 20171213160445) do
+ActiveRecord::Schema.define(version: 20171220191323) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -568,6 +568,15 @@ ActiveRecord::Schema.define(version: 20171213160445) do
t.text "status_reason"
end
+ create_table "clusters_applications_prometheus", force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "status", null: false
+ t.string "version", null: false
+ t.text "status_reason"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ end
+
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png
index 11ce324f938..11ce324f938 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/gitlab_ou.png
Binary files differ
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif
index a6727a3d85f..a6727a3d85f 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/ldap_ou.gif
Binary files differ
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/img/user_auth.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/user_auth.gif
index 36e6085259f..36e6085259f 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/img/user_auth.gif
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/img/user_auth.gif
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
new file mode 100644
index 00000000000..9b9b8ca89e5
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -0,0 +1,267 @@
+# How to configure LDAP with GitLab CE
+
+> **[Article Type](../../../development/writing_documentation.html#types-of-technical-articles):** admin guide ||
+> **Level:** intermediary ||
+> **Author:** [Chris Wilson](https://gitlab.com/MrChrisW) ||
+> **Publication date:** 2017-05-03
+
+## Introduction
+
+Managing a large number of users in GitLab can become a burden for system administrators. As an organization grows so do user accounts. Keeping these user accounts in sync across multiple enterprise applications often becomes a time consuming task.
+
+In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems.
+
+GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](#group-syncing-ee) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab.
+
+## Getting started
+
+### Choosing an LDAP Server
+
+The main reason organizations choose to utilize a LDAP server is to keep the entire organization's user base consolidated into a central repository. Users can access multiple applications and systems across the IT environment using a single login. Because LDAP is an open, vendor-neutral, industry standard application protocol, the number of applications using LDAP authentication continues to increase.
+
+There are many commercial and open source [directory servers](https://en.wikipedia.org/wiki/Directory_service#LDAP_implementations) that support the LDAP protocol. Deciding on the right directory server highly depends on the existing IT environment in which the server will be integrated with.
+
+For example, [Active Directory](https://technet.microsoft.com/en-us/library/hh831484(v=ws.11).aspx) is generally favored in a primarily Windows environment, as this allows quick integration with existing services. Other popular directory services include:
+
+- [Oracle Internet Directory](http://www.oracle.com/technetwork/middleware/id-mgmt/overview/index-082035.html)
+- [OpenLDAP](http://www.openldap.org/)
+- [389 Directory](http://directory.fedoraproject.org/)
+- [OpenDJ](https://forgerock.org/opendj/)
+- [ApacheDS](https://directory.apache.org/)
+
+> GitLab uses the [Net::LDAP](https://rubygems.org/gems/net-ldap) library under the hood. This means it supports all [IETF](https://tools.ietf.org/html/rfc2251) compliant LDAPv3 servers.
+
+### Active Directory (AD)
+
+We won't cover the installation and configuration of Windows Server or Active Directory Domain Services in this tutorial. There are a number of resources online to guide you through this process:
+
+- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012 ](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
+
+- Install Active Directory Domain Services (AD DS) (_technet.microsoft.com_)- [Install Active Directory Domain Services](https://technet.microsoft.com/windows-server-docs/identity/ad-ds/deploy/install-active-directory-domain-services--level-100-#BKMK_PS)
+
+> **Shortcut:** You can quickly install AD DS via PowerShell using
+`Install-WindowsFeature AD-Domain-Services -IncludeManagementTools`
+
+### Creating an AD **OU** structure
+
+Configuring organizational units (**OU**s) is an important part of setting up Active Directory. **OU**s form the base for an entire organizational structure. Using GitLab as an example we have designed the **OU** structure below using the geographic **OU** model. In the Geographic Model we separate **OU**s for different geographic regions.
+
+| GitLab **OU** Design | GitLab AD Structure |
+| :----------------------------: | :------------------------------: |
+| ![GitLab OU Design][gitlab_ou] | ![GitLab AD Structure][ldap_ou] |
+
+[gitlab_ou]: img/gitlab_ou.png
+[ldap_ou]: img/ldap_ou.gif
+
+Using PowerShell you can output the **OU** structure as a table (_all names are examples only_):
+
+```ps
+Get-ADObject -LDAPFilter "(objectClass=*)" -SearchBase 'OU=GitLab INT,DC=GitLab,DC=org' -Properties CanonicalName | Format-Table Name,CanonicalName -A
+```
+
+```
+OU CanonicalName
+---- -------------
+GitLab INT GitLab.org/GitLab INT
+United States GitLab.org/GitLab INT/United States
+Developers GitLab.org/GitLab INT/United States/Developers
+Gary Johnson GitLab.org/GitLab INT/United States/Developers/Gary Johnson
+Ellis Matthews GitLab.org/GitLab INT/United States/Developers/Ellis Matthews
+William Collins GitLab.org/GitLab INT/United States/Developers/William Collins
+People Ops GitLab.org/GitLab INT/United States/People Ops
+Margaret Baker GitLab.org/GitLab INT/United States/People Ops/Margaret Baker
+Libby Hartzler GitLab.org/GitLab INT/United States/People Ops/Libby Hartzler
+Victoria Ryles GitLab.org/GitLab INT/United States/People Ops/Victoria Ryles
+The Netherlands GitLab.org/GitLab INT/The Netherlands
+Developers GitLab.org/GitLab INT/The Netherlands/Developers
+John Doe GitLab.org/GitLab INT/The Netherlands/Developers/John Doe
+Jon Mealy GitLab.org/GitLab INT/The Netherlands/Developers/Jon Mealy
+Jane Weingarten GitLab.org/GitLab INT/The Netherlands/Developers/Jane Weingarten
+Production GitLab.org/GitLab INT/The Netherlands/Production
+Sarah Konopka GitLab.org/GitLab INT/The Netherlands/Production/Sarah Konopka
+Cynthia Bruno GitLab.org/GitLab INT/The Netherlands/Production/Cynthia Bruno
+David George GitLab.org/GitLab INT/The Netherlands/Production/David George
+United Kingdom GitLab.org/GitLab INT/United Kingdom
+Developers GitLab.org/GitLab INT/United Kingdom/Developers
+Leroy Fox GitLab.org/GitLab INT/United Kingdom/Developers/Leroy Fox
+Christopher Alley GitLab.org/GitLab INT/United Kingdom/Developers/Christopher Alley
+Norris Morita GitLab.org/GitLab INT/United Kingdom/Developers/Norris Morita
+Support GitLab.org/GitLab INT/United Kingdom/Support
+Laura Stanley GitLab.org/GitLab INT/United Kingdom/Support/Laura Stanley
+Nikki Schuman GitLab.org/GitLab INT/United Kingdom/Support/Nikki Schuman
+Harriet Butcher GitLab.org/GitLab INT/United Kingdom/Support/Harriet Butcher
+Global Groups GitLab.org/GitLab INT/Global Groups
+DevelopersNL GitLab.org/GitLab INT/Global Groups/DevelopersNL
+DevelopersUK GitLab.org/GitLab INT/Global Groups/DevelopersUK
+DevelopersUS GitLab.org/GitLab INT/Global Groups/DevelopersUS
+ProductionNL GitLab.org/GitLab INT/Global Groups/ProductionNL
+SupportUK GitLab.org/GitLab INT/Global Groups/SupportUK
+People Ops US GitLab.org/GitLab INT/Global Groups/People Ops US
+Global Admins GitLab.org/GitLab INT/Global Groups/Global Admins
+```
+
+> See [more information](https://technet.microsoft.com/en-us/library/ff730967.aspx) on searching Active Directory with Windows PowerShell from [The Scripting Guys](https://technet.microsoft.com/en-us/scriptcenter/dd901334.aspx)
+
+## GitLab LDAP configuration
+
+The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` configuration file. Below is an example of a complete configuration using an Active Directory.
+
+The two Active Directory specific values are `active_directory: true` and `uid: 'sAMAccountName'`. `sAMAccountName` is an attribute returned by Active Directory used for GitLab usernames. See the example output from `ldapsearch` for a full list of attributes a "person" object (user) has in **AD** - [`ldapsearch` example](#using-ldapsearch-unix)
+
+> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](#gitlab-enterprise-edition---ldap-features)
+
+### Example `gitlab.rb` LDAP
+
+```
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = {
+'main' => {
+ 'label' => 'GitLab AD',
+ 'host' => 'ad.example.org',
+ 'port' => 636,
+ 'uid' => 'sAMAccountName',
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=org',
+ 'password' => 'Password1',
+ 'active_directory' => true,
+ 'base' => 'OU=GitLab INT,DC=GitLab,DC=org',
+ 'group_base' => 'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org',
+ 'admin_group' => 'Global Admins'
+ }
+}
+```
+
+> **Note:** Remember to run `gitlab-ctl reconfigure` after modifying `gitlab.rb`
+
+## Security improvements (LDAPS)
+
+Security is an important aspect when deploying an LDAP server. By default, LDAP traffic is transmitted unsecured. LDAP can be secured using SSL/TLS called LDAPS, or commonly "LDAP over SSL".
+
+Securing LDAP (enabling LDAPS) on Windows Server 2012 involves installing a valid SSL certificate. For full details see Microsoft's guide [How to enable LDAP over SSL with a third-party certification authority](https://support.microsoft.com/en-us/help/321051/how-to-enable-ldap-over-ssl-with-a-third-party-certification-authority)
+
+> By default a LDAP service listens for connections on TCP and UDP port 389. LDAPS (LDAP over SSL) listens on port 636
+
+### Testing you AD server
+
+#### Using **AdFind** (Windows)
+
+You can use the [`AdFind`](https://social.technet.microsoft.com/wiki/contents/articles/7535.adfind-command-examples.aspx) utility (on Windows based systems) to test that your LDAP server is accessible and authentication is working correctly. This is a freeware utility built by [Joe Richards](http://www.joeware.net/freetools/tools/adfind/index.htm).
+
+**Return all objects**
+
+You can use the filter `objectclass=*` to return all directory objects.
+
+```sh
+adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f (objectClass=*)
+```
+
+**Return single object using filter**
+
+You can also retrieve a single object by **specifying** the object name or full **DN**. In this example we specify the object name only `CN=Leroy Fox`.
+
+```sh
+adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f (&(objectcategory=person)(CN=Leroy Fox))”
+```
+
+#### Using **ldapsearch** (Unix)
+
+You can use the `ldapsearch` utility (on Unix based systems) to test that your LDAP server is accessible and authentication is working correctly. This utility is included in the [`ldap-utils`](https://wiki.debian.org/LDAP/LDAPUtils) package.
+
+**Return all objects**
+
+You can use the filter `objectclass=*` to return all directory objects.
+
+```sh
+ldapsearch -D "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" \
+-w Password1 -p 636 -h ad.example.org \
+-b "OU=GitLab INT,DC=GitLab,DC=org" -Z \
+-s sub "(objectclass=*)"
+```
+
+**Return single object using filter**
+
+You can also retrieve a single object by **specifying** the object name or full **DN**. In this example we specify the object name only `CN=Leroy Fox`.
+
+```sh
+ldapsearch -D "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -w Password1 -p 389 -h ad.example.org -b "OU=GitLab INT,DC=GitLab,DC=org" -Z -s sub "CN=Leroy Fox"
+```
+
+**Full output of `ldapsearch` command:** - Filtering for _CN=Leroy Fox_
+
+```
+# LDAPv3
+# base <OU=GitLab INT,DC=GitLab,DC=org> with scope subtree
+# filter: CN=Leroy Fox
+# requesting: ALL
+#
+
+# Leroy Fox, Developers, United Kingdom, GitLab INT, GitLab.org
+dn: CN=Leroy Fox,OU=Developers,OU=United Kingdom,OU=GitLab INT,DC=GitLab,DC=or
+ g
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: Leroy Fox
+sn: Fox
+givenName: Leroy
+distinguishedName: CN=Leroy Fox,OU=Developers,OU=United Kingdom,OU=GitLab INT,
+ DC=GitLab,DC=org
+instanceType: 4
+whenCreated: 20170210030500.0Z
+whenChanged: 20170213050128.0Z
+displayName: Leroy Fox
+uSNCreated: 16790
+memberOf: CN=DevelopersUK,OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org
+uSNChanged: 20812
+name: Leroy Fox
+objectGUID:: rBCAo6NR6E6vfSKgzcUILg==
+userAccountControl: 512
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 131311695009850084
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAA9GMAb7tdJZvsATf7ZwQAAA==
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: Leroyf
+sAMAccountType: 805306368
+userPrincipalName: Leroyf@GitLab.org
+objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=GitLab,DC=org
+dSCorePropagationData: 16010101000000.0Z
+lastLogonTimestamp: 131314356887754250
+
+# search result
+search: 2
+result: 0 Success
+
+# numResponses: 2
+# numEntries: 1
+```
+
+## Basic user authentication
+
+After configuring LDAP, basic authentication will be available. Users can then login using their directory credentials. An extra tab is added to the GitLab login screen for the configured LDAP server (e.g "**GitLab AD**").
+
+![GitLab OU Structure](img/user_auth.gif)
+
+Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../ldap.md#security) on LDAP security.
+
+If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
+
+## LDAP extended features on GitLab EE
+
+With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/), besides everything we just described, you'll
+have extended functionalities with LDAP, such as:
+
+- Group sync
+- Group permissions
+- Updating user permissions
+- Multiple LDAP servers
+
+Read through the article on [LDAP for GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/) for an overview.
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 6b5a0f139c5..881b6a827f4 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -38,6 +38,9 @@ in the application settings.
## Configuration
+For a complete guide on configuring LDAP with GitLab Community Edition, please check
+the admin guide [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md).
+
To enable LDAP integration you need to add your LDAP server settings in
`/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 5a4a8d888b3..c9b72d4a1dd 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -84,6 +84,7 @@ POST /projects/:id/repository/commits
| `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb` |
| `content` | string | no | File content, required for all except `delete`. Optional for `move` |
| `encoding` | string | no | `text` or `base64`. `text` is default. |
+| `last_commit_id` | string | no | Last known file commit id. Will be only considered in update, move and delete actions. |
```bash
PAYLOAD=$(cat << 'JSON'
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index c517a38a8ba..a1a0b1b756c 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -151,3 +151,4 @@ Parameters:
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message
+- `last_commit_id` (optional) - Last known file commit id
diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md
index c64851bad2b..6a590b53727 100644
--- a/doc/articles/artifactory_and_gitlab/index.md
+++ b/doc/articles/artifactory_and_gitlab/index.md
@@ -1,278 +1 @@
-# How to deploy Maven projects to Artifactory with GitLab CI/CD
-
-> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
-> **Level:** intermediary ||
-> **Author:** [Fabio Busatto](https://gitlab.com/bikebilly) ||
-> **Publication date:** 2017-08-15
-
-## Introduction
-
-In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/)
-to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://www.jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
-
-You'll create two different projects:
-
-- `simple-maven-dep`: the app built and deployed to Artifactory (available at https://gitlab.com/gitlab-examples/maven/simple-maven-dep)
-- `simple-maven-app`: the app using the previous one as a dependency (available at https://gitlab.com/gitlab-examples/maven/simple-maven-app)
-
-We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/).
-We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
-
-## Create the simple Maven dependency
-
-First of all, you need an application to work with: in this specific case we will
-use a simple one, but it could be any Maven application. This will be the
-dependency you want to package and deploy to Artifactory, in order to be
-available to other projects.
-
-### Prepare the dependency application
-
-For this article you'll use a Maven app that can be cloned from our example
-project:
-
-1. Log in to your GitLab account
-1. Create a new project by selecting **Import project from ➔ Repo by URL**
-1. Add the following URL:
-
- ```
- https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
- ```
-1. Click **Create project**
-
-This application is nothing more than a basic class with a stub for a JUnit based test suite.
-It exposes a method called `hello` that accepts a string as input, and prints a hello message on the screen.
-
-The project structure is really simple, and you should consider these two resources:
-
-- `pom.xml`: project object model (POM) configuration file
-- `src/main/java/com/example/dep/Dep.java`: source of our application
-
-### Configure the Artifactory deployment
-
-The application is ready to use, but you need some additional steps to deploy it to Artifactory:
-
-1. Log in to Artifactory with your user's credentials.
-1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel.
-1. Copy to clipboard the configuration snippet under the **Deploy** paragraph.
-1. Change the `url` value in order to have it configurable via secret variables.
-1. Copy the snippet in the `pom.xml` file for your project, just after the
- `dependencies` section. The snippet should look like this:
-
- ```xml
- <distributionManagement>
- <repository>
- <id>central</id>
- <name>83d43b5afeb5-releases</name>
- <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
- </repository>
- </distributionManagement>
- ```
-
-Another step you need to do before you can deploy the dependency to Artifactory
-is to configure the authentication data. It is a simple task, but Maven requires
-it to stay in a file called `settings.xml` that has to be in the `.m2` subdirectory
-in the user's homedir.
-
-Since you want to use GitLab Runner to automatically deploy the application, you
-should create the file in the project's home directory and set a command line
-parameter in `.gitlab-ci.yml` to use the custom location instead of the default one:
-
-1. Create a folder called `.m2` in the root of your repository
-1. Create a file called `settings.xml` in the `.m2` folder
-1. Copy the following content into a `settings.xml` file:
-
- ```xml
- <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
- xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <servers>
- <server>
- <id>central</id>
- <username>${env.MAVEN_REPO_USER}</username>
- <password>${env.MAVEN_REPO_PASS}</password>
- </server>
- </servers>
- </settings>
- ```
-
- Username and password will be replaced by the correct values using secret variables.
-
-### Configure GitLab CI/CD for `simple-maven-dep`
-
-Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and deploy the dependency!
-
-GitLab CI/CD uses a file in the root of the repo, named `.gitlab-ci.yml`, to read the definitions for jobs
-that will be executed by the configured GitLab Runners. You can read more about this file in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/).
-
-First of all, remember to set up secret variables for your deployment. Navigate to your project's **Settings > CI/CD** page
-and add the following secret variables (replace them with your current values, of course):
-
-- **MAVEN_REPO_URL**: `http://artifactory.example.com:8081/artifactory` (your Artifactory URL)
-- **MAVEN_REPO_USER**: `gitlab` (your Artifactory username)
-- **MAVEN_REPO_PASS**: `AKCp2WXr3G61Xjz1PLmYa3arm3yfBozPxSta4taP3SeNu2HPXYa7FhNYosnndFNNgoEds8BCS` (your Artifactory Encrypted Password)
-
-Now it's time to define jobs in `.gitlab-ci.yml` and push it to the repo:
-
-```yaml
-image: maven:latest
-
-variables:
- MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
-
-cache:
- paths:
- - .m2/repository/
- - target/
-
-build:
- stage: build
- script:
- - mvn $MAVEN_CLI_OPTS compile
-
-test:
- stage: test
- script:
- - mvn $MAVEN_CLI_OPTS test
-
-deploy:
- stage: deploy
- script:
- - mvn $MAVEN_CLI_OPTS deploy
- only:
- - master
-```
-
-GitLab Runner will use the latest [Maven Docker image](https://hub.docker.com/_/maven/), which already contains all the tools and the dependencies you need to manage the project,
-in order to run the jobs.
-
-Environment variables are set to instruct Maven to use the `homedir` of the repo instead of the user's home when searching for configuration and dependencies.
-
-Caching the `.m2/repository folder` (where all the Maven files are stored), and the `target` folder (where our application will be created), is useful for speeding up the process
-by running all Maven phases in a sequential order, therefore, executing `mvn test` will automatically run `mvn compile` if necessary.
-
-Both `build` and `test` jobs leverage the `mvn` command to compile the application and to test it as defined in the test suite that is part of the application.
-
-Deploy to Artifactory is done as defined by the secret variables we have just set up.
-The deployment occurs only if we're pushing or merging to `master` branch, so that the development versions are tested but not published.
-
-Done! Now you have all the changes in the GitLab repo, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
-If the deployment has been successful, the deploy job log will output:
-
-```
-[INFO] ------------------------------------------------------------------------
-[INFO] BUILD SUCCESS
-[INFO] ------------------------------------------------------------------------
-[INFO] Total time: 1.983 s
-```
-
->**Note**:
-the `mvn` command downloads a lot of files from the internet, so you'll see a lot of extra activity in the log the first time you run it.
-
-Yay! You did it! Checking in Artifactory will confirm that you have a new artifact available in the `libs-release-local` repo.
-
-## Create the main Maven application
-
-Now that you have the dependency available on Artifactory, it's time to use it!
-Let's see how we can have it as a dependency to our main application.
-
-### Prepare the main application
-
-We'll use again a Maven app that can be cloned from our example project:
-
-1. Create a new project by selecting **Import project from ➔ Repo by URL**
-1. Add the following URL:
-
- ```
- https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
- ```
-1. Click **Create project**
-
-This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
-file you can see that it imports the `com.example.dep.Dep` class and calls the `hello` method passing `GitLab` as a parameter.
-
-Since Maven doesn't know how to resolve the dependency, you need to modify the configuration:
-
-1. Go back to Artifactory
-1. Browse the `libs-release-local` repository
-1. Select the `simple-maven-dep-1.0.jar` file
-1. Find the configuration snippet from the **Dependency Declaration** section of the main panel
-1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
- The snippet should look like this:
-
- ```xml
- <dependency>
- <groupId>com.example.dep</groupId>
- <artifactId>simple-maven-dep</artifactId>
- <version>1.0</version>
- </dependency>
- ```
-
-### Configure the Artifactory repository location
-
-At this point you defined the dependency for the application, but you still miss where you can find the required files.
-You need to create a `.m2/settings.xml` file as you did for the dependency project, and let Maven know the location using environment variables.
-
-Here is how you can get the content of the file directly from Artifactory:
-
-1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel
-1. Click on **Generate Maven Settings**
-1. Click on **Generate Settings**
-1. Copy to clipboard the configuration file
-1. Save the file as `.m2/settings.xml` in your repo
-
-Now you are ready to use the Artifactory repository to resolve dependencies and use `simple-maven-dep` in your main application!
-
-### Configure GitLab CI/CD for `simple-maven-app`
-
-You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
-
-You want to leverage [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and run your awesome application,
-and see if you can get the greeting as expected!
-
-All you need to do is to add the following `.gitlab-ci.yml` to the repo:
-
-```yaml
-image: maven:latest
-
-stages:
- - build
- - test
- - run
-
-variables:
- MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
-
-cache:
- paths:
- - .m2/repository/
- - target/
-
-build:
- stage: build
- script:
- - mvn $MAVEN_CLI_OPTS compile
-
-test:
- stage: test
- script:
- - mvn $MAVEN_CLI_OPTS test
-
-run:
- stage: run
- script:
- - mvn $MAVEN_CLI_OPTS package
- - mvn $MAVEN_CLI_OPTS exec:java -Dexec.mainClass="com.example.app.App"
-```
-
-It is very similar to the configuration used for `simple-maven-dep`, but instead of the `deploy` job there is a `run` job.
-Probably something that you don't want to use in real projects, but here it is useful to see the application executed automatically.
-
-And that's it! In the `run` job output log you will find a friendly hello to GitLab!
-
-## Conclusion
-
-In this article we covered the basic steps to use an Artifactory Maven repository to automatically publish and consume artifacts.
-
-A similar approach could be used to interact with any other Maven compatible Binary Repository Manager.
-Obviously, you can improve these examples, optimizing the `.gitlab-ci.yml` file to better suit your needs, and adapting to your workflow.
+This document was moved to [another location](../../ci/examples/artifactory_and_gitlab/index.md)
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index 25a24bc1d32..a8320c12e6b 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -1,267 +1 @@
-# How to configure LDAP with GitLab CE
-
-> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** admin guide ||
-> **Level:** intermediary ||
-> **Author:** [Chris Wilson](https://gitlab.com/MrChrisW) ||
-> **Publication date:** 2017-05-03
-
-## Introduction
-
-Managing a large number of users in GitLab can become a burden for system administrators. As an organization grows so do user accounts. Keeping these user accounts in sync across multiple enterprise applications often becomes a time consuming task.
-
-In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems.
-
-GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](#group-syncing-ee) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab.
-
-## Getting started
-
-### Choosing an LDAP Server
-
-The main reason organizations choose to utilize a LDAP server is to keep the entire organization's user base consolidated into a central repository. Users can access multiple applications and systems across the IT environment using a single login. Because LDAP is an open, vendor-neutral, industry standard application protocol, the number of applications using LDAP authentication continues to increase.
-
-There are many commercial and open source [directory servers](https://en.wikipedia.org/wiki/Directory_service#LDAP_implementations) that support the LDAP protocol. Deciding on the right directory server highly depends on the existing IT environment in which the server will be integrated with.
-
-For example, [Active Directory](https://technet.microsoft.com/en-us/library/hh831484(v=ws.11).aspx) is generally favored in a primarily Windows environment, as this allows quick integration with existing services. Other popular directory services include:
-
-- [Oracle Internet Directory](http://www.oracle.com/technetwork/middleware/id-mgmt/overview/index-082035.html)
-- [OpenLDAP](http://www.openldap.org/)
-- [389 Directory](http://directory.fedoraproject.org/)
-- [OpenDJ](https://forgerock.org/opendj/)
-- [ApacheDS](https://directory.apache.org/)
-
-> GitLab uses the [Net::LDAP](https://rubygems.org/gems/net-ldap) library under the hood. This means it supports all [IETF](https://tools.ietf.org/html/rfc2251) compliant LDAPv3 servers.
-
-### Active Directory (AD)
-
-We won't cover the installation and configuration of Windows Server or Active Directory Domain Services in this tutorial. There are a number of resources online to guide you through this process:
-
-- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012 ](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
-
-- Install Active Directory Domain Services (AD DS) (_technet.microsoft.com_)- [Install Active Directory Domain Services](https://technet.microsoft.com/windows-server-docs/identity/ad-ds/deploy/install-active-directory-domain-services--level-100-#BKMK_PS)
-
-> **Shortcut:** You can quickly install AD DS via PowerShell using
-`Install-WindowsFeature AD-Domain-Services -IncludeManagementTools`
-
-### Creating an AD **OU** structure
-
-Configuring organizational units (**OU**s) is an important part of setting up Active Directory. **OU**s form the base for an entire organizational structure. Using GitLab as an example we have designed the **OU** structure below using the geographic **OU** model. In the Geographic Model we separate **OU**s for different geographic regions.
-
-| GitLab **OU** Design | GitLab AD Structure |
-| :----------------------------: | :------------------------------: |
-| ![GitLab OU Design][gitlab_ou] | ![GitLab AD Structure][ldap_ou] |
-
-[gitlab_ou]: img/gitlab_ou.png
-[ldap_ou]: img/ldap_ou.gif
-
-Using PowerShell you can output the **OU** structure as a table (_all names are examples only_):
-
-```ps
-Get-ADObject -LDAPFilter "(objectClass=*)" -SearchBase 'OU=GitLab INT,DC=GitLab,DC=org' -Properties CanonicalName | Format-Table Name,CanonicalName -A
-```
-
-```
-OU CanonicalName
----- -------------
-GitLab INT GitLab.org/GitLab INT
-United States GitLab.org/GitLab INT/United States
-Developers GitLab.org/GitLab INT/United States/Developers
-Gary Johnson GitLab.org/GitLab INT/United States/Developers/Gary Johnson
-Ellis Matthews GitLab.org/GitLab INT/United States/Developers/Ellis Matthews
-William Collins GitLab.org/GitLab INT/United States/Developers/William Collins
-People Ops GitLab.org/GitLab INT/United States/People Ops
-Margaret Baker GitLab.org/GitLab INT/United States/People Ops/Margaret Baker
-Libby Hartzler GitLab.org/GitLab INT/United States/People Ops/Libby Hartzler
-Victoria Ryles GitLab.org/GitLab INT/United States/People Ops/Victoria Ryles
-The Netherlands GitLab.org/GitLab INT/The Netherlands
-Developers GitLab.org/GitLab INT/The Netherlands/Developers
-John Doe GitLab.org/GitLab INT/The Netherlands/Developers/John Doe
-Jon Mealy GitLab.org/GitLab INT/The Netherlands/Developers/Jon Mealy
-Jane Weingarten GitLab.org/GitLab INT/The Netherlands/Developers/Jane Weingarten
-Production GitLab.org/GitLab INT/The Netherlands/Production
-Sarah Konopka GitLab.org/GitLab INT/The Netherlands/Production/Sarah Konopka
-Cynthia Bruno GitLab.org/GitLab INT/The Netherlands/Production/Cynthia Bruno
-David George GitLab.org/GitLab INT/The Netherlands/Production/David George
-United Kingdom GitLab.org/GitLab INT/United Kingdom
-Developers GitLab.org/GitLab INT/United Kingdom/Developers
-Leroy Fox GitLab.org/GitLab INT/United Kingdom/Developers/Leroy Fox
-Christopher Alley GitLab.org/GitLab INT/United Kingdom/Developers/Christopher Alley
-Norris Morita GitLab.org/GitLab INT/United Kingdom/Developers/Norris Morita
-Support GitLab.org/GitLab INT/United Kingdom/Support
-Laura Stanley GitLab.org/GitLab INT/United Kingdom/Support/Laura Stanley
-Nikki Schuman GitLab.org/GitLab INT/United Kingdom/Support/Nikki Schuman
-Harriet Butcher GitLab.org/GitLab INT/United Kingdom/Support/Harriet Butcher
-Global Groups GitLab.org/GitLab INT/Global Groups
-DevelopersNL GitLab.org/GitLab INT/Global Groups/DevelopersNL
-DevelopersUK GitLab.org/GitLab INT/Global Groups/DevelopersUK
-DevelopersUS GitLab.org/GitLab INT/Global Groups/DevelopersUS
-ProductionNL GitLab.org/GitLab INT/Global Groups/ProductionNL
-SupportUK GitLab.org/GitLab INT/Global Groups/SupportUK
-People Ops US GitLab.org/GitLab INT/Global Groups/People Ops US
-Global Admins GitLab.org/GitLab INT/Global Groups/Global Admins
-```
-
-> See [more information](https://technet.microsoft.com/en-us/library/ff730967.aspx) on searching Active Directory with Windows PowerShell from [The Scripting Guys](https://technet.microsoft.com/en-us/scriptcenter/dd901334.aspx)
-
-## GitLab LDAP configuration
-
-The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` configuration file. Below is an example of a complete configuration using an Active Directory.
-
-The two Active Directory specific values are `active_directory: true` and `uid: 'sAMAccountName'`. `sAMAccountName` is an attribute returned by Active Directory used for GitLab usernames. See the example output from `ldapsearch` for a full list of attributes a "person" object (user) has in **AD** - [`ldapsearch` example](#using-ldapsearch-unix)
-
-> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](#gitlab-enterprise-edition---ldap-features)
-
-### Example `gitlab.rb` LDAP
-
-```
-gitlab_rails['ldap_enabled'] = true
-gitlab_rails['ldap_servers'] = {
-'main' => {
- 'label' => 'GitLab AD',
- 'host' => 'ad.example.org',
- 'port' => 636,
- 'uid' => 'sAMAccountName',
- 'encryption' => 'simple_tls',
- 'verify_certificates' => true,
- 'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=org',
- 'password' => 'Password1',
- 'active_directory' => true,
- 'base' => 'OU=GitLab INT,DC=GitLab,DC=org',
- 'group_base' => 'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org',
- 'admin_group' => 'Global Admins'
- }
-}
-```
-
-> **Note:** Remember to run `gitlab-ctl reconfigure` after modifying `gitlab.rb`
-
-## Security improvements (LDAPS)
-
-Security is an important aspect when deploying an LDAP server. By default, LDAP traffic is transmitted unsecured. LDAP can be secured using SSL/TLS called LDAPS, or commonly "LDAP over SSL".
-
-Securing LDAP (enabling LDAPS) on Windows Server 2012 involves installing a valid SSL certificate. For full details see Microsoft's guide [How to enable LDAP over SSL with a third-party certification authority](https://support.microsoft.com/en-us/help/321051/how-to-enable-ldap-over-ssl-with-a-third-party-certification-authority)
-
-> By default a LDAP service listens for connections on TCP and UDP port 389. LDAPS (LDAP over SSL) listens on port 636
-
-### Testing you AD server
-
-#### Using **AdFind** (Windows)
-
-You can use the [`AdFind`](https://social.technet.microsoft.com/wiki/contents/articles/7535.adfind-command-examples.aspx) utility (on Windows based systems) to test that your LDAP server is accessible and authentication is working correctly. This is a freeware utility built by [Joe Richards](http://www.joeware.net/freetools/tools/adfind/index.htm).
-
-**Return all objects**
-
-You can use the filter `objectclass=*` to return all directory objects.
-
-```sh
-adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f (objectClass=*)
-```
-
-**Return single object using filter**
-
-You can also retrieve a single object by **specifying** the object name or full **DN**. In this example we specify the object name only `CN=Leroy Fox`.
-
-```sh
-adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f (&(objectcategory=person)(CN=Leroy Fox))”
-```
-
-#### Using **ldapsearch** (Unix)
-
-You can use the `ldapsearch` utility (on Unix based systems) to test that your LDAP server is accessible and authentication is working correctly. This utility is included in the [`ldap-utils`](https://wiki.debian.org/LDAP/LDAPUtils) package.
-
-**Return all objects**
-
-You can use the filter `objectclass=*` to return all directory objects.
-
-```sh
-ldapsearch -D "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" \
--w Password1 -p 636 -h ad.example.org \
--b "OU=GitLab INT,DC=GitLab,DC=org" -Z \
--s sub "(objectclass=*)"
-```
-
-**Return single object using filter**
-
-You can also retrieve a single object by **specifying** the object name or full **DN**. In this example we specify the object name only `CN=Leroy Fox`.
-
-```sh
-ldapsearch -D "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -w Password1 -p 389 -h ad.example.org -b "OU=GitLab INT,DC=GitLab,DC=org" -Z -s sub "CN=Leroy Fox"
-```
-
-**Full output of `ldapsearch` command:** - Filtering for _CN=Leroy Fox_
-
-```
-# LDAPv3
-# base <OU=GitLab INT,DC=GitLab,DC=org> with scope subtree
-# filter: CN=Leroy Fox
-# requesting: ALL
-#
-
-# Leroy Fox, Developers, United Kingdom, GitLab INT, GitLab.org
-dn: CN=Leroy Fox,OU=Developers,OU=United Kingdom,OU=GitLab INT,DC=GitLab,DC=or
- g
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-cn: Leroy Fox
-sn: Fox
-givenName: Leroy
-distinguishedName: CN=Leroy Fox,OU=Developers,OU=United Kingdom,OU=GitLab INT,
- DC=GitLab,DC=org
-instanceType: 4
-whenCreated: 20170210030500.0Z
-whenChanged: 20170213050128.0Z
-displayName: Leroy Fox
-uSNCreated: 16790
-memberOf: CN=DevelopersUK,OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org
-uSNChanged: 20812
-name: Leroy Fox
-objectGUID:: rBCAo6NR6E6vfSKgzcUILg==
-userAccountControl: 512
-badPwdCount: 0
-codePage: 0
-countryCode: 0
-badPasswordTime: 0
-lastLogoff: 0
-lastLogon: 0
-pwdLastSet: 131311695009850084
-primaryGroupID: 513
-objectSid:: AQUAAAAAAAUVAAAA9GMAb7tdJZvsATf7ZwQAAA==
-accountExpires: 9223372036854775807
-logonCount: 0
-sAMAccountName: Leroyf
-sAMAccountType: 805306368
-userPrincipalName: Leroyf@GitLab.org
-objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=GitLab,DC=org
-dSCorePropagationData: 16010101000000.0Z
-lastLogonTimestamp: 131314356887754250
-
-# search result
-search: 2
-result: 0 Success
-
-# numResponses: 2
-# numEntries: 1
-```
-
-## Basic user authentication
-
-After configuring LDAP, basic authentication will be available. Users can then login using their directory credentials. An extra tab is added to the GitLab login screen for the configured LDAP server (e.g "**GitLab AD**").
-
-![GitLab OU Structure](img/user_auth.gif)
-
-Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../../administration/auth/ldap.md#security) on LDAP security.
-
-If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
-
-## LDAP extended features on GitLab EE
-
-With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/), besides everything we just described, you'll
-have extended functionalities with LDAP, such as:
-
-- Group sync
-- Group permissions
-- Updating user permissions
-- Multiple LDAP servers
-
-Read through the article on [LDAP for GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) for an overview.
+This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md).
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 862fe0868a6..06675e15d76 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -10,16 +10,6 @@ They are written by members of the GitLab Team and by
Part of the articles listed below link to the [GitLab Blog](https://about.gitlab.com/blog/),
where they were originally published.
-## Authentication
-
-Explore GitLab's supported [authentications methods](../topics/authentication/index.md):
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| **LDAP** |
-| [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)| Admin guide | 2017-05-03 |
-| [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) | Admin guide | 2017-05-03 |
-
## Build, test, and deploy with GitLab CI/CD
Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/README.md):
@@ -28,7 +18,6 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| :------------ | :------: | --------------: |
| [Autoscaling GitLab Runners on AWS](runner_autoscale_aws/index.md) | Admin guide | 2017-11-24 |
| [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md) | Tutorial | 2017-08-31 |
-| [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017-07-27 |
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 12404eddbe2..05d792dea0f 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -121,7 +121,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
- **Articles**
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](../articles/laravel_with_gitlab_and_envoy/index.md)
- - [How to deploy Maven projects to Artifactory with GitLab CI/CD](../articles/artifactory_and_gitlab/index.md)
+ - [How to deploy Maven projects to Artifactory with GitLab CI/CD](examples/artifactory_and_gitlab/index.md)
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 0a2419b7ed2..22afcb9199d 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -164,6 +164,21 @@ not without its own challenges:
- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest
form offered. To use a different driver, see
[Using the overlayfs driver](#using-the-overlayfs-driver).
+- Since the `docker:dind` container and the runner container don't share their
+ root filesystem, the job's working directory can be used as a mount point for
+ children containers. For example, if you have files you want to share with a
+ child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH`
+ and use it as your mount point (for a more thorough explanation, check [issue
+ #41227](https://gitlab.com/gitlab-org/gitlab-ce/issues/41227)):
+
+ ```yaml
+ variables:
+ MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt
+
+ script:
+ - mkdir -p "$MOUNT_POINT"
+ - docker run -v "$MOUNT_POINT:/mnt" my-docker-image
+ ```
An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index d05b4db953a..25a0c5dcff5 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -27,8 +27,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
### Java
-- **Articles:**
- - [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
+- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
### Scala
@@ -41,18 +40,15 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
### Elixir
- [Test a Phoenix application](test-phoenix-application.md)
-- **Articles:**
- - [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
+- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
### iOS
-- **Articles:**
- - [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
### Android
-- **Articles:**
- - [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
+- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
### Code quality analysis
@@ -63,20 +59,19 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Using `dpl` as deployment tool](deployment/README.md)
- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- **Articles:**
- - [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/)
+- [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/)
+- [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
-## GitLab CI for GitLab Pages
+## GitLab CI/CD for GitLab Pages
- [Example projects](https://gitlab.com/pages)
-- **Articles:**
- - [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](../../user/project/pages/getting_started_part_four.md)
- - [SSGs Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/):
- examples for Ruby-, NodeJS-, Python-, and GoLang-based SSGs
- - [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- - [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
-
-See the topic [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
+- [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](../../user/project/pages/getting_started_part_four.md)
+- [SSGs Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/):
+examples for Ruby-, NodeJS-, Python-, and GoLang-based SSGs
+- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+
+See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
## More
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
new file mode 100644
index 00000000000..8e91cd05d8a
--- /dev/null
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -0,0 +1,282 @@
+---
+redirect_from: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
+---
+
+# How to deploy Maven projects to Artifactory with GitLab CI/CD
+
+> **[Article Type](../../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Fabio Busatto](https://gitlab.com/bikebilly) ||
+> **Publication date:** 2017-08-15
+
+## Introduction
+
+In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/)
+to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://www.jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
+
+You'll create two different projects:
+
+- `simple-maven-dep`: the app built and deployed to Artifactory (available at https://gitlab.com/gitlab-examples/maven/simple-maven-dep)
+- `simple-maven-app`: the app using the previous one as a dependency (available at https://gitlab.com/gitlab-examples/maven/simple-maven-app)
+
+We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/).
+We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
+
+## Create the simple Maven dependency
+
+First of all, you need an application to work with: in this specific case we will
+use a simple one, but it could be any Maven application. This will be the
+dependency you want to package and deploy to Artifactory, in order to be
+available to other projects.
+
+### Prepare the dependency application
+
+For this article you'll use a Maven app that can be cloned from our example
+project:
+
+1. Log in to your GitLab account
+1. Create a new project by selecting **Import project from ➔ Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
+ ```
+1. Click **Create project**
+
+This application is nothing more than a basic class with a stub for a JUnit based test suite.
+It exposes a method called `hello` that accepts a string as input, and prints a hello message on the screen.
+
+The project structure is really simple, and you should consider these two resources:
+
+- `pom.xml`: project object model (POM) configuration file
+- `src/main/java/com/example/dep/Dep.java`: source of our application
+
+### Configure the Artifactory deployment
+
+The application is ready to use, but you need some additional steps to deploy it to Artifactory:
+
+1. Log in to Artifactory with your user's credentials.
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel.
+1. Copy to clipboard the configuration snippet under the **Deploy** paragraph.
+1. Change the `url` value in order to have it configurable via secret variables.
+1. Copy the snippet in the `pom.xml` file for your project, just after the
+ `dependencies` section. The snippet should look like this:
+
+ ```xml
+ <distributionManagement>
+ <repository>
+ <id>central</id>
+ <name>83d43b5afeb5-releases</name>
+ <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
+ </repository>
+ </distributionManagement>
+ ```
+
+Another step you need to do before you can deploy the dependency to Artifactory
+is to configure the authentication data. It is a simple task, but Maven requires
+it to stay in a file called `settings.xml` that has to be in the `.m2` subdirectory
+in the user's homedir.
+
+Since you want to use GitLab Runner to automatically deploy the application, you
+should create the file in the project's home directory and set a command line
+parameter in `.gitlab-ci.yml` to use the custom location instead of the default one:
+
+1. Create a folder called `.m2` in the root of your repository
+1. Create a file called `settings.xml` in the `.m2` folder
+1. Copy the following content into a `settings.xml` file:
+
+ ```xml
+ <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
+ xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <servers>
+ <server>
+ <id>central</id>
+ <username>${env.MAVEN_REPO_USER}</username>
+ <password>${env.MAVEN_REPO_PASS}</password>
+ </server>
+ </servers>
+ </settings>
+ ```
+
+ Username and password will be replaced by the correct values using secret variables.
+
+### Configure GitLab CI/CD for `simple-maven-dep`
+
+Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and deploy the dependency!
+
+GitLab CI/CD uses a file in the root of the repo, named `.gitlab-ci.yml`, to read the definitions for jobs
+that will be executed by the configured GitLab Runners. You can read more about this file in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/).
+
+First of all, remember to set up secret variables for your deployment. Navigate to your project's **Settings > CI/CD** page
+and add the following secret variables (replace them with your current values, of course):
+
+- **MAVEN_REPO_URL**: `http://artifactory.example.com:8081/artifactory` (your Artifactory URL)
+- **MAVEN_REPO_USER**: `gitlab` (your Artifactory username)
+- **MAVEN_REPO_PASS**: `AKCp2WXr3G61Xjz1PLmYa3arm3yfBozPxSta4taP3SeNu2HPXYa7FhNYosnndFNNgoEds8BCS` (your Artifactory Encrypted Password)
+
+Now it's time to define jobs in `.gitlab-ci.yml` and push it to the repo:
+
+```yaml
+image: maven:latest
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+deploy:
+ stage: deploy
+ script:
+ - mvn $MAVEN_CLI_OPTS deploy
+ only:
+ - master
+```
+
+GitLab Runner will use the latest [Maven Docker image](https://hub.docker.com/_/maven/), which already contains all the tools and the dependencies you need to manage the project,
+in order to run the jobs.
+
+Environment variables are set to instruct Maven to use the `homedir` of the repo instead of the user's home when searching for configuration and dependencies.
+
+Caching the `.m2/repository folder` (where all the Maven files are stored), and the `target` folder (where our application will be created), is useful for speeding up the process
+by running all Maven phases in a sequential order, therefore, executing `mvn test` will automatically run `mvn compile` if necessary.
+
+Both `build` and `test` jobs leverage the `mvn` command to compile the application and to test it as defined in the test suite that is part of the application.
+
+Deploy to Artifactory is done as defined by the secret variables we have just set up.
+The deployment occurs only if we're pushing or merging to `master` branch, so that the development versions are tested but not published.
+
+Done! Now you have all the changes in the GitLab repo, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
+If the deployment has been successful, the deploy job log will output:
+
+```
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 1.983 s
+```
+
+>**Note**:
+the `mvn` command downloads a lot of files from the internet, so you'll see a lot of extra activity in the log the first time you run it.
+
+Yay! You did it! Checking in Artifactory will confirm that you have a new artifact available in the `libs-release-local` repo.
+
+## Create the main Maven application
+
+Now that you have the dependency available on Artifactory, it's time to use it!
+Let's see how we can have it as a dependency to our main application.
+
+### Prepare the main application
+
+We'll use again a Maven app that can be cloned from our example project:
+
+1. Create a new project by selecting **Import project from ➔ Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
+ ```
+1. Click **Create project**
+
+This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
+file you can see that it imports the `com.example.dep.Dep` class and calls the `hello` method passing `GitLab` as a parameter.
+
+Since Maven doesn't know how to resolve the dependency, you need to modify the configuration:
+
+1. Go back to Artifactory
+1. Browse the `libs-release-local` repository
+1. Select the `simple-maven-dep-1.0.jar` file
+1. Find the configuration snippet from the **Dependency Declaration** section of the main panel
+1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
+ The snippet should look like this:
+
+ ```xml
+ <dependency>
+ <groupId>com.example.dep</groupId>
+ <artifactId>simple-maven-dep</artifactId>
+ <version>1.0</version>
+ </dependency>
+ ```
+
+### Configure the Artifactory repository location
+
+At this point you defined the dependency for the application, but you still miss where you can find the required files.
+You need to create a `.m2/settings.xml` file as you did for the dependency project, and let Maven know the location using environment variables.
+
+Here is how you can get the content of the file directly from Artifactory:
+
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel
+1. Click on **Generate Maven Settings**
+1. Click on **Generate Settings**
+1. Copy to clipboard the configuration file
+1. Save the file as `.m2/settings.xml` in your repo
+
+Now you are ready to use the Artifactory repository to resolve dependencies and use `simple-maven-dep` in your main application!
+
+### Configure GitLab CI/CD for `simple-maven-app`
+
+You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
+
+You want to leverage [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and run your awesome application,
+and see if you can get the greeting as expected!
+
+All you need to do is to add the following `.gitlab-ci.yml` to the repo:
+
+```yaml
+image: maven:latest
+
+stages:
+ - build
+ - test
+ - run
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+run:
+ stage: run
+ script:
+ - mvn $MAVEN_CLI_OPTS package
+ - mvn $MAVEN_CLI_OPTS exec:java -Dexec.mainClass="com.example.app.App"
+```
+
+It is very similar to the configuration used for `simple-maven-dep`, but instead of the `deploy` job there is a `run` job.
+Probably something that you don't want to use in real projects, but here it is useful to see the application executed automatically.
+
+And that's it! In the `run` job output log you will find a friendly hello to GitLab!
+
+## Conclusion
+
+In this article we covered the basic steps to use an Artifactory Maven repository to automatically publish and consume artifacts.
+
+A similar approach could be used to interact with any other Maven compatible Binary Repository Manager.
+Obviously, you can improve these examples, optimizing the `.gitlab-ci.yml` file to better suit your needs, and adapting to your workflow.
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
new file mode 100644
index 00000000000..a7945d05cd0
--- /dev/null
+++ b/doc/ci/examples/browser_performance.md
@@ -0,0 +1,50 @@
+# Browser Performance Testing with the Sitespeed.io container
+
+This example shows how to run the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on your code by using
+GitLab CI/CD and [Sitespeed.io](https://www.sitespeed.io) using Docker-in-Docker.
+
+First, you need a GitLab Runner with the [docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+
+Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `performance`:
+
+```yaml
+ stage: performance
+ image: docker:git
+ services:
+ - docker:dind
+ script:
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io --outputFolder sitespeed-results https://my.website.com
+ artifacts:
+ paths:
+ - sitespeed-results/
+```
+
+This will create a `performance` job in your CI/CD pipeline and will run Sitespeed.io against the webpage you define. The full HTML Sitespeed.io report will be saved as an artifact, and if you have Pages enabled it can be viewed directly in your browser. For further customization options of Sitespeed.io, including the ability to provide a list of URLs to test, please consult their [documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/).
+
+For GitLab [Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) users, a performance score can be automatically
+extracted and shown right in the merge request widget. Learn more about [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
+
+## Performance testing on Review Apps
+
+The above CI YML is great for testing against static environments, and it can be extended for dynamic environments. There are a few extra steps to take to set this up:
+1. The `performance` job should run after the environment has started.
+1. In the `deploy` job, persist the hostname so it is available to the `performance` job. The same can be done for static environments like staging and production to unify the code path. Saving it as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`.
+1. In the `performance` job read the artifact into an environment variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test URL's.
+1. Now you can run the Sitespeed.io container against the desired hostname and paths.
+
+A simple `performance` job would look like:
+
+```yaml
+ stage: performance
+ image: docker:git
+ services:
+ - docker:dind
+ script:
+ - export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io --outputFolder sitespeed-results $CI_ENVIRONMENT_URL
+ artifacts:
+ paths:
+ - sitespeed-results/
+```
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index db13e0e6249..9cb1f708a6a 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -18,6 +18,10 @@ 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 |
diff --git a/doc/development/ux_guide/img/james-mackey.png b/doc/development/ux_guide/img/james-mackey.png
index 6db257c5b39..c8f9097f69f 100644
--- a/doc/development/ux_guide/img/james-mackey.png
+++ b/doc/development/ux_guide/img/james-mackey.png
Binary files differ
diff --git a/doc/development/ux_guide/img/karolina-plaskaty.png b/doc/development/ux_guide/img/karolina-plaskaty.png
index 2e356c99762..ae2e98b7bad 100644
--- a/doc/development/ux_guide/img/karolina-plaskaty.png
+++ b/doc/development/ux_guide/img/karolina-plaskaty.png
Binary files differ
diff --git a/doc/development/ux_guide/img/matthieu-poirier.png b/doc/development/ux_guide/img/matthieu-poirier.png
new file mode 100644
index 00000000000..dd21948ebe2
--- /dev/null
+++ b/doc/development/ux_guide/img/matthieu-poirier.png
Binary files differ
diff --git a/doc/development/ux_guide/img/nazim-ramesh.png b/doc/development/ux_guide/img/nazim-ramesh.png
index 01ba0391630..cc3e197679d 100644
--- a/doc/development/ux_guide/img/nazim-ramesh.png
+++ b/doc/development/ux_guide/img/nazim-ramesh.png
Binary files differ
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index fce882a45f1..6afb33cfc36 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -1,127 +1,252 @@
# UX Personas
* [Nazim Ramesh](#nazim-ramesh)
- - Small to medium size organisations using GitLab CE
+ - Small to medium size organizations using GitLab CE
+* [Matthieu Poirier](#matthieu-poirier)
+ - Responsible for managing and maintaining GitLab installation
+ - Any size organization
+ - Using CE or EE
* [James Mackey](#james-mackey)
- - Medium to large size organisations using CE or EE
- - Small organisations using EE
+ - Medium to large size organizations using CE or EE
+ - Small organizations using EE
* [Karolina Plaskaty](#karolina-plaskaty)
- Using GitLab.com for personal/hobby projects
- Would like to use GitLab at work
- - Working for a medium to large size organisation
+ - Working within a medium to large size organization
---
## Nazim Ramesh
-- Small to medium size organisations using GitLab CE
+- Small to medium size organizations using GitLab CE
-<img src="img/nazim-ramesh.png" width="300px">
+![nazim-ramesh](img/nazim-ramesh.png)
### Demographics
-- **Age**<br>32 years old
-- **Location**<br>Germany
-- **Education**<br>Bachelor of Science in Computer Science
-- **Occupation**<br>Full-stack web developer
-- **Programming experience**<br>Over 10 years
-- **Frequently used programming languages**<br>JavaScript, SQL, PHP
-- **Hobbies / interests**<br>Functional programming, open source, gaming, web development and web security.
+**Age**
+
+32 years old
+
+**Location**
+
+Germany
+
+**Education**
+
+Bachelor of Science in Computer Science
+
+**Occupation**
+
+Full-stack web developer
+
+**Programming experience**
+
+Over 10 years
+
+**Frequently used programming languages**
+
+JavaScript, SQL, PHP
+
+**Hobbies / interests**
+
+Functional programming, open source, gaming, web development, and web security.
### Motivations
-Nazim works for a software development company which currently hires around 80 people. When Nazim first joined the company, the engineering team were using Subversion (SVN) as their primary form of source control. However, Nazim felt SVN was not flexible enough to work with many feature branches and noticed that developers with less experience of source control struggled with the central-repository nature of SVN. Armed with a wishlist of features, Nazim began comparing source control tools. A search for “self-hosted Git server repository management” returned GitLab. In his own words, Nazim explains why he wanted the engineering team to start using GitLab:
+Nazim works for a software development company which currently hires around 80 people. When Nazim first joined the company, the engineering team were using Subversion (SVN) as their primary form of source control. However, Nazim felt SVN was not flexible enough to work with many feature branches and noticed that developers with less experience of source control struggled with the central-repository nature of SVN. Armed with a wish list of features, Nazim began comparing source control tools. A search for "self-hosted Git server repository management" returned GitLab. In his own words, Nazim explains why he wanted the engineering team to start using GitLab:
->
-“I wanted them to switch away from SVN. I needed a server application to manage repositories. The common tools that were around just didn’t meet the requirements. Most of them were too simple or plain...GitLab provided all the required features. Also costs had to be low, since we don’t have a big budget for those things...the Community Edition was perfect in this regard.”
->
+>"I wanted them to switch away from SVN. I needed a server application to manage repositories. The common tools that were around just didn't meet the requirements. Most of them were too simple or plain...GitLab provided all the required features. Also, costs had to be low since we don't have a big budget for those things...the Community Edition was perfect in this regard."
In his role as a full-stack web developer, Nazim could recommend products that he would like the engineering team to use, but final approval lay with his line manager, Mike, VP of Engineering. Nazim recalls that he was met with reluctance from his colleagues when he raised moving to Git and using GitLab.
->
-“The biggest challenge...why should we change anything at all from the status quo? We needed to switch from SVN to Git. They knew they needed to learn Git and a Git workflow...using Git was scary to my colleagues...they thought it was more complex than SVN to use.”
->
+>"The biggest challenge...why should we change anything at all from the status quo? We needed to switch from SVN to Git. They knew they needed to learn Git and a Git workflow...using Git was scary to my colleagues...they thought it was more complex than SVN to use."
Undeterred, Nazim decided to migrate a couple of projects across to GitLab.
->
-“Old SVN users couldn’t see the benefits of Git at first. It took a month or two to convince them.”
->
+>"Old SVN users couldn't see the benefits of Git at first. It took a month or two to convince them."
-Slowly, by showing his colleagues how easy it was to use Git, the majority of the team’s projects were migrated to GitLab.
+Slowly, by showing his colleagues how easy it was to use Git, the majority of the team's projects were migrated to GitLab.
-The engineering team have been using GitLab CE for around 2 years now. Nazim credits himself as being entirely responsible for his company’s decision to move to GitLab.
+The engineering team have been using GitLab CE for around 2 years now. Nazim credits himself as being entirely responsible for his company's decision to move to GitLab.
### Frustrations
#### Adoption to GitLab has been slow
-Not only has the engineering team had to get to grips with Git, they’ve also had to adapt to using GitLab. Due to lack of training and existing skills in other tools, the full feature set of GitLab CE is not being utilised. Nazim sold GitLab to his manager as an ‘all in one’ tool which would replace multiple tools used within the company, thus saving costs. Nazim hasn’t had the time to integrate the legacy tools to GitLab and he’s struggling to convince his peers to change their habits.
+Not only has the engineering team had to get to grips with Git, they've also had to adapt to using GitLab. Due to lack of training and existing skills in other tools, the full feature set of GitLab CE is not being utilized. Nazim sold GitLab to his manager as an ‘all in one' tool which would replace multiple tools used within the company, thus saving costs. Nazim hasn't had the time to integrate the legacy tools to GitLab and he's struggling to convince his peers to change their habits.
#### Missing Features
-Nazim’s company want GitLab to be able to do everything. There isn’t a large budget for software, so they’re selective about what tools are implemented. It needs to add real value to the company. In order for GitLab to be widely adopted and to meet the requirements of different roles within the company, it needs a host of features. When an individual within Nazim’s company wants to know if GitLab has a specific feature or does a particular thing, Nazim is the person to ask. He becomes the point of contact to investigate, build or sometimes just raise the feature request. Nazim gets frustrated when GitLab isn’t able to do what he or his colleagues need it to do.
+Nazim's company want GitLab to be able to do everything. There isn't a large budget for software, so they're selective about what tools are implemented. It needs to add real value to the company. In order for GitLab to be widely adopted and to meet the requirements of different roles within the company, it needs a host of features. When an individual within Nazim's company wants to know if GitLab has a specific feature or does a particular thing, Nazim is the person to ask. He becomes the point of contact to investigate, build or sometimes just raise the feature request. Nazim gets frustrated when GitLab isn't able to do what he or his colleagues need it to do.
#### Regressions and bugs
-Nazim often has to calm down his colleagues, when a release contains regressions or new bugs. As he puts it “every new version adds something awesome, but breaks something”. He feels that “old issues for "minor" annoyances get quickly buried in the mass of open issues and linger for a very long time. More generally, I have the feeling that GitLab focus on adding new functionalities, but overlook a bunch of annoying minor regressions or introduced bugs.” Due to limited resource and expertise within the team, not only is it difficult to remain up-to-date with the frequent release cycle, it’s also counterproductive to fix workflows every month.
+Nazim often has to calm down his colleagues, when a release contains regressions or new bugs. As he puts it "every new version adds something awesome, but breaks something". He feels that "old issues for minor annoyances get quickly buried in the mass of open issues and linger for a very long time. More generally, I have the feeling that GitLab focus on adding new functionalities, but overlook a bunch of annoying minor regressions or introduced bugs." Due to limited resource and expertise within the team, not only is it difficult to remain up-to-date with the frequent release cycle, it's also counterproductive to fix workflows every month.
#### Uses too much RAM and CPU
->
-“Memory usages mean that if we host it from a cloud based host like AWS, we spend almost as much on the instance as what we would pay GitHub”
->
+>"Memory usages mean that if we host it from a cloud-based host like AWS, we spend almost as much on the instance as what we would pay GitHub"
+
#### UI/UX
-GitLab’s interface initially attracted Nazim when he was comparing version control software. He thought it would help his less technical colleagues to adapt to using Git and perhaps, GitLab could be rolled out to other areas of the business, beyond engineering. However, using GitLab’s interface daily has left him frustrated at the lack of personalisation / control over his user experience. He’s also regularly lost in a maze of navigation. Whilst he acknowledges that GitLab listens to its users and that the interface is improving, he becomes annoyed when the changes are too progressive. “Too frequent UI changes. Most of them tend to turn out great after a few cycles of fixes, but the frequency is still far too high for me to feel comfortable to always stay on the current release.”
+GitLab's interface initially attracted Nazim when he was comparing version control software. He thought it would help his less technical colleagues to adapt to using Git and perhaps, GitLab could be rolled out to other areas of the business, beyond engineering. However, using GitLab's interface daily has left him frustrated at the lack of personalization/control over his user experience. He's also regularly lost in a maze of navigation. Whilst he acknowledges that GitLab listens to its users and that the interface is improving, he becomes annoyed when the changes are too progressive. "Too frequent UI changes. Most of them tend to turn out great after a few cycles of fixes, but the frequency is still far too high for me to feel comfortable to always stay on the current release."
### Goals
* To convince his colleagues to fully adopt GitLab CE, thus improving workflow and collaboration.
-* To use a feature rich version control platform that covers all stages of the development lifecycle, in order to reduce dependencies on other tools.
+* To use a feature-rich version control platform that covers all stages of the development lifecycle, in order to reduce dependencies on other tools.
* To use an intuitive and stable product, so he can spend more time on his core job responsibilities and less time bug-fixing, guiding colleagues, etc.
---
+## Matthieu Poirier
+- Responsible for managing and maintaining GitLab installation
+- Any size organization
+- Using CE or EE
+
+![matthieu-poirier](img/matthieu-poirier.png)
+
+### Demographics
+
+**Age**
+
+42 years old
+
+**Location**
+
+France
+
+**Education**
+
+Masters Degree in Computer Science
+
+**Occupation**
+
+DevOps Engineer
+
+**Programming experience**
+
+Over 10 years
+
+**Frequently used programming languages**
+
+JavaScript, SQL, PHP and Node.js
+
+**Hobbies / interests**
+
+Functional programming, data analysis, building apps, and tools.
+
+### Motivations
+Matthieu works in DevOps for a web services company which currently hires 90 staff. When Matthieu first joined the company, he was responsible for managing a custom built in-house bug tracking tool and release management system. Over time, as the company grew, his colleagues requested more features and tools to help them in their day-to-day work. To meet their needs, Matthieu was forced to "hack together" a solution. In his own words, Matthieu explains that it became:
+
+>"...a huge pain managing access to all the individual pieces. In addition, they didn't have any integration with each other, nobody ended up using them and we couldn't do any workflows with merge requests and the like. I was sick of managing all those separate parts and wanted to move to a single platform that would handle it all."
+
+
+He further explains that he wanted to introduce "better, easier, more formal code reviews" and to start using continuous integration and deployment.
+
+Matthieu tried to find a platform which would consolidate the company's existing toolset, and centralize code, documentation, and issues. He discovered GitHub, but the price was an issue:
+
+>"We needed to host our code on-site and wanted GitHub Enterprise functionality without the GitHub Enterprise costs."
+
+
+Not only was GitLab cheaper than GitHub, it was also more cost-effective than maintaining multiple tools. Subsequently, Matthieu found it easy to sell the merits of GitLab to his manager.
+
+Matthieu describes GitLab as:
+
+>"the only tool that offers the real feeling of having everything you need in one place."
+
+
+He credits himself as being entirely responsible for moving his company to GitLab.
+
+### Frustrations
+#### Updating to the latest release
+Matthieu introduced his company to GitLab. He is responsible for maintaining and managing the company's installation in addition to his day job. He feels updates are too frequent and he doesn't always have sufficient time to update GitLab. As a result, he's not up to date with releases.
+
+Matthieu tried to set up automatic updates, however, as he isn't a Systems Administrator, he wasn't confident in his set-up. He feels he should be able to "upgrade without users even noticing" but hasn't figured out how to do this yet. Matthieu would like the "update process to be triggered from the Admin Panel, perhaps accompanied with a changelog and the option to skip updates."
+
+Matthieu is looking for confirmation that his update procedure is "secure and efficient" so more tutorials related to this topic would be useful to him.
+
+#### Configuration
+Matthieu dislikes using the combination of gitlab.rb and the UI for changing settings. He explains that it "would be nice to be able to configure more from the Admin UI rather than just the config files."
+
+#### Creating a backup
+Matthieu explains that the "backup solution is not well integrated into the UI", for example, he "cannot see if backups succeeded" or whether they have been rolled back to via the UI.
+
+#### Onboarding
+It's Matthieu's responsibility to get teams across his organization up and running with GitLab. He explains that whilst many teams might be leveraging GitLab, they are:
+
+>"..not aware of GitLab's powerful CI or our omnibus install of Mattermost...It would be nice to have a tutorial type walkthrough available when a new user logs in on how to get started with all these features. AutoDevOps may solve some of this, but GitLab has many powerful features wrapped up into it and some [teams] may just think that it is only a Git repo similar to GitHub."
+
+
+He states that there has been: "a sluggishness of others to adapt" and it's "a low-effort adaptation at that."
+
+### Goals
+* To save time. One of the reasons Matthieu moved his company to GitLab was to reduce the effort it took him to manage and configure multiple tools, thus saving him time. He has to balance his day job in addition to managing the company's GitLab installation and onboarding new teams to GitLab.
+* To use a platform which is easy to manage. Matthieu isn't a Systems Administrator, and when updating GitLab, creating backups, etc. He would prefer to work within GitLab's UI. Explanations / guided instructions when configuring settings in GitLab's interface would really help Matthieu. He needs reassurance that what he is about to change is
+
+1. the right setting
+2. will provide him with the desired result he wants.
+
+* Matthieu needs to educate his colleagues about GitLab. Matthieu's colleagues won't adopt GitLab as they're unaware of its capabilities and the positive impact it could have on their work. Matthieu needs support in getting this message across to them.
+
+---
## James Mackey
-- Medium to large size organisations using CE or EE
-- Small organisations using EE
+- Medium to large size organizations using CE or EE
+- Small organizations using EE
-<img src="img/james-mackey.png" width="300px">
+![james-mackey.png](img/james-mackey.png)
### Demographics
-- **Age**<br>36 years old
-- **Location**<br>US
-- **Education**<br>Masters degree in Computer Science
-- **Occupation**<br>Full-stack web developer
-- **Programming experience**<br>Over 10 years
-- **Frequently used programming languages**<br>JavaScript, SQL, Node.js, Java, PHP, Python
-- **Hobbies / interests**<br>DevOps, open source, web development, science, automation and electronics.
+**Age**
+
+36 years old
+
+**Location**
+
+US
+
+**Education**
+
+Masters degree in Computer Science
+
+**Occupation**
+
+Full-stack web developer
+
+**Programming experience**
+
+Over 10 years
+
+**Frequently used programming languages**
+
+JavaScript, SQL, Node.js, Java, PHP, Python
+
+**Hobbies / interests**
+
+DevOps, open source, web development, science, automation, and electronics.
### Motivations
-James works for a research company which currently hires around 800 staff. He began using GitLab.com back in 2013 for his own open source, hobby projects and loved “the simplicity of installation, administration and use”. After using GitLab for over a year, he began to wonder about using it at work. James explains:
+James works for a research company which currently hires around 800 staff. He began using GitLab.com back in 2013 for his own open source, hobby projects and loved "the simplicity of installation, administration and use". After using GitLab for over a year, he began to wonder about using it at work. James explains:
->
-“We first installed the CE edition...on a staging server for a PoC and asked a beta team to use it, specifically for the Merge Request features. Soon other teams began asking us to be beta users too, because the team that was already using GitLab was really enjoying it.”
->
+>"We first installed the CE edition...on a staging server for a PoC and asked a beta team to use it, specifically for the Merge Request features. Soon other teams began asking us to be beta users too because the team that was already using GitLab was really enjoying it."
-James and his colleagues also reviewed competitor products including GitHub Enterprise, but they found it “less innovative and with considerable costs...GitLab had the features we wanted at a much lower cost per head than GitHub”.
+
+James and his colleagues also reviewed competitor products including GitHub Enterprise, but they found it "less innovative and with considerable costs...GitLab had the features we wanted at a much lower cost per head than GitHub".
The company James works for provides employees with a discretionary budget to spend how they want on software, so James and his team decided to upgrade to EE.
-James feels partially responsible for his organisation’s decision to start using GitLab.
+James feels partially responsible for his organization's decision to start using GitLab.
+
+>"It's still up to the teams themselves [to decide] which tools to use. We just had a great experience moving our daily development to GitLab, so other teams have followed the path or are thinking about switching."
->
-“It's still up to the teams themselves [to decide] which tools to use. We just had a great experience moving our daily development to GitLab, so other teams have followed the path or are thinking about switching.”
->
### Frustrations
#### Third Party Integration
-Some of GitLab EE’s features are too basic, in particular, issues boards which do not have the level of reporting that James and his team need. Subsequently, they still need to use GitLab EE in conjunction with other tools, such as JIRA. Whilst James feels it isn’t essential for GitLab to meet all his needs (his company are happy for him to use, and pay for, multiple tools), he sometimes isn’t sure what is/isn’t possible with plugins and what level of custom development he and his team will need to do.
+Some of GitLab EE's features are too basic, in particular, issues boards which do not have the level of reporting that James and his team need. Subsequently, they still need to use GitLab EE in conjunction with other tools, such as JIRA. Whilst James feels it isn't essential for GitLab to meet all his needs (his company are happy for him to use, and pay for, multiple tools), he sometimes isn't sure what is/isn't possible with plugins and what level of custom development he and his team will need to do.
#### UX/UI
-James and his team use CI quite heavily for several projects. Whilst they’ve welcomed improvements to the builds and pipelines interface, they still have some difficulty following build process on the different tabs under Pipelines. Some confusion has arisen from not knowing where to find different pieces of information or how to get to the next stages logs from the current stage’s log output screen. They feel more intuitive linking and flow may alleviate the problem. Generally, they feel GitLab’s navigation needs to reviewed and optimised.
+James and his team use CI quite heavily for several projects. Whilst they've welcomed improvements to the builds and pipelines interface, they still have some difficulty following build process on the different tabs under Pipelines. Some confusion has arisen from not knowing where to find different pieces of information or how to get to the next stages logs from the current stage's log output screen. They feel more intuitive linking and flow may alleviate the problem. Generally, they feel GitLab's navigation needs to reviewed and optimized.
#### Permissions
->
-“There is no granular control over user or group permissions. The permissions for a project are too tightly coupled to the permissions for Gitlab CI/build pipelines.”
->
+>"There is no granular control over user or group permissions. The permissions for a project are too tightly coupled to the permissions for Gitlab CI/build pipelines."
+
### Goals
-* To be able to integrate third party tools easily with GitLab EE and to create custom integrations and patches where needed.
+* To be able to integrate third-party tools easily with GitLab EE and to create custom integrations and patches where needed.
* To use GitLab EE primarily for code hosting, merge requests, continuous integration and issue management. James and his team want to be able to understand and use these particular features easily.
* To able to share one instance of GitLab EE with multiple teams across the business. Advanced user management, the ability to separate permissions on different parts of the source code, etc are important to James.
@@ -130,36 +255,56 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w
## Karolina Plaskaty
- Using GitLab.com for personal/hobby projects
- Would like to use GitLab at work
-- Working for a medium to large size organisation
+- Working within a medium to large size organization
-<img src="img/karolina-plaskaty.png" width="300px">
+![karolina-plaskaty.png](img/karolina-plaskaty.png)
### Demographics
-- **Age**<br>26 years old
-- **Location**<br>UK
-- **Education**<br>Self taught
-- **Occupation**<br>Junior web-developer
-- **Programming experience**<br>6 years
-- **Frequently used programming languages**<br>JavaScript and SQL
-- **Hobbies / interests**<br>Web development, mobile development, UX, open source, gaming and travel.
+**Age**
+
+26 years old
+
+**Location**
+
+UK
+
+**Education**
+
+Self taught
+
+**Occupation**
+
+Junior web-developer
+
+**Programming experience**
+
+6 years
+
+**Frequently used programming languages**
+
+JavaScript and SQL
+
+**Hobbies / interests**
+
+Web development, mobile development, UX, open source, gaming, and travel.
### Motivations
-Karolina has been using GitLab.com for around a year. She roughly spends 8 hours every week programming, of that, 2 hours is spent contributing to open source projects. Karolina contributes to open source projects to gain programming experience and to give back to the community. She likes GitLab.com for its free private repositories and range of features which provide her with everything she needs for her personal projects. Karolina is also a massive fan of GitLab’s values and the fact that it isn’t a “behemoth of a company”. She explains that “displaying every single thing (doc, culture, assumptions, development...) in the open gives me greater confidence to choose Gitlab personally and to recommend it at work.” She’s also an avid reader of GitLab’s blog.
+Karolina has been using GitLab.com for around a year. She roughly spends 8 hours every week programming, of that, 2 hours is spent contributing to open source projects. Karolina contributes to open source projects to gain programming experience and to give back to the community. She likes GitLab.com for its free private repositories and range of features which provide her with everything she needs for her personal projects. Karolina is also a massive fan of GitLab's values and the fact that it isn't a "behemoth of a company". She explains that "displaying every single thing (doc, culture, assumptions, development...) in the open gives me greater confidence to choose Gitlab personally and to recommend it at work." She's also an avid reader of GitLab's blog.
-Karolina works for a software development company which currently hires around 500 people. Karolina would love to use GitLab at work but the company has used GitHub Enterprise for a number of years. She describes management at her company as “old fashioned” and explains that it’s “less of a technical issue and more of a cultural issue” to convince upper management to move to GitLab. Karolina is also relatively new to the company so she’s apprehensive about pushing too hard to change version control platforms.
+Karolina works for a software development company which currently hires around 500 people. Karolina would love to use GitLab at work but the company has used GitHub Enterprise for a number of years. She describes management at her company as "old fashioned" and explains that it's "less of a technical issue and more of a cultural issue" to convince upper management to move to GitLab. Karolina is also relatively new to the company so she's apprehensive about pushing too hard to change version control platforms.
### Frustrations
#### Unable to use GitLab at work
-Karolina wants to use GitLab at work but isn’t sure how to approach the subject with management. In her current role, she doesn’t feel that she has the authority to request GitLab.
+Karolina wants to use GitLab at work but isn't sure how to approach the subject with management. In her current role, she doesn't feel that she has the authority to request GitLab.
#### Performance
-GitLab.com is frequently slow and unavailable. Karolina has also heard that GitLab is a “memory hog” which has deterred her from running GitLab on her own machine for just hobby / personal projects.
+GitLab.com is frequently slow and unavailable. Karolina has also heard that GitLab is a "memory hog" which has deterred her from running GitLab on her own machine for just hobby / personal projects.
#### UX/UI
-Karolina has an interest in UX and therefore has strong opinions about how GitLab should look and feel. She feels the interface is cluttered, “it has too many links/buttons” and the navigation “feels a bit weird sometimes. I get lost if I don’t pay attention.” As Karolina also enjoys contributing to open-source projects, it’s important to her that GitLab is well designed for public repositories, she doesn’t feel that GitLab currently achieves this.
+Karolina has an interest in UX and therefore has strong opinions about how GitLab should look and feel. She feels the interface is cluttered, "it has too many links/buttons" and the navigation "feels a bit weird sometimes. I get lost if I don't pay attention." As Karolina also enjoys contributing to open-source projects, it's important to her that GitLab is well designed for public repositories, she doesn't feel that GitLab currently achieves this.
### Goals
* To develop her programming experience and to learn from other developers.
* To contribute to both her own and other open source projects.
-* To use a fast and intuitive version control platform.
+* To use a fast and intuitive version control platform. \ No newline at end of file
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 56888b05609..67b89d608cc 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -133,9 +133,9 @@ Remove the old Ruby 1.8 if present:
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.5.tar.gz
- echo '3247e217d6745c27ef23bdc77b6abdb4b57a118f ruby-2.3.5.tar.gz' | shasum -c - && tar xzf ruby-2.3.5.tar.gz
- cd ruby-2.3.5
+ 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
@@ -367,7 +367,7 @@ sudo usermod -aG redis git
# Enable packfile bitmaps
sudo -u git -H git config --global repack.writeBitmaps true
-
+
# Enable push options
sudo -u git -H git config --global receive.advertisePushOptions true
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 1f30909b0aa..a8aa11265d0 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -20,7 +20,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html)
- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
- **Articles:**
- - [How to Configure LDAP with GitLab CE](../../articles/how_to_configure_ldap_gitlab_ce/index.md)
+ - [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/)
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/ldap/debugging_ldap.html)
diff --git a/doc/topics/autodevops/img/auto_devops_settings.png b/doc/topics/autodevops/img/auto_devops_settings.png
deleted file mode 100644
index 067c9da3fdc..00000000000
--- a/doc/topics/autodevops/img/auto_devops_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 0b48596006d..339bc2bd4fe 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -122,11 +122,13 @@ Google Cloud.
## Enabling Auto DevOps
-**Note:**
If you haven't done already, read the [prerequisites](#prerequisites) to make
full use of Auto DevOps. If this is your fist time, we recommend you follow the
[quick start guide](#quick-start).
+To enable Auto DevOps to your project:
+
+1. Check that your project doesn't have a `.gitlab-ci.yml`, and remove it otherwise
1. Go to your project's **Settings > CI/CD > General pipelines settings** and
find the Auto DevOps section
1. Select "Enable Auto DevOps"
@@ -134,22 +136,13 @@ full use of Auto DevOps. If this is your fist time, we recommend you follow the
that will be used by Kubernetes to deploy your application
1. Hit **Save changes** for the changes to take effect
-![Project AutoDevops settings section](img/auto_devops_settings.png)
-
-Now that it's enabled, there are a few more steps depending on whether your project
-has a `.gitlab-ci.yml` or not:
-
-- **For projects with no `.gitlab-ci.yml` present:**
- A pipeline needs to be triggered either by pushing a new commit to the
- repository or manually visiting `https://example.gitlab.com/<username>/<project>/pipelines/new`
- and creating a new pipeline for your default branch, generally `master`.
-- **For projects with a `.gitlab-ci.yml` present:**
- All you need to do is remove your existing `.gitlab-ci.yml`, and you can even
- do that in a branch to test Auto DevOps before committing to `master`.
+Once saved, an Auto DevOps pipeline will be triggered on the default branch.
NOTE: **Note:**
-Starting with GitLab 10.3, when enabling Auto DevOps, a pipeline is
-automatically run on the default branch.
+For GitLab versions 10.0 - 10.2, when enabling Auto DevOps, a pipeline needs to be
+manually triggered either by pushing a new commit to the repository or by visiting
+`https://example.gitlab.com/<username>/<project>/pipelines/new` and creating
+a new pipeline for your default branch, generally `master`.
NOTE: **Note:**
If you are a GitLab Administrator, you can enable Auto DevOps instance wide
diff --git a/doc/update/10.1-to-10.2.md b/doc/update/10.1-to-10.2.md
index 9e0d8f79522..632e8befa74 100644
--- a/doc/update/10.1-to-10.2.md
+++ b/doc/update/10.1-to-10.2.md
@@ -339,11 +339,11 @@ 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.0)
+## Things went south? Revert to previous version (10.1)
### 1. Revert the code to the previous version
-Follow the [upgrade guide from 9.5 to 10.0](9.5-to-10.0.md), except for the
+Follow the [upgrade guide from 10.0 to 10.1](10.0-to-10.1.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
diff --git a/doc/update/10.2-to-10.3.md b/doc/update/10.2-to-10.3.md
index 07f9ee965f0..d6e2db8a353 100644
--- a/doc/update/10.2-to-10.3.md
+++ b/doc/update/10.2-to-10.3.md
@@ -339,11 +339,11 @@ 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.0)
+## Things went south? Revert to previous version (10.2)
### 1. Revert the code to the previous version
-Follow the [upgrade guide from 9.5 to 10.0](9.5-to-10.0.md), except for the
+Follow the [upgrade guide from 10.1 to 10.2](10.1-to-10.2.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
diff --git a/doc/update/10.3-to-10.4.md b/doc/update/10.3-to-10.4.md
new file mode 100644
index 00000000000..850cb3103f4
--- /dev/null
+++ b/doc/update/10.3-to-10.4.md
@@ -0,0 +1,360 @@
+---
+comments: false
+---
+
+# From 10.3 to 10.4
+
+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
+
+```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 now runs [webpack](http://webpack.js.org) to compile frontend assets and
+it has a minimum requirement of node v4.3.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v4.3.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/>
+
+
+Since 8.17, GitLab requires the use of yarn `>= v0.17.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-4-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-4-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-3-stable:config/gitlab.yml.example origin/10-4-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-3-stable:lib/support/nginx/gitlab-ssl origin/10-4-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-3-stable:lib/support/nginx/gitlab origin/10-4-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-4-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-4-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-3-stable:lib/support/init.d/gitlab.default.example origin/10-4-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.3)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 10.2 to 10.3](10.2-to-10.3.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-4-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-4-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/group/img/transfer_project_to_other_group.png b/doc/user/group/img/transfer_project_to_other_group.png
deleted file mode 100644
index 042c002f83f..00000000000
--- a/doc/user/group/img/transfer_project_to_other_group.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 1733017cbc0..7f77a33aadc 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -65,7 +65,7 @@ together in a single list view.
> **Notes:**
- For a list of words that are not allowed to be used as group names see the
- [reserved names][reserved].
+ [reserved names](../reserved_names.md).
You can create a group in GitLab from:
@@ -97,7 +97,7 @@ Add members to a group by navigating to the group's dashboard, and clicking **Me
![add members to group](img/add_new_members.png)
-Select the [permission level][permissions] and add the new member. You can also set the expiring
+Select the [permission level](../permissions.md#permissions) and add the new member. You can also set the expiring
date for that user, from which they will no longer have access to your group.
One of the benefits of putting multiple projects in one group is that you can
@@ -152,16 +152,9 @@ There are two different ways to add a new project to a group:
![Select group](img/select_group_dropdown.png)
-## Transfer an existing project into a group
+## Transfer projects into groups
-You can transfer an existing project into a group as long as you have at least **Master** [permissions][permissions] to that group
-and if you are an **Owner** of the project.
-
-![Transfer a project to a new namespace](img/transfer_project_to_other_group.png)
-
-Find this option under your project's settings.
-
-GitLab administrators can use the admin interface to move any project to any namespace if needed.
+Learn how to [transfer a project into a group](../project/index.md#transfer-an-existing-project-into-a-group).
## Sharing a project with a group
@@ -190,24 +183,29 @@ access further configurations for your group.
#### Changing a group's path
-> **Note:** If you want to retain ownership over the original namespace and
-protect the URL redirects, then instead of changing a group's path or renaming a
-username, you can create a new group and transfer projects to it.
+Changing a group's path can have unintended side effects. Read
+[how redirects will behave](../project/index.md#redirects-when-changing-repository-paths)
+before proceeding.
-Changing a group's path can have unintended side effects.
+If you are vacating the path so it can be claimed by another group or user,
+you may need to rename the group name as well since both names and paths must
+be unique.
-* Existing web URLs for the group and anything under it (i.e. projects) will
-redirect to the new URLs.
-* Existing Git remote URLs for projects under the group will redirect to the new remote URL, and they
-will show a warning with the new remote URL.
-* The redirect to the new URL is permanent, that implies the original namespace
-can't be claimed again by any group or user.
-* If you are vacating the path so it can be claimed by another group or user,
-you may need to rename the group name as well since both names and paths must be
-unique
+To change your group path:
-> It is currently not possible to rename a namespace if it contains a
-project with container registry tags, because the project cannot be moved.
+1. Navigate to your group's **Settings > General**.
+1. Enter a new name under "Group path".
+1. Hit **Save group**.
+
+CAUTION: **Caution:**
+It is currently not possible to rename a namespace if it contains a
+project with [Container Registry](../project/container_registry.md) tags,
+because the project cannot be moved.
+
+TIP: **TIP:**
+If you want to retain ownership over the original namespace and
+protect the URL redirects, then instead of changing a group's path or renaming a
+username, you can create a new group and transfer projects to it.
#### Enforce 2FA to group members
@@ -246,11 +244,7 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
- **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
-and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter][ee].)
+and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter](https://about.gitlab.com/products/).)
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]).
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
-
-[permissions]: ../permissions.md#permissions
-[ee]: https://about.gitlab.com/products/
-[reserved]: ../reserved_names.md
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index dae4cbe170b..ab16f8d14c1 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -8,51 +8,6 @@ experience according to the best approach to their cases.
There are several ways to sign into your GitLab account.
See the [authentication topic](../../topics/authentication/index.md) for more details.
-### Why do I keep getting signed out?
-
-When signing in to the main GitLab application, a `_gitlab_session` cookie is
-set. `_gitlab_session` is cleared client-side when you close your browser
-and expires after "Application settings -> Session duration (minutes)"/`session_expire_delay`
-(defaults to `10080` minutes = 7 days).
-
-When signing in to the main GitLab application, you can also check the
-"Remember me" option which sets the `remember_user_token`
-cookie (via [`devise`](https://github.com/plataformatec/devise)).
-`remember_user_token` expires after
-`config/initializers/devise.rb` -> `config.remember_for` (defaults to 2 weeks).
-
-When the `_gitlab_session` expires or isn't available, GitLab uses the `remember_user_token`
-to get you a new `_gitlab_session` and keep you signed in through browser restarts.
-
-After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
-you will be asked to sign in again to verify your identity (which is for security reasons).
-
-## Username
-
-Your `username` is a unique [`namespace`](../group/index.md#namespaces)
-related to your user ID.
-
-### Changing your username
-
-You can change your `username` from your
-[profile settings](#profile-settings).
-
-> **Note:** If you want to retain ownership over the original namespace and
-protect the URL redirects, then instead of changing your username, you can
-create a new group and transfer projects to it.
-Alternatively, you can follow [this detailed procedure from the GitLab Team Handbook](https://about.gitlab.com/handbook/tools-and-tips/#how-to-change-your-username-at-gitlabcom).
-
-Changing your username can have unintended side effects.
-
-* Existing web URLs for the user and anything under it (i.e. projects) will
-redirect to the new URLs.
-* Existing Git remote URLs for projects under the user will redirect to the new remote URL. Git responses
-will show a warning with the new remote URL.
-* The redirect to the new URL is permanent, that implies the original namespace can't be claimed again by any group or user.
-
-> It is currently not possible to rename a namespace if it contains a
-project with container registry tags, because the project cannot be moved.
-
## User profile
Your profile is available from the up-right corner menu bar (user's avatar) > **Profile**,
@@ -84,4 +39,51 @@ From there, you can:
- Manage [SSH keys](../../ssh/README.md#ssh) to access your account via SSH
- Manage your [preferences](preferences.md#syntax-highlighting-theme)
to customize your own GitLab experience
-- Acess your audit log, a security log of important events involving your account
+- Access your audit log, a security log of important events involving your account
+
+## Changing your username
+
+Your `username` is a unique [`namespace`](../group/index.md#namespaces)
+related to your user ID. Changing it can have unintended side effects, read
+[how redirects will behave](../project/index.md#redirects-when-changing-repository-paths)
+before proceeding.
+
+To change your `username`:
+
+1. Navigate to your [profile's](#profile-settings) **Settings > Account**.
+1. Enter a new username under "Change username".
+1. Hit **Update username**.
+
+CAUTION: **Caution:**
+It is currently not possible to change your username if it contains a
+project with [Container Registry](../project/container_registry.md) tags,
+because the project cannot be moved.
+
+TIP: **Tip:**
+If you want to retain ownership over the original namespace and
+protect the URL redirects, then instead of changing a group's path or renaming a
+username, you can create a new group and transfer projects to it.
+Alternatively, you can follow [this detailed procedure from the GitLab Team Handbook](https://about.gitlab.com/handbook/tools-and-tips/#how-to-change-your-username-at-gitlabcom)
+which also covers the case where you have projects hosted with
+[GitLab Pages](../project/pages/index.md).
+
+## Troubleshooting
+
+### Why do I keep getting signed out?
+
+When signing in to the main GitLab application, a `_gitlab_session` cookie is
+set. `_gitlab_session` is cleared client-side when you close your browser
+and expires after "Application settings -> Session duration (minutes)"/`session_expire_delay`
+(defaults to `10080` minutes = 7 days).
+
+When signing in to the main GitLab application, you can also check the
+"Remember me" option which sets the `remember_user_token`
+cookie (via [`devise`](https://github.com/plataformatec/devise)).
+`remember_user_token` expires after
+`config/initializers/devise.rb` -> `config.remember_for` (defaults to 2 weeks).
+
+When the `_gitlab_session` expires or isn't available, GitLab uses the `remember_user_token`
+to get you a new `_gitlab_session` and keep you signed in through browser restarts.
+
+After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
+you will be asked to sign in again to verify your identity (which is for security reasons).
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 5d91aef5735..4c772c62f8d 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -93,6 +93,9 @@ from your fork to the upstream project
## Project settings
+Set the project's visibility level and the access levels to its various pages
+and perform actions like archiving, renaming or transferring a project.
+
Read through the documentation on [project settings](settings/index.md).
## Import or export a project
@@ -116,3 +119,32 @@ when a project is part of a group (under a
[group namespace](../group/index.md#namespaces)).
If you choose to leave a project you will no longer be a project
member, therefore, unable to contribute.
+
+## Redirects when changing repository paths
+
+When a repository path changes, it is essential to smoothly transition from the
+old location to the new one. GitLab provides two kinds of redirects: the web UI
+and Git push/pull redirects.
+
+Depending on the situation, different things apply.
+
+When [transferring a project](settings/index.md#transferring-an-existing-project-into-another-namespace),
+or [renaming a user](../profile/index.md#changing-your-username) or
+[changing a group path](../group/index.md#changing-a-group-s-path):
+
+- **The redirect to the new URL is permanent**, which means that the original
+ namespace can't be claimed again by any group or user.
+- Existing web URLs for the namespace and anything under it (e.g., projects) will
+ redirect to the new URLs.
+- Starting with GitLab 10.3, existing Git remote URLs for projects under the
+ namespace will redirect to the new remote URL. Every time you push/pull to a
+ repository that has changed its location, a warning message to update
+ your remote will be displayed instead of rejecting your action.
+ This means that any automation scripts, or Git clients will continue to
+ work after a rename, making any transition a lot smoother.
+ To avoid pulling from or pushing to an entirely incorrect repository, the old
+ path will be reserved.
+
+When [renaming-a-repository](settings/index.md#renaming-a-repository), the same
+things apply, except for the Git push/pull actions which will be rejected with a
+warning message to change to the new remote URL.
diff --git a/doc/user/project/integrations/samples/prometheus.yml b/doc/user/project/integrations/samples/prometheus.yml
index 30b59e172a1..3a4735d282f 100644
--- a/doc/user/project/integrations/samples/prometheus.yml
+++ b/doc/user/project/integrations/samples/prometheus.yml
@@ -94,7 +94,7 @@ spec:
- name: prometheus
image: prom/prometheus:latest
args:
- - '-config.file=/prometheus-data/prometheus.yml'
+ - '--config.file=/prometheus-data/prometheus.yml'
ports:
- name: prometheus
containerPort: 9090
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index 6b9976d133c..d41be0989d2 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -1,6 +1,11 @@
# Signing commits with GPG
-> [Introduced][ce-9546] in GitLab 9.5.
+NOTE: **Note:**
+The term GPG is used for all OpenPGP/PGP/GPG related material and
+implementations.
+
+> - [Introduced][ce-9546] in GitLab 9.5.
+> - Subkeys support was added in GitLab 10.1.
GitLab can show whether a commit is verified or not when signed with a GPG key.
All you need to do is upload the public GPG key in your profile settings.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 2b6fde1e2a5..f01fa5b1860 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -1,11 +1,20 @@
# Project settings
+NOTE: **Note:**
+Only project Masters and Admin users have the [permissions] to access a project
+settings.
+
You can adjust your [project](../index.md) settings by navigating
to your project's homepage and clicking **Settings**.
## General settings
-Adjust your project's path and name, description, avatar, [default branch](../repository/branches/index.md#default-branch), and tags:
+Under a project's general settings you can find everything concerning the
+functionality of a project.
+
+### General project settings
+
+Adjust your project's name, description, avatar, [default branch](../repository/branches/index.md#default-branch), and tags:
![general project settings](img/general_settings.png)
@@ -45,14 +54,64 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
#### Archiving a project
->**Note:** Only Project Owners and Admin users have the permission to archive a project
+NOTE: **Note:**
+Only project Owners and Admin users have the [permissions] to archive a project.
+
+An archived project will be hidden by default in the project listings.
+
+1. Navigate to your project's **Settings > General > Advanced settings**.
+1. Under "Archive project", hit the **Archive project** button.
+1. Confirm the action when asked to.
+
+An archived project can be fully restored and will therefore retain its
+repository and all associated resources whilst in an archived state.
+
+#### Renaming a repository
+
+NOTE: **Note:**
+Only project Masters and Admin users have the [permissions] to rename a
+repository. Not to be confused with a project's name where it can also be
+changed from the [general project settings](#general-project-settings).
+
+A project's repository name defines its URL (the one you use to access the
+project via a browser) and its place on the file disk where GitLab is installed.
+
+To rename a repository:
+
+1. Navigate to your project's **Settings > General > Advanced settings**.
+1. Under "Rename repository", change the "Path" to your liking.
+1. Hit **Rename project**.
+
+Remember that this can have unintended side effects since everyone with the
+old URL will not be able to push or pull. Read more about what happens with the
+[redirects when renaming repositories](../index.md#redirects-when-changing-repository-paths).
+
+#### Transferring an existing project into another namespace
+
+NOTE: **Note:**
+Only project Owners and Admin users have the [permissions] to transfer a project.
+
+You can transfer an existing project into a [group](../../group/index.md) if:
+
+1. you have at least **Master** [permissions] to that group
+1. you are an **Owner** of the project.
+
+Similarly, if you are an owner of a group, you can transfer any of its projects
+under your own user.
-It's possible to mark a project as archived via the Project Settings. An archived project will be hidden by default in the project listings.
+To transfer a project:
-An archived project can be fully restored and will therefore retain it's repository and all associated resources whilst in an archived state.
+1. Navigate to your project's **Settings > General > Advanced settings**.
+1. Under "Transfer project", choose the namespace you want to transfer the
+ project to.
+1. Confirm the transfer by typing the project's path as instructed.
-#### Renaming a project
+Once done, you will be taken to the new project's namespace. At this point,
+read what happens with the
+[redirects from the old project to the new one](../index.md#redirects-when-changing-repository-paths).
->**Note:** Only Project Owners and Admin users have the permission to rename a project
+NOTE: **Note:**
+GitLab administrators can use the admin interface to move any project to any
+namespace if needed.
-It's possible to rename a project from "Rename repository" or "Transfer project" sections. When doing so, you will need to update your local repositories to point to the new location, otherwise Git operations will be rejected.
+[permissions]: ../../permissions.md##project-members-permissions
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 195285f9157..f7b87dee8e1 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -170,4 +170,4 @@ GitLab checks files to detect LFS pointers on push. If LFS pointers are detected
Verify that LFS in installed locally and consider a manual push with `git lfs push --all`.
-If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projets api](../../api/projects.md#edit-project).
+If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projects api](../../api/projects.md#edit-project).
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 6e04f09f322..93b057430d3 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -278,17 +278,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content('Your changes could not be committed')
end
- step 'I create bare repo' do
- click_link 'Create empty bare repository'
- end
-
- step 'I click on "README" link' do
- click_link 'README'
-
- # Remove pre-receive hook so we can push without auth
- FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
- end
-
step "I switch ref to 'test'" do
first('.js-project-refs-dropdown').click
diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb
index db63c5038ae..a4d8507960e 100644
--- a/lib/after_commit_queue.rb
+++ b/lib/after_commit_queue.rb
@@ -14,7 +14,15 @@ module AfterCommitQueue
def run_after_commit_or_now(&block)
if AfterCommitQueue.inside_transaction?
- run_after_commit(&block)
+ if ActiveRecord::Base.connection.current_transaction.records.include?(self)
+ run_after_commit(&block)
+ else
+ # If the current transaction does not include this record, we can run
+ # the block now, even if it queues a Sidekiq job.
+ Sidekiq::Worker.skipping_transaction_check do
+ instance_eval(&block)
+ end
+ end
else
instance_eval(&block)
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 758f15c8a67..c1f933ec54b 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -2,19 +2,21 @@ require 'uri'
module Banzai
module Filter
- # HTML filter that "fixes" relative links to files in a repository.
+ # HTML filter that "fixes" relative links to uploads or files in a repository.
#
# Context options:
# :commit
+ # :group
# :project
# :project_wiki
# :ref
# :requested_path
class RelativeLinkFilter < HTML::Pipeline::Filter
- def call
- return doc unless linkable_files?
+ include Gitlab::Utils::StrongMemoize
+ def call
@uri_types = {}
+ clear_memoization(:linkable_files)
doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
@@ -31,13 +33,35 @@ module Banzai
protected
def linkable_files?
- context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
+ strong_memoize(:linkable_files) do
+ context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
+ end
end
def process_link_attr(html_attr)
return if html_attr.blank?
return if html_attr.value.start_with?('//')
+ if html_attr.value.start_with?('/uploads/')
+ process_link_to_upload_attr(html_attr)
+ elsif linkable_files?
+ process_link_to_repository_attr(html_attr)
+ end
+ end
+
+ def process_link_to_upload_attr(html_attr)
+ uri_parts = [html_attr.value]
+
+ if group
+ uri_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
+ elsif project
+ uri_parts.unshift(relative_url_root, project.full_path)
+ end
+
+ html_attr.value = File.join(*uri_parts)
+ end
+
+ def process_link_to_repository_attr(html_attr)
uri = URI(html_attr.value)
if uri.relative? && uri.path.present?
html_attr.value = rebuild_relative_uri(uri).to_s
@@ -51,7 +75,7 @@ module Banzai
uri.path = [
relative_url_root,
- context[:project].full_path,
+ project.full_path,
uri_type(file_path),
Addressable::URI.escape(ref),
Addressable::URI.escape(file_path)
@@ -123,11 +147,19 @@ module Banzai
end
def ref
- context[:ref] || context[:project].default_branch
+ context[:ref] || project.default_branch
+ end
+
+ def group
+ context[:group]
+ end
+
+ def project
+ context[:project]
end
def repository
- @repository ||= context[:project].try(:repository)
+ @repository ||= project&.repository
end
end
end
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
deleted file mode 100644
index d64f9ac4eb6..00000000000
--- a/lib/banzai/filter/upload_link_filter.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'uri'
-
-module Banzai
- module Filter
- # HTML filter that "fixes" relative upload links to files.
- # Context options:
- # :project (required) - Current project
- #
- class UploadLinkFilter < HTML::Pipeline::Filter
- def call
- return doc unless project || group
-
- doc.xpath('descendant-or-self::a[starts-with(@href, "/uploads/")]').each do |el|
- process_link_attr el.attribute('href')
- end
-
- doc.xpath('descendant-or-self::img[starts-with(@src, "/uploads/")]').each do |el|
- process_link_attr el.attribute('src')
- end
-
- doc
- end
-
- protected
-
- def process_link_attr(html_attr)
- html_attr.value = build_url(html_attr.value).to_s
- end
-
- def build_url(uri)
- base_path = Gitlab.config.gitlab.url
-
- if group
- urls = Gitlab::Routing.url_helpers
- # we need to get last 2 parts of the uri which are secret and filename
- uri_parts = uri.split(File::SEPARATOR)
- file_path = urls.show_group_uploads_path(group, uri_parts[-2], uri_parts[-1])
- File.join(base_path, file_path)
- else
- File.join(base_path, project.full_path, uri)
- end
- end
-
- def project
- context[:project]
- end
-
- def group
- context[:group]
- end
-
- # Ensure that a :project key exists in context
- #
- # Note that while the key might exist, its value could be nil!
- def validate
- needs :project
- end
- end
- end
-end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 55874ad50a3..c746f6f64e9 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -15,7 +15,6 @@ module Banzai
Filter::MathFilter,
Filter::MermaidFilter,
- Filter::UploadLinkFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb
index 1fba0b2db0b..26505c91be3 100644
--- a/lib/gitlab/ci/config/entry/node.rb
+++ b/lib/gitlab/ci/config/entry/node.rb
@@ -93,9 +93,7 @@ module Gitlab
private
- def entries
- @entries
- end
+ attr_reader :entries
end
end
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index d0cfe2386ca..cd490aaa291 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -61,7 +61,9 @@ module Gitlab
end
def line_for_position(pos)
- diff_lines.find { |line| position(line) == pos }
+ return nil unless pos.position_type == 'text'
+
+ diff_lines.find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line }
end
def position_for_line_code(code)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 044c60caa05..603323d0452 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -56,11 +56,12 @@ module Gitlab
# Do nothing if hooks already exist
unless real_local_hooks_path == File.realpath(global_hooks_path)
- # Move the existing hooks somewhere safe
- FileUtils.mv(
- local_hooks_path,
- "#{local_hooks_path}.old.#{Time.now.to_i}"
- ) if File.exist?(local_hooks_path)
+ if File.exist?(local_hooks_path)
+ # Move the existing hooks somewhere safe
+ FileUtils.mv(
+ local_hooks_path,
+ "#{local_hooks_path}.old.#{Time.now.to_i}")
+ end
# Create the hooks symlink
FileUtils.ln_sf(global_hooks_path, local_hooks_path)
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 407cdefc04d..0f0588b8b23 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -1,96 +1,8 @@
module Gitlab
module Kubernetes
- class Helm
+ module Helm
HELM_VERSION = '2.7.0'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
- INSTALL_DEPS = <<-EOS.freeze
- set -eo pipefail
- apk add -U ca-certificates openssl >/dev/null
- wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
- mv /tmp/linux-amd64/helm /usr/bin/
- EOS
-
- InstallCommand = Struct.new(:name, :install_helm, :chart) do
- def pod_name
- "install-#{name}"
- end
- end
-
- def initialize(kubeclient)
- @kubeclient = kubeclient
- @namespace = Gitlab::Kubernetes::Namespace.new(NAMESPACE, kubeclient)
- end
-
- def install(command)
- @namespace.ensure_exists!
- @kubeclient.create_pod(pod_resource(command))
- end
-
- ##
- # Returns Pod phase
- #
- # https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
- #
- # values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
- #
- def installation_status(pod_name)
- @kubeclient.get_pod(pod_name, @namespace.name).status.phase
- end
-
- def installation_log(pod_name)
- @kubeclient.get_pod_log(pod_name, @namespace.name).body
- end
-
- def delete_installation_pod!(pod_name)
- @kubeclient.delete_pod(pod_name, @namespace.name)
- end
-
- private
-
- def pod_resource(command)
- labels = { 'gitlab.org/action': 'install', 'gitlab.org/application': command.name }
- metadata = { name: command.pod_name, namespace: @namespace.name, labels: labels }
- container = {
- name: 'helm',
- image: 'alpine:3.6',
- env: generate_pod_env(command),
- command: %w(/bin/sh),
- args: %w(-c $(COMMAND_SCRIPT))
- }
- spec = { containers: [container], restartPolicy: 'Never' }
-
- ::Kubeclient::Resource.new(metadata: metadata, spec: spec)
- end
-
- def generate_pod_env(command)
- {
- HELM_VERSION: HELM_VERSION,
- TILLER_NAMESPACE: @namespace.name,
- COMMAND_SCRIPT: generate_script(command)
- }.map { |key, value| { name: key, value: value } }
- end
-
- def generate_script(command)
- [
- INSTALL_DEPS,
- helm_init_command(command),
- helm_install_command(command)
- ].join("\n")
- end
-
- def helm_init_command(command)
- if command.install_helm
- 'helm init >/dev/null'
- else
- 'helm init --client-only >/dev/null'
- end
- end
-
- def helm_install_command(command)
- return if command.chart.nil?
-
- "helm install #{command.chart} --name #{command.name} --namespace #{@namespace.name} >/dev/null"
- end
end
end
end
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
new file mode 100644
index 00000000000..ebd7dc1b100
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -0,0 +1,42 @@
+module Gitlab
+ module Kubernetes
+ module Helm
+ class Api
+ def initialize(kubeclient)
+ @kubeclient = kubeclient
+ @namespace = Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, kubeclient)
+ end
+
+ def install(command)
+ @namespace.ensure_exists!
+ @kubeclient.create_pod(pod_resource(command))
+ end
+
+ ##
+ # Returns Pod phase
+ #
+ # https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
+ #
+ # values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
+ #
+ def installation_status(pod_name)
+ @kubeclient.get_pod(pod_name, @namespace.name).status.phase
+ end
+
+ def installation_log(pod_name)
+ @kubeclient.get_pod_log(pod_name, @namespace.name).body
+ end
+
+ def delete_installation_pod!(pod_name)
+ @kubeclient.delete_pod(pod_name, @namespace.name)
+ end
+
+ private
+
+ def pod_resource(command)
+ Pod.new(command, @namespace.name, @kubeclient).generate
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
new file mode 100644
index 00000000000..8d8c441a4b1
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module Kubernetes
+ module Helm
+ class InstallCommand
+ attr_reader :name, :install_helm, :chart, :chart_values_file
+
+ def initialize(name, install_helm: false, chart: false, chart_values_file: false)
+ @name = name
+ @install_helm = install_helm
+ @chart = chart
+ @chart_values_file = chart_values_file
+ end
+
+ def pod_name
+ "install-#{name}"
+ end
+
+ def generate_script(namespace_name)
+ [
+ install_dps_command,
+ init_command,
+ complete_command(namespace_name)
+ ].join("\n")
+ end
+
+ private
+
+ def init_command
+ if install_helm
+ 'helm init >/dev/null'
+ else
+ 'helm init --client-only >/dev/null'
+ end
+ end
+
+ def complete_command(namespace_name)
+ return unless chart
+
+ "helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
+ end
+
+ def install_dps_command
+ <<~HEREDOC
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+ HEREDOC
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
new file mode 100644
index 00000000000..233f6bf6227
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -0,0 +1,69 @@
+module Gitlab
+ module Kubernetes
+ module Helm
+ class Pod
+ def initialize(command, namespace_name, kubeclient)
+ @command = command
+ @namespace_name = namespace_name
+ @kubeclient = kubeclient
+ end
+
+ def generate
+ spec = { containers: [container_specification], restartPolicy: 'Never' }
+ if command.chart_values_file
+ generate_config_map
+ spec['volumes'] = volumes_specification
+ end
+ ::Kubeclient::Resource.new(metadata: metadata, spec: spec)
+ end
+
+ private
+
+ attr_reader :command, :namespace_name, :kubeclient
+
+ def container_specification
+ container = {
+ name: 'helm',
+ image: 'alpine:3.6',
+ env: generate_pod_env(command),
+ command: %w(/bin/sh),
+ args: %w(-c $(COMMAND_SCRIPT))
+ }
+ container[:volumeMounts] = volume_mounts_specification if command.chart_values_file
+ container
+ end
+
+ def labels
+ { 'gitlab.org/action': 'install', 'gitlab.org/application': command.name }
+ end
+
+ def metadata
+ { name: command.pod_name, namespace: namespace_name, labels: labels }
+ end
+
+ def volume_mounts_specification
+ [{ name: 'config-volume', mountPath: '/etc/config' }]
+ end
+
+ def volumes_specification
+ [{ name: 'config-volume', configMap: { name: 'values-config' } }]
+ end
+
+ def generate_pod_env(command)
+ {
+ HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
+ TILLER_NAMESPACE: namespace_name,
+ COMMAND_SCRIPT: command.generate_script(namespace_name)
+ }.map { |key, value| { name: key, value: value } }
+ end
+
+ def generate_config_map
+ resource = ::Kubeclient::Resource.new
+ resource.metadata = { name: 'values-config', namespace: namespace_name }
+ resource.data = YAML.load_file(command.chart_values_file)
+ kubeclient.create_config_map(resource)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 6ea132fc5bf..877cebf6786 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -38,6 +38,7 @@ module Gitlab
# This is memoized since this method is called for every instrumented
# method. Loading data from an external cache on every method call slows
# things down too much.
+ # in milliseconds
@method_call_threshold ||= settings[:method_call_threshold]
end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 9112164f22e..329b07af5db 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -35,8 +35,8 @@ module Gitlab
@transaction = transaction
@name = name
@labels = { module: @module_name, method: @method_name }
- @real_time = 0
- @cpu_time = 0
+ @real_time = 0.0
+ @cpu_time = 0.0
@call_count = 0
end
@@ -54,7 +54,7 @@ module Gitlab
@call_count += 1
if call_measurement_enabled? && above_threshold?
- self.class.call_duration_histogram.observe(@transaction.labels.merge(labels), real_time / 1000.0)
+ self.class.call_duration_histogram.observe(@transaction.labels.merge(labels), real_time)
end
retval
@@ -65,8 +65,8 @@ module Gitlab
Metric.new(
Instrumentation.series,
{
- duration: real_time,
- cpu_duration: cpu_time,
+ duration: real_time.in_milliseconds.to_i,
+ cpu_duration: cpu_time.in_milliseconds.to_i,
call_count: call_count
},
method: @name
@@ -76,7 +76,7 @@ module Gitlab
# Returns true if the total runtime of this method exceeds the method call
# threshold.
def above_threshold?
- real_time >= Metrics.method_call_threshold
+ real_time.in_milliseconds >= Metrics.method_call_threshold
end
def call_measurement_enabled?
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index b68800417a2..4e1ea62351f 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -52,7 +52,7 @@ module Gitlab
metrics[:memory_usage].set(labels, System.memory_usage)
metrics[:file_descriptors].set(labels, System.file_descriptor_count)
- metrics[:sampler_duration].observe(labels.merge(worker_label), (System.monotonic_time - start_time) / 1000.0)
+ metrics[:sampler_duration].observe(labels.merge(worker_label), System.monotonic_time - start_time)
ensure
GC::Profiler.clear
end
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index efd3c9daf79..250897a79c2 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -66,7 +66,7 @@ module Gitlab
:gitlab_cache_operation_duration_seconds,
'Cache access time',
Transaction::BASE_LABELS.merge({ action: nil }),
- [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
+ [0.001, 0.01, 0.1, 1, 10]
)
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index c2cbd3c16a1..e60e245cf89 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -35,27 +35,27 @@ module Gitlab
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process
- .clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
+ .clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
end
else
def self.cpu_time
Process
- .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
+ .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
end
end
# Returns the current real time in a given precision.
#
- # Returns the time as a Fixnum.
- def self.real_time(precision = :millisecond)
+ # Returns the time as a Float for precision = :float_second.
+ def self.real_time(precision = :float_second)
Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end
- # Returns the current monotonic clock time in a given precision.
+ # Returns the current monotonic clock time as seconds with microseconds precision.
#
- # Returns the time as a Fixnum.
- def self.monotonic_time(precision = :millisecond)
- Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
+ # Returns the time as a Float.
+ def self.monotonic_time
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index ee3afc5ffdb..e7975c023a9 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -35,6 +35,10 @@ module Gitlab
@finished_at ? (@finished_at - @started_at) : 0.0
end
+ def duration_milliseconds
+ duration.in_milliseconds.to_i
+ end
+
def allocated_memory
@memory_after - @memory_before
end
@@ -50,7 +54,7 @@ module Gitlab
@memory_after = System.memory_usage
@finished_at = System.monotonic_time
- self.class.metric_transaction_duration_seconds.observe(labels, duration * 1000)
+ self.class.metric_transaction_duration_seconds.observe(labels, duration)
self.class.metric_transaction_allocated_memory_bytes.observe(labels, allocated_memory * 1024.0)
Thread.current[THREAD_KEY] = nil
@@ -97,7 +101,7 @@ module Gitlab
end
def track_self
- values = { duration: duration, allocated_memory: allocated_memory }
+ values = { duration: duration_milliseconds, allocated_memory: allocated_memory }
@values.each do |name, value|
values[name] = value
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index fef9d3e31d4..7037e2e61cc 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -27,10 +27,17 @@ module Gitlab
# It allows us to search only for projects user has access to
attr_reader :limit_projects
- def initialize(current_user, limit_projects, query)
+ # Whether a custom filter is used to restrict scope of projects.
+ # If the default filter (which lists all projects user has access to)
+ # is used, we can skip it when filtering merge requests and optimize the
+ # query
+ attr_reader :default_project_filter
+
+ def initialize(current_user, limit_projects, query, default_project_filter: false)
@current_user = current_user
@limit_projects = limit_projects || Project.all
@query = query
+ @default_project_filter = default_project_filter
end
def objects(scope, page = nil)
@@ -94,7 +101,11 @@ module Gitlab
end
def merge_requests
- merge_requests = MergeRequestsFinder.new(current_user).execute.in_projects(project_ids_relation)
+ merge_requests = MergeRequestsFinder.new(current_user).execute
+ unless default_project_filter
+ merge_requests = merge_requests.in_projects(project_ids_relation)
+ end
+
merge_requests =
if query =~ /[#!](\d+)\z/
merge_requests.where(iid: $1)
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index 9cc986535e1..c9e3eed82f2 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -6,6 +6,7 @@ require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_route
require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
+require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
@@ -15,4 +16,5 @@ task setup_postgresql: :environment do
IndexRoutesPathForLike.new.up
AddLowerPathIndexToRedirectRoutes.new.up
IndexRedirectRoutesPathForLike.new.up
+ AddIndexOnNamespacesLowerName.new.up
end
diff --git a/package.json b/package.json
index a5bf2309a0f..d80e25e1ac6 100644
--- a/package.json
+++ b/package.json
@@ -79,6 +79,7 @@
"vue": "^2.5.8",
"vue-loader": "^13.5.0",
"vue-resource": "^1.3.4",
+ "vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.8",
"vuex": "^3.0.1",
"webpack": "^3.5.5",
@@ -87,7 +88,7 @@
"worker-loader": "^1.1.0"
},
"devDependencies": {
- "@gitlab-org/gitlab-svgs": "^1.3.0",
+ "@gitlab-org/gitlab-svgs": "^1.4.0",
"babel-plugin-istanbul": "^4.1.5",
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
diff --git a/qa/qa.rb b/qa/qa.rb
index f473686d344..340f5e35c67 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -17,6 +17,8 @@ module QA
#
module Factory
autoload :Base, 'qa/factory/base'
+ autoload :Dependency, 'qa/factory/dependency'
+ autoload :Product, 'qa/factory/product'
module Resource
autoload :Sandbox, 'qa/factory/resource/sandbox'
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index 7b951a99b69..00851a7bece 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -1,15 +1,34 @@
module QA
module Factory
class Base
+ def fabricate!(*_args)
+ raise NotImplementedError
+ end
+
def self.fabricate!(*args)
- new.tap do |factory|
+ Factory::Product.populate!(new) do |factory|
yield factory if block_given?
- return factory.fabricate!(*args)
+
+ dependencies.each do |name, signature|
+ Factory::Dependency.new(name, factory, signature).build!
+ end
+
+ factory.fabricate!(*args)
end
end
- def fabricate!(*_args)
- raise NotImplementedError
+ def self.dependencies
+ @dependencies ||= {}
+ end
+
+ def self.dependency(factory, as:, &block)
+ as.tap do |name|
+ class_eval { attr_accessor name }
+
+ Dependency::Signature.new(factory, block).tap do |signature|
+ dependencies.store(name, signature)
+ end
+ end
end
end
end
diff --git a/qa/qa/factory/dependency.rb b/qa/qa/factory/dependency.rb
new file mode 100644
index 00000000000..d0e85a68237
--- /dev/null
+++ b/qa/qa/factory/dependency.rb
@@ -0,0 +1,38 @@
+module QA
+ module Factory
+ class Dependency
+ Signature = Struct.new(:factory, :block)
+
+ def initialize(name, factory, signature)
+ @name = name
+ @factory = factory
+ @signature = signature
+ end
+
+ def overridden?
+ !!@factory.public_send(@name)
+ end
+
+ def build!
+ return if overridden?
+
+ Builder.new(@signature).fabricate!.tap do |product|
+ @factory.public_send("#{@name}=", product)
+ end
+ end
+
+ class Builder
+ def initialize(signature)
+ @factory = signature.factory
+ @block = signature.block
+ end
+
+ def fabricate!
+ @factory.fabricate! do |factory|
+ @block&.call(factory)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb
new file mode 100644
index 00000000000..df35bbbb443
--- /dev/null
+++ b/qa/qa/factory/product.rb
@@ -0,0 +1,26 @@
+require 'capybara/dsl'
+
+module QA
+ module Factory
+ class Product
+ include Capybara::DSL
+
+ def initialize(factory)
+ @factory = factory
+ @location = current_url
+ end
+
+ def visit!
+ visit @location
+ end
+
+ def self.populate!(factory)
+ raise ArgumentError unless block_given?
+
+ yield factory
+
+ new(factory)
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb
index 1d5375d8c76..2f4de4173d4 100644
--- a/qa/qa/factory/repository/push.rb
+++ b/qa/qa/factory/repository/push.rb
@@ -1,16 +1,13 @@
-require "pry-byebug"
-
module QA
module Factory
module Repository
class Push < Factory::Base
- PAGE_REGEX_CHECK =
- %r{\/#{Runtime::Namespace.sandbox_name}\/qa-test[^\/]+\/{1}[^\/]+\z}.freeze
+ attr_writer :file_name, :file_content, :commit_message, :branch_name
- attr_writer :file_name,
- :file_content,
- :commit_message,
- :branch_name
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-with-code'
+ project.description = 'Project with repository'
+ end
def initialize
@file_name = 'file.txt'
@@ -20,12 +17,10 @@ module QA
end
def fabricate!
+ project.visit!
+
Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do
- unless PAGE_REGEX_CHECK.match(current_path)
- raise "To perform this scenario the current page should be project show."
- end
-
choose_repository_clone_http
repository_location
end
diff --git a/qa/qa/factory/resource/group.rb b/qa/qa/factory/resource/group.rb
index a081cd94d39..9f13e26f35c 100644
--- a/qa/qa/factory/resource/group.rb
+++ b/qa/qa/factory/resource/group.rb
@@ -4,17 +4,29 @@ module QA
class Group < Factory::Base
attr_writer :path, :description
+ dependency Factory::Resource::Sandbox, as: :sandbox
+
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def fabricate!
- Page::Group::New.perform do |group|
- group.set_path(@path)
- group.set_description(@description)
- group.set_visibility('Private')
- group.create
+ sandbox.visit!
+
+ Page::Group::Show.perform do |page|
+ if page.has_subgroup?(@path)
+ page.go_to_subgroup(@path)
+ else
+ page.go_to_new_subgroup
+
+ Page::Group::New.perform do |group|
+ group.set_path(@path)
+ group.set_description(@description)
+ group.set_visibility('Private')
+ group.create
+ end
+ end
end
end
end
diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb
index 64fcfb084bb..07c2e3086d1 100644
--- a/qa/qa/factory/resource/project.rb
+++ b/qa/qa/factory/resource/project.rb
@@ -6,26 +6,17 @@ module QA
class Project < Factory::Base
attr_writer :description
+ dependency Factory::Resource::Group, as: :group
+
def name=(name)
@name = "#{name}-#{SecureRandom.hex(8)}"
+ @description = 'My awesome project'
end
def fabricate!
- Factory::Resource::Sandbox.fabricate!
-
- Page::Group::Show.perform do |page|
- if page.has_subgroup?(Runtime::Namespace.name)
- page.go_to_subgroup(Runtime::Namespace.name)
- else
- page.go_to_new_subgroup
+ group.visit!
- Factory::Resource::Group.fabricate! do |group|
- group.path = Runtime::Namespace.name
- end
- end
-
- page.go_to_new_project
- end
+ Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb
index fd2177915c5..558da1c973b 100644
--- a/qa/qa/factory/resource/sandbox.rb
+++ b/qa/qa/factory/resource/sandbox.rb
@@ -6,18 +6,24 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
+ def initialize
+ @name = Runtime::Namespace.sandbox_name
+ end
+
def fabricate!
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
- if page.has_group?(Runtime::Namespace.sandbox_name)
- page.go_to_group(Runtime::Namespace.sandbox_name)
+ if page.has_group?(@name)
+ page.go_to_group(@name)
else
page.go_to_new_group
- Resource::Group.fabricate! do |group|
- group.path = Runtime::Namespace.sandbox_name
- group.description = 'GitLab QA Sandbox'
+ Page::Group::New.perform do |group|
+ group.set_path(@name)
+ group.set_description('GitLab QA Sandbox')
+ group.set_visibility('Private')
+ group.create
end
end
end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 59cd147e055..8f999511d58 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -23,7 +23,7 @@ module QA
def password=(pass)
@password = pass
- @uri.password = CGI.escape(pass)
+ @uri.password = CGI.escape(pass).gsub('+', '%20')
end
def use_default_credentials
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 100e71ae157..0a16c07d64b 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -21,6 +21,7 @@ module QA
find('.dropdown-toggle').click
find("li[data-value='new-subgroup']").click
end
+
find("input[data-action='new-subgroup']").click
end
@@ -29,6 +30,7 @@ module QA
find('.dropdown-toggle').click
find("li[data-value='new-project']").click
end
+
find("input[data-action='new-project']").click
end
end
diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb
index e47c769b015..4f6ffe14c9f 100644
--- a/qa/qa/specs/features/repository/push_spec.rb
+++ b/qa/qa/specs/features/repository/push_spec.rb
@@ -5,15 +5,10 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::Project.fabricate! do |scenario|
- scenario.name = 'project_with_code'
- scenario.description = 'project with repository'
- end
-
- Factory::Repository::Push.fabricate! do |scenario|
- scenario.file_name = 'README.md'
- scenario.file_content = '# This is test project'
- scenario.commit_message = 'Add README.md'
+ Factory::Repository::Push.fabricate! do |push|
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
end
Page::Project::Show.act do
@@ -22,7 +17,7 @@ module QA
end
expect(page).to have_content('README.md')
- expect(page).to have_content('This is test project')
+ expect(page).to have_content('This is a test project')
end
end
end
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
new file mode 100644
index 00000000000..a3ba0176819
--- /dev/null
+++ b/qa/spec/factory/base_spec.rb
@@ -0,0 +1,88 @@
+describe QA::Factory::Base do
+ describe '.fabricate!' do
+ subject { Class.new(described_class) }
+ let(:factory) { spy('factory') }
+ let(:product) { spy('product') }
+
+ before do
+ allow(QA::Factory::Product).to receive(:new).and_return(product)
+ end
+
+ it 'instantiates the factory and calls factory method' do
+ expect(subject).to receive(:new).and_return(factory)
+
+ subject.fabricate!('something')
+
+ expect(factory).to have_received(:fabricate!).with('something')
+ end
+
+ it 'returns fabrication product' do
+ allow(subject).to receive(:new).and_return(factory)
+ allow(factory).to receive(:fabricate!).and_return('something')
+
+ result = subject.fabricate!('something')
+
+ expect(result).to eq product
+ end
+
+ it 'yields factory before calling factory method' do
+ allow(subject).to receive(:new).and_return(factory)
+
+ subject.fabricate! do |factory|
+ factory.something!
+ end
+
+ expect(factory).to have_received(:something!).ordered
+ expect(factory).to have_received(:fabricate!).ordered
+ end
+ end
+
+ describe '.dependency' do
+ let(:dependency) { spy('dependency') }
+
+ before do
+ stub_const('Some::MyDependency', dependency)
+ end
+
+ subject do
+ Class.new(described_class) do
+ dependency Some::MyDependency, as: :mydep do |factory|
+ factory.something!
+ end
+ end
+ end
+
+ it 'appends a new dependency and accessors' do
+ expect(subject.dependencies).to be_one
+ end
+
+ it 'defines dependency accessors' do
+ expect(subject.new).to respond_to :mydep, :mydep=
+ end
+ end
+
+ describe 'building dependencies' do
+ let(:dependency) { double('dependency') }
+ let(:instance) { spy('instance') }
+
+ subject do
+ Class.new(described_class) do
+ dependency Some::MyDependency, as: :mydep
+ end
+ end
+
+ before do
+ stub_const('Some::MyDependency', dependency)
+
+ allow(subject).to receive(:new).and_return(instance)
+ allow(instance).to receive(:mydep).and_return(nil)
+ allow(QA::Factory::Product).to receive(:new)
+ end
+
+ it 'builds all dependencies first' do
+ expect(dependency).to receive(:fabricate!).once
+
+ subject.fabricate!
+ end
+ end
+end
diff --git a/qa/spec/factory/dependency_spec.rb b/qa/spec/factory/dependency_spec.rb
new file mode 100644
index 00000000000..32405415126
--- /dev/null
+++ b/qa/spec/factory/dependency_spec.rb
@@ -0,0 +1,59 @@
+describe QA::Factory::Dependency do
+ let(:dependency) { spy('dependency' ) }
+ let(:factory) { spy('factory') }
+ let(:block) { spy('block') }
+
+ let(:signature) do
+ double('signature', factory: dependency, block: block)
+ end
+
+ subject do
+ described_class.new(:mydep, factory, signature)
+ end
+
+ describe '#overridden?' do
+ it 'returns true if factory has overridden dependency' do
+ allow(factory).to receive(:mydep).and_return('something')
+
+ expect(subject).to be_overridden
+ end
+
+ it 'returns false if dependency has not been overridden' do
+ allow(factory).to receive(:mydep).and_return(nil)
+
+ expect(subject).not_to be_overridden
+ end
+ end
+
+ describe '#build!' do
+ context 'when dependency has been overridden' do
+ before do
+ allow(subject).to receive(:overridden?).and_return(true)
+ end
+
+ it 'does not fabricate dependency' do
+ subject.build!
+
+ expect(dependency).not_to have_received(:fabricate!)
+ end
+ end
+
+ context 'when dependency has not been overridden' do
+ before do
+ allow(subject).to receive(:overridden?).and_return(false)
+ end
+
+ it 'fabricates dependency' do
+ subject.build!
+
+ expect(dependency).to have_received(:fabricate!)
+ end
+
+ it 'sets product in the factory' do
+ subject.build!
+
+ expect(factory).to have_received(:mydep=).with(dependency)
+ end
+ end
+ end
+end
diff --git a/qa/spec/factory/product_spec.rb b/qa/spec/factory/product_spec.rb
new file mode 100644
index 00000000000..3d9e86a641b
--- /dev/null
+++ b/qa/spec/factory/product_spec.rb
@@ -0,0 +1,44 @@
+describe QA::Factory::Product do
+ let(:factory) { spy('factory') }
+ let(:product) { spy('product') }
+
+ describe '.populate!' do
+ it 'instantiates and yields factory' do
+ expect(described_class).to receive(:new).with(factory)
+
+ described_class.populate!(factory) do |instance|
+ instance.something = 'string'
+ end
+
+ expect(factory).to have_received(:something=).with('string')
+ end
+
+ it 'returns a fabrication product' do
+ expect(described_class).to receive(:new)
+ .with(factory).and_return(product)
+
+ result = described_class.populate!(factory) do |instance|
+ instance.something = 'string'
+ end
+
+ expect(result).to be product
+ end
+
+ it 'raises unless block given' do
+ expect { described_class.populate!(factory) }
+ .to raise_error ArgumentError
+ end
+ end
+
+ describe '.visit!' do
+ it 'makes it possible to visit fabrication product' do
+ allow_any_instance_of(described_class)
+ .to receive(:current_url).and_return('some url')
+ allow_any_instance_of(described_class)
+ .to receive(:visit).and_return('visited some url')
+
+ expect(described_class.new(factory).visit!)
+ .to eq 'visited some url'
+ end
+ end
+end
diff --git a/rubocop/cop/active_record_dependent.rb b/rubocop/cop/active_record_dependent.rb
deleted file mode 100644
index 8d15f150885..00000000000
--- a/rubocop/cop/active_record_dependent.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require_relative '../model_helpers'
-
-module RuboCop
- module Cop
- # Cop that prevents the use of `dependent: ...` in ActiveRecord models.
- class ActiveRecordDependent < RuboCop::Cop::Cop
- include ModelHelpers
-
- MSG = 'Do not use `dependent: to remove associated data, ' \
- 'use foreign keys with cascading deletes instead'.freeze
-
- METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze
-
- def on_send(node)
- return unless in_model?(node)
- return unless METHOD_NAMES.include?(node.children[1])
-
- node.children.last.each_node(:pair) do |pair|
- key_name = pair.children[0].children[0]
-
- add_offense(pair, :expression) if key_name == :dependent
- end
- end
- end
- end
-end
diff --git a/rubocop/cop/active_record_serialize.rb b/rubocop/cop/active_record_serialize.rb
deleted file mode 100644
index 204caf37f8b..00000000000
--- a/rubocop/cop/active_record_serialize.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require_relative '../model_helpers'
-
-module RuboCop
- module Cop
- # Cop that prevents the use of `serialize` in ActiveRecord models.
- class ActiveRecordSerialize < RuboCop::Cop::Cop
- include ModelHelpers
-
- MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze
-
- def on_send(node)
- return unless in_model?(node)
-
- add_offense(node, :selector) if node.children[1] == :serialize
- end
- end
- end
-end
diff --git a/rubocop/cop/custom_error_class.rb b/rubocop/cop/custom_error_class.rb
deleted file mode 100644
index 38d93acfe88..00000000000
--- a/rubocop/cop/custom_error_class.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-module RuboCop
- module Cop
- # This cop makes sure that custom error classes, when empty, are declared
- # with Class.new.
- #
- # @example
- # # bad
- # class FooError < StandardError
- # end
- #
- # # okish
- # class FooError < StandardError; end
- #
- # # good
- # FooError = Class.new(StandardError)
- class CustomErrorClass < RuboCop::Cop::Cop
- MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze
-
- def on_class(node)
- _klass, parent, body = node.children
-
- return if body
-
- parent_klass = class_name_from_node(parent)
-
- return unless parent_klass && parent_klass.to_s.end_with?('Error')
-
- add_offense(node, :expression)
- end
-
- def autocorrect(node)
- klass, parent, _body = node.children
- replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
-
- lambda do |corrector|
- corrector.replace(node.source_range, replacement)
- end
- end
-
- private
-
- # The nested constant `Foo::Bar::Baz` looks like:
- #
- # s(:const,
- # s(:const,
- # s(:const, nil, :Foo), :Bar), :Baz)
- #
- # So recurse through that to get the name as written in the source.
- #
- def class_name_from_node(node, suffix = nil)
- return unless node&.type == :const
-
- name = node.children[1].to_s
- name = "#{name}::#{suffix}" if suffix
-
- if node.children[0]
- class_name_from_node(node.children[0], name)
- else
- name
- end
- end
- end
- end
-end
diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb
deleted file mode 100644
index e157d8e0791..00000000000
--- a/rubocop/cop/gem_fetcher.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module RuboCop
- module Cop
- # This cop prevents usage of the `git` and `github` arguments to `gem` in a
- # `Gemfile` in order to avoid additional points of failure beyond
- # rubygems.org.
- class GemFetcher < RuboCop::Cop::Cop
- MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'.freeze
-
- GIT_KEYS = [:git, :github].freeze
-
- def on_send(node)
- return unless gemfile?(node)
-
- func_name = node.children[1]
- return unless func_name == :gem
-
- node.children.last.each_node(:pair) do |pair|
- key_name = pair.children[0].children[0].to_sym
- if GIT_KEYS.include?(key_name)
- add_offense(node, pair.source_range, MSG)
- end
- end
- end
-
- private
-
- def gemfile?(node)
- node
- .location
- .expression
- .source_buffer
- .name
- .end_with?("Gemfile")
- end
- end
- end
-end
diff --git a/rubocop/cop/gitlab/module_with_instance_variables.rb b/rubocop/cop/gitlab/module_with_instance_variables.rb
index 5c9cde98512..dd8bd2dfdf0 100644
--- a/rubocop/cop/gitlab/module_with_instance_variables.rb
+++ b/rubocop/cop/gitlab/module_with_instance_variables.rb
@@ -30,12 +30,12 @@ module RuboCop
if only_ivar_or_assignment?(definition)
# We don't allow if any other ivar is used
definition.each_descendant(:ivar) do |offense|
- add_offense(offense, :expression)
+ add_offense(offense, location: :expression)
end
# We allow initialize method and single ivar
elsif !initialize_method?(definition) && !single_ivar?(definition)
definition.each_descendant(:ivar, :ivasgn) do |offense|
- add_offense(offense, :expression)
+ add_offense(offense, location: :expression)
end
end
end
diff --git a/rubocop/cop/in_batches.rb b/rubocop/cop/in_batches.rb
deleted file mode 100644
index c0240187e66..00000000000
--- a/rubocop/cop/in_batches.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require_relative '../model_helpers'
-
-module RuboCop
- module Cop
- # Cop that prevents the use of `in_batches`
- class InBatches < RuboCop::Cop::Cop
- MSG = 'Do not use `in_batches`, use `each_batch` from the EachBatch module instead'.freeze
-
- def on_send(node)
- return unless node.children[1] == :in_batches
-
- add_offense(node, :selector)
- end
- end
- end
-end
diff --git a/rubocop/cop/include_sidekiq_worker.rb b/rubocop/cop/include_sidekiq_worker.rb
index 4a6332286a2..8da4a147219 100644
--- a/rubocop/cop/include_sidekiq_worker.rb
+++ b/rubocop/cop/include_sidekiq_worker.rb
@@ -9,14 +9,14 @@ module RuboCop
MSG = 'Include `ApplicationWorker`, not `Sidekiq::Worker`.'.freeze
def_node_matcher :includes_sidekiq_worker?, <<~PATTERN
- (send nil :include (const (const nil :Sidekiq) :Worker))
+ (send nil? :include (const (const nil? :Sidekiq) :Worker))
PATTERN
def on_send(node)
return if in_spec?(node)
return unless includes_sidekiq_worker?(node)
- add_offense(node.arguments.first, :expression)
+ add_offense(node.arguments.first, location: :expression)
end
def autocorrect(node)
diff --git a/rubocop/cop/line_break_after_guard_clauses.rb b/rubocop/cop/line_break_after_guard_clauses.rb
deleted file mode 100644
index 67477f064ab..00000000000
--- a/rubocop/cop/line_break_after_guard_clauses.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# frozen_string_literal: true
-
-module RuboCop
- module Cop
- # Ensures a line break after guard clauses.
- #
- # @example
- # # bad
- # return unless condition
- # do_stuff
- #
- # # good
- # return unless condition
- #
- # do_stuff
- #
- # # bad
- # raise if condition
- # do_stuff
- #
- # # good
- # raise if condition
- #
- # do_stuff
- #
- # Multiple guard clauses are allowed without
- # line break.
- #
- # # good
- # return unless condition_a
- # return unless condition_b
- #
- # do_stuff
- #
- # Guard clauses in case statement are allowed without
- # line break.
- #
- # # good
- # case model
- # when condition_a
- # return true unless condition_b
- # when
- # ...
- # end
- #
- # Guard clauses before end are allowed without
- # line break.
- #
- # # good
- # if condition_a
- # do_something
- # else
- # do_something_else
- # return unless condition
- # end
- #
- # do_something_more
- class LineBreakAfterGuardClauses < RuboCop::Cop::Cop
- MSG = 'Add a line break after guard clauses'
-
- def_node_matcher :guard_clause_node?, <<-PATTERN
- [{(send nil? {:raise :fail :throw} ...) return break next} single_line?]
- PATTERN
-
- def on_if(node)
- return unless node.single_line?
- return unless guard_clause?(node)
- return if next_line(node).blank? || clause_last_line?(next_line(node)) || guard_clause?(next_sibling(node))
-
- add_offense(node, :expression, MSG)
- end
-
- def autocorrect(node)
- lambda do |corrector|
- corrector.insert_after(node.loc.expression, "\n")
- end
- end
-
- private
-
- def guard_clause?(node)
- return false unless node.if_type?
-
- guard_clause_node?(node.if_branch)
- end
-
- def next_sibling(node)
- node.parent.children[node.sibling_index + 1]
- end
-
- def next_line(node)
- processed_source[node.loc.line]
- end
-
- def clause_last_line?(line)
- line =~ /^\s*(?:end|elsif|else|when|rescue|ensure)/
- end
- end
- end
-end
diff --git a/rubocop/cop/migration/add_column.rb b/rubocop/cop/migration/add_column.rb
index d2cf36c454a..2530d6477e8 100644
--- a/rubocop/cop/migration/add_column.rb
+++ b/rubocop/cop/migration/add_column.rb
@@ -29,7 +29,7 @@ module RuboCop
opts.each_node(:pair) do |pair|
if hash_key_type(pair) == :sym && hash_key_name(pair) == :default
- add_offense(node, :selector)
+ add_offense(node, location: :selector)
end
end
end
diff --git a/rubocop/cop/migration/add_concurrent_foreign_key.rb b/rubocop/cop/migration/add_concurrent_foreign_key.rb
index d1fc94d55be..d78c7b9b043 100644
--- a/rubocop/cop/migration/add_concurrent_foreign_key.rb
+++ b/rubocop/cop/migration/add_concurrent_foreign_key.rb
@@ -15,7 +15,7 @@ module RuboCop
name = node.children[1]
- add_offense(node, :selector) if name == :add_foreign_key
+ add_offense(node, location: :selector) if name == :add_foreign_key
end
def method_name(node)
diff --git a/rubocop/cop/migration/add_concurrent_index.rb b/rubocop/cop/migration/add_concurrent_index.rb
index 69852f4d580..a2e4ac72565 100644
--- a/rubocop/cop/migration/add_concurrent_index.rb
+++ b/rubocop/cop/migration/add_concurrent_index.rb
@@ -21,7 +21,7 @@ module RuboCop
node.each_ancestor(:def) do |def_node|
next unless method_name(def_node) == :change
- add_offense(def_node, :name)
+ add_offense(def_node, location: :name)
end
end
diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb
index fa21a0d6555..4aea3c0cce3 100644
--- a/rubocop/cop/migration/add_index.rb
+++ b/rubocop/cop/migration/add_index.rb
@@ -27,7 +27,7 @@ module RuboCop
# data in these tables yet.
next if new_tables.include?(first_arg)
- add_offense(send_node, :selector)
+ add_offense(send_node, location: :selector)
end
end
diff --git a/rubocop/cop/migration/add_timestamps.rb b/rubocop/cop/migration/add_timestamps.rb
index 08ddd91e54d..ba32d6a9960 100644
--- a/rubocop/cop/migration/add_timestamps.rb
+++ b/rubocop/cop/migration/add_timestamps.rb
@@ -13,7 +13,7 @@ module RuboCop
def on_send(node)
return unless in_migration?(node)
- add_offense(node, :selector) if method_name(node) == :add_timestamps
+ add_offense(node, location: :selector) if method_name(node) == :add_timestamps
end
def method_name(node)
diff --git a/rubocop/cop/migration/datetime.rb b/rubocop/cop/migration/datetime.rb
index 9cba3c35b26..03ad3f3f601 100644
--- a/rubocop/cop/migration/datetime.rb
+++ b/rubocop/cop/migration/datetime.rb
@@ -17,7 +17,7 @@ module RuboCop
method_name = node.children[1]
if method_name == :datetime || method_name == :timestamp
- add_offense(send_node, :selector, format(MSG, method_name))
+ add_offense(send_node, location: :selector, message: format(MSG, method_name))
end
end
end
@@ -32,7 +32,7 @@ module RuboCop
last_argument = descendant.children.last
if last_argument == :datetime || last_argument == :timestamp
- add_offense(node, :expression, format(MSG, last_argument))
+ add_offense(node, location: :expression, message: format(MSG, last_argument))
end
end
end
diff --git a/rubocop/cop/migration/hash_index.rb b/rubocop/cop/migration/hash_index.rb
index 2cc59691d84..3206b73bd3d 100644
--- a/rubocop/cop/migration/hash_index.rb
+++ b/rubocop/cop/migration/hash_index.rb
@@ -29,7 +29,7 @@ module RuboCop
hash_key_name(pair) == :using
if hash_key_value(pair).to_s == 'hash'
- add_offense(pair, :expression)
+ add_offense(pair, location: :expression)
end
end
end
diff --git a/rubocop/cop/migration/remove_column.rb b/rubocop/cop/migration/remove_column.rb
index e53eb2e07b2..fffb4ab7fab 100644
--- a/rubocop/cop/migration/remove_column.rb
+++ b/rubocop/cop/migration/remove_column.rb
@@ -20,7 +20,7 @@ module RuboCop
send_method = send_node.children[1]
if send_method == :remove_column
- add_offense(send_node, :selector)
+ add_offense(send_node, location: :selector)
end
end
end
diff --git a/rubocop/cop/migration/remove_concurrent_index.rb b/rubocop/cop/migration/remove_concurrent_index.rb
index 268c49865cb..2328740cf36 100644
--- a/rubocop/cop/migration/remove_concurrent_index.rb
+++ b/rubocop/cop/migration/remove_concurrent_index.rb
@@ -16,7 +16,7 @@ module RuboCop
return unless node.children[1] == :remove_concurrent_index
node.each_ancestor(:def) do |def_node|
- add_offense(def_node, :name) if method_name(def_node) == :change
+ add_offense(def_node, location: :name) if method_name(def_node) == :change
end
end
diff --git a/rubocop/cop/migration/remove_index.rb b/rubocop/cop/migration/remove_index.rb
index 613b35dd00d..4df3b1ba756 100644
--- a/rubocop/cop/migration/remove_index.rb
+++ b/rubocop/cop/migration/remove_index.rb
@@ -13,7 +13,7 @@ module RuboCop
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
- add_offense(send_node, :selector) if method_name(send_node) == :remove_index
+ add_offense(send_node, location: :selector) if method_name(send_node) == :remove_index
end
end
diff --git a/rubocop/cop/migration/reversible_add_column_with_default.rb b/rubocop/cop/migration/reversible_add_column_with_default.rb
index f413f06f39b..dd49188defa 100644
--- a/rubocop/cop/migration/reversible_add_column_with_default.rb
+++ b/rubocop/cop/migration/reversible_add_column_with_default.rb
@@ -9,7 +9,7 @@ module RuboCop
include MigrationHelpers
def_node_matcher :add_column_with_default?, <<~PATTERN
- (send nil :add_column_with_default $...)
+ (send nil? :add_column_with_default $...)
PATTERN
def_node_matcher :defines_change?, <<~PATTERN
@@ -26,7 +26,7 @@ module RuboCop
node.each_ancestor(:def) do |def_node|
next unless defines_change?(def_node)
- add_offense(def_node, :name)
+ add_offense(def_node, location: :name)
end
end
end
diff --git a/rubocop/cop/migration/safer_boolean_column.rb b/rubocop/cop/migration/safer_boolean_column.rb
index 0335c25d85d..dc5c55df6fb 100644
--- a/rubocop/cop/migration/safer_boolean_column.rb
+++ b/rubocop/cop/migration/safer_boolean_column.rb
@@ -28,7 +28,7 @@ module RuboCop
].freeze
def_node_matcher :add_column?, <<~PATTERN
- (send nil :add_column $...)
+ (send nil? :add_column $...)
PATTERN
def on_send(node)
@@ -54,7 +54,7 @@ module RuboCop
NULL_OFFENSE
end
- add_offense(node, :expression, format(offense, table)) if offense
+ add_offense(node, location: :expression, message: format(offense, table)) if offense
end
def no_default?(opts)
diff --git a/rubocop/cop/migration/timestamps.rb b/rubocop/cop/migration/timestamps.rb
index 71a9420cc3b..6cf5648b996 100644
--- a/rubocop/cop/migration/timestamps.rb
+++ b/rubocop/cop/migration/timestamps.rb
@@ -14,7 +14,7 @@ module RuboCop
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
- add_offense(send_node, :selector) if method_name(send_node) == :timestamps
+ add_offense(send_node, location: :selector) if method_name(send_node) == :timestamps
end
end
diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb
index 3f886cbfea3..db2b5564297 100644
--- a/rubocop/cop/migration/update_column_in_batches.rb
+++ b/rubocop/cop/migration/update_column_in_batches.rb
@@ -18,7 +18,7 @@ module RuboCop
spec_path = spec_filename(node)
unless File.exist?(File.expand_path(spec_path, rails_root))
- add_offense(node, :expression, format(MSG, spec_path))
+ add_offense(node, location: :expression, message: format(MSG, spec_path))
end
end
diff --git a/rubocop/cop/migration/update_large_table.rb b/rubocop/cop/migration/update_large_table.rb
index 3ae3fb1b68e..bb14d0f4f56 100644
--- a/rubocop/cop/migration/update_large_table.rb
+++ b/rubocop/cop/migration/update_large_table.rb
@@ -35,7 +35,7 @@ module RuboCop
].freeze
def_node_matcher :batch_update?, <<~PATTERN
- (send nil ${:add_column_with_default :update_column_in_batches} $(sym ...) ...)
+ (send nil? ${:add_column_with_default :update_column_in_batches} $(sym ...) ...)
PATTERN
def on_send(node)
@@ -49,7 +49,7 @@ module RuboCop
return unless LARGE_TABLES.include?(table)
- add_offense(node, :expression, format(MSG, update_method, table))
+ add_offense(node, location: :expression, message: format(MSG, update_method, table))
end
end
end
diff --git a/rubocop/cop/polymorphic_associations.rb b/rubocop/cop/polymorphic_associations.rb
deleted file mode 100644
index 7d554704550..00000000000
--- a/rubocop/cop/polymorphic_associations.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require_relative '../model_helpers'
-
-module RuboCop
- module Cop
- # Cop that prevents the use of polymorphic associations
- class PolymorphicAssociations < RuboCop::Cop::Cop
- include ModelHelpers
-
- MSG = 'Do not use polymorphic associations, use separate tables instead'.freeze
-
- def on_send(node)
- return unless in_model?(node)
- return unless node.children[1] == :belongs_to
-
- node.children.last.each_node(:pair) do |pair|
- key_name = pair.children[0].children[0]
-
- add_offense(pair, :expression) if key_name == :polymorphic
- end
- end
- end
- end
-end
diff --git a/rubocop/cop/project_path_helper.rb b/rubocop/cop/project_path_helper.rb
index 3e1ce71ac06..f3810622eb1 100644
--- a/rubocop/cop/project_path_helper.rb
+++ b/rubocop/cop/project_path_helper.rb
@@ -17,7 +17,7 @@ module RuboCop
return unless method_name(namespace_expr) == :namespace
return unless receiver(namespace_expr) == project_expr
- add_offense(node, :selector)
+ add_offense(node, location: :selector)
end
def autocorrect(node)
diff --git a/rubocop/cop/redirect_with_status.rb b/rubocop/cop/redirect_with_status.rb
deleted file mode 100644
index 36810642c88..00000000000
--- a/rubocop/cop/redirect_with_status.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module RuboCop
- module Cop
- # This cop prevents usage of 'redirect_to' in actions 'destroy' without specifying 'status'.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/31840
- class RedirectWithStatus < RuboCop::Cop::Cop
- MSG = 'Do not use "redirect_to" without "status" in "destroy" action'.freeze
-
- def on_def(node)
- return unless in_controller?(node)
- return unless destroy?(node) || destroy_all?(node)
-
- node.each_descendant(:send) do |def_node|
- next unless redirect_to?(def_node)
-
- methods = []
-
- def_node.children.last.each_node(:pair) do |pair|
- methods << pair.children.first.children.first
- end
-
- add_offense(def_node, :selector) unless methods.include?(:status)
- end
- end
-
- private
-
- def in_controller?(node)
- node.location.expression.source_buffer.name.end_with?('_controller.rb')
- end
-
- def destroy?(node)
- node.children.first == :destroy
- end
-
- def destroy_all?(node)
- node.children.first == :destroy_all
- end
-
- def redirect_to?(node)
- node.children[1] == :redirect_to
- end
- end
- end
-end
diff --git a/rubocop/cop/rspec/env_assignment.rb b/rubocop/cop/rspec/env_assignment.rb
index 257454af0e1..8b61fa8e264 100644
--- a/rubocop/cop/rspec/env_assignment.rb
+++ b/rubocop/cop/rspec/env_assignment.rb
@@ -1,4 +1,3 @@
-require 'rubocop-rspec'
require_relative '../../spec_helpers'
module RuboCop
@@ -17,7 +16,7 @@ module RuboCop
# before do
# stub_env('FOO', 'bar')
# end
- class EnvAssignment < Cop
+ class EnvAssignment < RuboCop::Cop::Cop
include SpecHelpers
MESSAGE = "Don't assign to ENV, use `stub_env` instead.".freeze
@@ -32,7 +31,7 @@ module RuboCop
return unless in_spec?(node)
return unless env_assignment?(node)
- add_offense(node, :expression, MESSAGE)
+ add_offense(node, location: :expression, message: MESSAGE)
end
def autocorrect(node)
diff --git a/rubocop/cop/rspec/single_line_hook.rb b/rubocop/cop/rspec/single_line_hook.rb
deleted file mode 100644
index be611054323..00000000000
--- a/rubocop/cop/rspec/single_line_hook.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'rubocop-rspec'
-
-module RuboCop
- module Cop
- module RSpec
- # This cop checks for single-line hook blocks
- #
- # @example
- #
- # # bad
- # before { do_something }
- # after(:each) { undo_something }
- #
- # # good
- # before do
- # do_something
- # end
- #
- # after(:each) do
- # undo_something
- # end
- class SingleLineHook < Cop
- MESSAGE = "Don't use single-line hook blocks.".freeze
-
- def_node_search :rspec_hook?, <<~PATTERN
- (send nil {:after :around :before} ...)
- PATTERN
-
- def on_block(node)
- return unless rspec_hook?(node)
- return unless node.single_line?
-
- add_offense(node, :expression, MESSAGE)
- end
- end
- end
- end
-end
diff --git a/rubocop/cop/rspec/verbose_include_metadata.rb b/rubocop/cop/rspec/verbose_include_metadata.rb
deleted file mode 100644
index 58390622d60..00000000000
--- a/rubocop/cop/rspec/verbose_include_metadata.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubocop-rspec'
-
-module RuboCop
- module Cop
- module RSpec
- # Checks for verbose include metadata used in the specs.
- #
- # @example
- # # bad
- # describe MyClass, js: true do
- # end
- #
- # # good
- # describe MyClass, :js do
- # end
- class VerboseIncludeMetadata < Cop
- MSG = 'Use `%s` instead of `%s`.'
-
- SELECTORS = %i[describe context feature example_group it specify example scenario its].freeze
-
- def_node_matcher :include_metadata, <<-PATTERN
- (send {(const nil :RSpec) nil} {#{SELECTORS.map(&:inspect).join(' ')}}
- !const
- ...
- (hash $...))
- PATTERN
-
- def_node_matcher :invalid_metadata?, <<-PATTERN
- (pair
- (sym $...)
- (true))
- PATTERN
-
- def on_send(node)
- invalid_metadata_matches(node) do |match|
- add_offense(node, :expression, format(MSG, good(match), bad(match)))
- end
- end
-
- def autocorrect(node)
- lambda do |corrector|
- invalid_metadata_matches(node) do |match|
- corrector.replace(match.loc.expression, good(match))
- end
- end
- end
-
- private
-
- def invalid_metadata_matches(node)
- include_metadata(node) do |matches|
- matches.select(&method(:invalid_metadata?)).each do |match|
- yield match
- end
- end
- end
-
- def bad(match)
- "#{metadata_key(match)}: true"
- end
-
- def good(match)
- ":#{metadata_key(match)}"
- end
-
- def metadata_key(match)
- match.children[0].source
- end
- end
- end
- end
-end
diff --git a/rubocop/cop/sidekiq_options_queue.rb b/rubocop/cop/sidekiq_options_queue.rb
index 43b35ba0214..253d2958866 100644
--- a/rubocop/cop/sidekiq_options_queue.rb
+++ b/rubocop/cop/sidekiq_options_queue.rb
@@ -9,7 +9,7 @@ module RuboCop
MSG = 'Do not manually set a queue; `ApplicationWorker` sets one automatically.'.freeze
def_node_matcher :sidekiq_options?, <<~PATTERN
- (send nil :sidekiq_options $...)
+ (send nil? :sidekiq_options $...)
PATTERN
def on_send(node)
@@ -19,7 +19,7 @@ module RuboCop
node.arguments.first.each_node(:pair) do |pair|
key_name = pair.key.children[0]
- add_offense(pair, :expression) if key_name == :queue
+ add_offense(pair, location: :expression) if key_name == :queue
end
end
end
diff --git a/rubocop/model_helpers.rb b/rubocop/model_helpers.rb
deleted file mode 100644
index 309723dc34c..00000000000
--- a/rubocop/model_helpers.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module RuboCop
- module ModelHelpers
- # Returns true if the given node originated from the models directory.
- def in_model?(node)
- path = node.location.expression.source_buffer.name
- models_path = File.join(Dir.pwd, 'app', 'models')
-
- path.start_with?(models_path)
- end
- end
-end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 8aa82e9413d..26087f15984 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,15 +1,6 @@
require_relative 'cop/active_record_dependent'
-require_relative 'cop/active_record_serialize'
-require_relative 'cop/custom_error_class'
-require_relative 'cop/gem_fetcher'
-require_relative 'cop/in_batches'
-require_relative 'cop/include_sidekiq_worker'
-require_relative 'cop/line_break_after_guard_clauses'
-require_relative 'cop/polymorphic_associations'
-require_relative 'cop/project_path_helper'
-require_relative 'cop/redirect_with_status'
require_relative 'cop/gitlab/module_with_instance_variables'
-require_relative 'cop/sidekiq_options_queue'
+require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
@@ -25,6 +16,8 @@ require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/migration/update_large_table'
+require_relative 'cop/project_path_helper'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/single_line_hook'
require_relative 'cop/rspec/verbose_include_metadata'
+require_relative 'cop/sidekiq_options_queue'
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index 8b460646059..99fdff5f846 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -52,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do
context 'when application is already installing' do
before do
- create(:cluster_applications_helm, :installing, cluster: cluster)
+ create(:clusters_applications_helm, :installing, cluster: cluster)
end
it 'returns 400' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 58116e6e0fe..45c424af8c4 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -98,6 +98,14 @@ describe Projects::MergeRequestsController do
expect(response).to match_response_schema('entities/merge_request_widget')
end
end
+
+ context 'when no serialiser was passed' do
+ it 'renders widget MR entity as json' do
+ go(serializer: nil, format: :json)
+
+ expect(response).to match_response_schema('entities/merge_request_widget')
+ end
+ end
end
describe "as diff" do
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index d82fa8e34aa..775fbb3d27b 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -1,5 +1,5 @@
FactoryBot.define do
- factory :cluster_applications_helm, class: Clusters::Applications::Helm do
+ factory :clusters_applications_helm, class: Clusters::Applications::Helm do
cluster factory: %i(cluster provided_by_gcp)
trait :not_installable do
@@ -31,5 +31,8 @@ FactoryBot.define do
installing
updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
end
+
+ factory :clusters_applications_ingress, class: Clusters::Applications::Ingress
+ factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus
end
end
diff --git a/spec/factories/clusters/applications/ingress.rb b/spec/factories/clusters/applications/ingress.rb
deleted file mode 100644
index 85f54a9d744..00000000000
--- a/spec/factories/clusters/applications/ingress.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-FactoryBot.define do
- factory :cluster_applications_ingress, class: Clusters::Applications::Ingress do
- cluster factory: %i(cluster provided_by_gcp)
-
- trait :not_installable do
- status(-2)
- end
-
- trait :installable do
- status 0
- end
-
- trait :scheduled do
- status 1
- end
-
- trait :installing do
- status 2
- end
-
- trait :installed do
- status 3
- end
-
- trait :errored do
- status(-1)
- status_reason 'something went wrong'
- end
-
- trait :timeouted do
- installing
- updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
- end
- end
-end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 5ed6b017dee..71dc169c6a2 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -14,6 +14,7 @@ FactoryBot.define do
trait :closed do
state :closed
+ closed_at { Time.now }
end
factory :closed_issue, traits: [:closed]
diff --git a/spec/features/merge_requests/image_diff_notes_spec.rb b/spec/features/merge_requests/image_diff_notes_spec.rb
index ddc73437917..b53570835cb 100644
--- a/spec/features/merge_requests/image_diff_notes_spec.rb
+++ b/spec/features/merge_requests/image_diff_notes_spec.rb
@@ -12,7 +12,8 @@ feature 'image diff notes', :js do
# Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara.
- allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png')
+ allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png')
+ allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.ico')
end
context 'create commit diff notes' do
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index f4c75a2f265..e17e9c2ccf5 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -66,6 +66,21 @@ describe 'Merge requests > User posts notes', :js do
end
end
+ describe 'when previewing a note' do
+ it 'shows the toolbar buttons when editing a note' do
+ page.within('.js-main-target-form') do
+ expect(page).to have_css('.md-header-toolbar.active')
+ end
+ end
+
+ it 'hides the toolbar buttons when previewing a note' do
+ find('.js-md-preview-button').click
+ page.within('.js-main-target-form') do
+ expect(page).not_to have_css('.md-header-toolbar.active')
+ end
+ end
+ end
+
describe 'when editing a note' do
it 'there should be a hidden edit form' do
is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1)
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index b34cd061ec6..9c4abec115f 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -73,7 +73,7 @@ feature 'Clusters Applications', :js do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil)
- create(:cluster_applications_helm, :installed, cluster: cluster)
+ create(:clusters_applications_helm, :installed, cluster: cluster)
page.within('.js-cluster-application-row-ingress') do
page.find(:css, '.js-cluster-application-install-button').click
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
deleted file mode 100644
index 33ccbc1a29f..00000000000
--- a/spec/features/projects/ref_switcher_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'rails_helper'
-
-feature 'Ref switcher', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
-
- before do
- project.team << [user, :master]
- set_cookie('new_repo', 'true')
- sign_in(user)
- visit project_tree_path(project, 'master')
- end
-
- it 'allow user to change ref by enter key' do
- click_button 'master'
- wait_for_requests
-
- page.within '.project-refs-form' do
- input = find('input[type="search"]')
- input.set 'binary'
- wait_for_requests
-
- expect(find('.dropdown-content ul')).to have_selector('li', count: 7)
-
- page.within '.dropdown-content ul' do
- input.native.send_keys :enter
- end
- end
-
- expect(page).to have_title 'add-pdf-text-binary'
- end
-
- it "user selects ref with special characters" do
- click_button 'master'
- wait_for_requests
-
- page.within '.project-refs-form' do
- page.fill_in 'Search branches and tags', with: "'test'"
- click_link "'test'"
- end
-
- expect(page).to have_title "'test'"
- end
-
- context "create branch" do
- let(:input) { find('.js-new-branch-name') }
-
- before do
- click_button 'master'
- wait_for_requests
-
- page.within '.project-refs-form' do
- find(".dropdown-footer-list a").click
- end
- end
-
- it "shows error message for the invalid branch name" do
- input.set 'foo bar'
- click_button('Create')
- wait_for_requests
- expect(page).to have_content 'Branch name is invalid'
- end
-
- it "should create new branch properly" do
- input.set 'new-branch-name'
- click_button('Create')
- wait_for_requests
- expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name'
- end
-
- it "should create new branch by Enter key" do
- input.set 'new-branch-name-2'
- input.native.send_keys :enter
- wait_for_requests
- expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name-2'
- end
- end
-end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 8f06328962e..3f6d16c8acf 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -13,6 +13,14 @@ feature 'Multi-file editor new directory', :js do
visit project_tree_path(project, :master)
wait_for_requests
+
+ click_link('Multi Edit')
+
+ wait_for_requests
+ end
+
+ after do
+ set_cookie('new_repo', 'false')
end
it 'creates directory in current directory' do
@@ -21,17 +29,29 @@ feature 'Multi-file editor new directory', :js do
click_link('New directory')
page.within('.modal') do
- find('.form-control').set('foldername')
+ find('.form-control').set('folder name')
click_button('Create directory')
end
+ find('.add-to-tree').click
+
+ click_link('New file')
+
+ page.within('.modal-dialog') do
+ find('.form-control').set('file name')
+
+ click_button('Create file')
+ end
+
+ wait_for_requests
+
find('.multi-file-commit-panel-collapse-btn').click
- fill_in('commit-message', with: 'commit message')
+ fill_in('commit-message', with: 'commit message ide')
click_button('Commit')
- expect(page).to have_selector('td', text: 'commit message')
+ expect(page).to have_content('folder name')
end
end
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index bdebc12ef47..ba71eef07f4 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -13,6 +13,14 @@ feature 'Multi-file editor new file', :js do
visit project_tree_path(project, :master)
wait_for_requests
+
+ click_link('Multi Edit')
+
+ wait_for_requests
+ end
+
+ after do
+ set_cookie('new_repo', 'false')
end
it 'creates file in current directory' do
@@ -21,17 +29,19 @@ feature 'Multi-file editor new file', :js do
click_link('New file')
page.within('.modal') do
- find('.form-control').set('filename')
+ find('.form-control').set('file name')
click_button('Create file')
end
+ wait_for_requests
+
find('.multi-file-commit-panel-collapse-btn').click
- fill_in('commit-message', with: 'commit message')
+ fill_in('commit-message', with: 'commit message ide')
click_button('Commit')
- expect(page).to have_selector('td', text: 'commit message')
+ expect(page).to have_content('file name')
end
end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index d4e57d1ecfa..9fbb1dbd0e8 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -15,6 +15,14 @@ feature 'Multi-file editor upload file', :js do
visit project_tree_path(project, :master)
wait_for_requests
+
+ click_link('Multi Edit')
+
+ wait_for_requests
+ end
+
+ after do
+ set_cookie('new_repo', 'false')
end
it 'uploads text file' do
@@ -41,6 +49,5 @@ feature 'Multi-file editor upload file', :js do
expect(page).to have_selector('.multi-file-tab', text: 'dk.png')
expect(page).not_to have_selector('.monaco-editor')
- expect(page).to have_content('The source could not be displayed for this temporary file.')
end
end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index 074914420a1..ae050f36b4a 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -73,6 +73,41 @@ describe GroupDescendantsFinder do
expect(finder.execute).to contain_exactly(matching_project)
end
end
+
+ context 'sorting by name' do
+ let!(:project1) { create(:project, namespace: group, name: 'a', path: 'project-a') }
+ let!(:project2) { create(:project, namespace: group, name: 'z', path: 'project-z') }
+ let(:params) do
+ {
+ sort: 'name_asc'
+ }
+ end
+
+ it 'sorts elements by name' do
+ expect(subject.execute).to eq(
+ [
+ project1,
+ project2
+ ]
+ )
+ end
+
+ context 'with nested groups', :nested_groups do
+ let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
+ let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
+
+ it 'sorts elements by name' do
+ expect(subject.execute).to eq(
+ [
+ subgroup1,
+ subgroup2,
+ project1,
+ project2
+ ]
+ )
+ end
+ end
+ end
end
context 'with nested groups', :nested_groups do
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index 7460da031c4..1a8affad4e3 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -21,6 +21,7 @@ describe('Applications', () => {
helm: { title: 'Helm Tiller' },
ingress: { title: 'Ingress' },
runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
},
});
});
@@ -33,6 +34,10 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).toBeDefined();
});
+ it('renders a row for Prometheus', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined();
+ });
+
/* * /
it('renders a row for GitLab Runner', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined();
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
index af6b6a73819..253b3c45243 100644
--- a/spec/javascripts/clusters/services/mock_data.js
+++ b/spec/javascripts/clusters/services/mock_data.js
@@ -22,6 +22,11 @@ const CLUSTERS_MOCK_DATA = {
name: 'runner',
status: APPLICATION_INSTALLING,
status_reason: null,
+ },
+ {
+ name: 'prometheus',
+ status: APPLICATION_ERROR,
+ status_reason: 'Cannot connect',
}],
},
},
@@ -30,6 +35,7 @@ const CLUSTERS_MOCK_DATA = {
'/gitlab-org/gitlab-shell/clusters/1/applications/helm': { },
'/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { },
'/gitlab-org/gitlab-shell/clusters/1/applications/runner': { },
+ '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { },
},
};
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index cb8b3d38e2e..ec2889355e6 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -82,6 +82,13 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
},
+ prometheus: {
+ title: 'Prometheus',
+ status: mockResponseData.applications[3].status,
+ statusReason: mockResponseData.applications[3].status_reason,
+ requestStatus: null,
+ requestReason: null,
+ },
},
});
});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 050f0ea9ebd..a6be474805b 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -290,15 +290,18 @@ import 'vendor/jquery.scrollTo';
$('body').removeAttr('data-page');
});
- it('requires an absolute pathname', function () {
- spyOn($, 'ajax').and.callFake(function (options) {
- expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
+ it('triggers Ajax request to JSON endpoint', function (done) {
+ const url = '/foo/bar/merge_requests/1/diffs';
+ spyOn(this.class, 'ajaxGet').and.callFake((options) => {
+ expect(options.url).toEqual(`${url}.json`);
+ done();
});
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+ this.class.loadDiff(url);
});
- it('triggers scroll event when diff already loaded', function () {
+ it('triggers scroll event when diff already loaded', function (done) {
+ spyOn(this.class, 'ajaxGet').and.callFake(() => done.fail());
spyOn(document, 'dispatchEvent');
this.class.diffsLoaded = true;
@@ -307,6 +310,7 @@ import 'vendor/jquery.scrollTo';
expect(
document.dispatchEvent,
).toHaveBeenCalledWith(new CustomEvent('scroll'));
+ done();
});
describe('with inline diff', () => {
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 1f4e858e731..2bbe963e393 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -1,6 +1,8 @@
/* eslint-disable quote-props, indent, comma-dangle */
-const metricsGroupsAPIResponse = {
+export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
+
+export const metricsGroupsAPIResponse = {
'success': true,
'data': [
{
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
index f750061a6a1..c4d3866c922 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import listCollapsed from '~/repo/components/commit_sidebar/list_collapsed.vue';
+import store from '~/ide/stores';
+import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
index 18c9b46fcd9..fc7c9ae9dd7 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import listItem from '~/repo/components/commit_sidebar/list_item.vue';
+import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
index df7e3c5de21..cb5240ad118 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import commitSidebarList from '~/repo/components/commit_sidebar/list.vue';
+import store from '~/ide/stores';
+import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
@@ -13,8 +13,11 @@ describe('Multi-file editor commit sidebar list', () => {
vm = createComponentWithStore(Component, store, {
title: 'Staged',
fileList: [],
- collapsed: false,
- }).$mount();
+ });
+
+ vm.$store.state.rightPanelCollapsed = false;
+
+ vm.$mount();
});
afterEach(() => {
@@ -43,30 +46,14 @@ describe('Multi-file editor commit sidebar list', () => {
describe('collapsed', () => {
beforeEach((done) => {
- vm.collapsed = true;
+ vm.$store.state.rightPanelCollapsed = true;
Vue.nextTick(done);
});
- it('adds collapsed class', () => {
- expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
- });
-
it('hides list', () => {
expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
expect(vm.$el.querySelector('.help-block')).toBeNull();
});
-
- it('hides collapse button', () => {
- expect(vm.$el.querySelector('.multi-file-commit-panel-collapse-btn')).toBeNull();
- });
- });
-
- it('clicking toggle collapse button emits toggle event', () => {
- spyOn(vm, '$emit');
-
- vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
-
- expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed');
});
});
diff --git a/spec/javascripts/repo/components/ide_context_bar_spec.js b/spec/javascripts/repo/components/ide_context_bar_spec.js
new file mode 100644
index 00000000000..3f8f37d2343
--- /dev/null
+++ b/spec/javascripts/repo/components/ide_context_bar_spec.js
@@ -0,0 +1,49 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import ideContextBar from '~/ide/components/ide_context_bar.vue';
+import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
+
+describe('Multi-file editor right context bar', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(ideContextBar);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.rightPanelCollapsed = false;
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('collapsed', () => {
+ beforeEach((done) => {
+ vm.$store.state.rightPanelCollapsed = true;
+
+ Vue.nextTick(done);
+ });
+
+ it('adds collapsed class', () => {
+ expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
+ });
+
+ it('shows correct icon', () => {
+ expect(vm.currentIcon).toBe('angle-double-left');
+ });
+ });
+
+ it('clicking toggle collapse button collapses the bar', () => {
+ spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve());
+
+ vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
+
+ expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({
+ side: 'right',
+ collapsed: true,
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js
index df7cf8aabbb..b6f70f585cd 100644
--- a/spec/javascripts/repo/components/repo_sidebar_spec.js
+++ b/spec/javascripts/repo/components/ide_repo_tree_spec.js
@@ -1,20 +1,26 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoSidebar from '~/repo/components/repo_sidebar.vue';
+import store from '~/ide/stores';
+import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
import { file, resetStore } from '../helpers';
-describe('RepoSidebar', () => {
+describe('IdeRepoTree', () => {
let vm;
beforeEach(() => {
- const RepoSidebar = Vue.extend(repoSidebar);
+ const IdeRepoTree = Vue.extend(ideRepoTree);
- vm = new RepoSidebar({
+ vm = new IdeRepoTree({
store,
+ propsData: {
+ treeId: 'abcproject/mybranch',
+ },
});
+ vm.$store.state.currentBranch = 'master';
vm.$store.state.isRoot = true;
- vm.$store.state.tree.push(file());
+ vm.$store.state.trees['abcproject/mybranch'] = {
+ tree: [file()],
+ };
vm.$mount();
});
@@ -26,13 +32,9 @@ describe('RepoSidebar', () => {
});
it('renders a sidebar', () => {
- const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody');
expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
- expect(thead.querySelector('.name').textContent.trim()).toEqual('Name');
- expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit');
- expect(thead.querySelector('.last-update').textContent.trim()).toEqual('Last update');
expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
expect(tbody.querySelector('.prev-directory')).toBeFalsy();
expect(tbody.querySelector('.loading-file')).toBeFalsy();
@@ -40,7 +42,6 @@ describe('RepoSidebar', () => {
});
it('renders 5 loading files if tree is loading', (done) => {
- vm.$store.state.tree = [];
vm.$store.state.loading = true;
Vue.nextTick(() => {
diff --git a/spec/javascripts/repo/components/ide_side_bar_spec.js b/spec/javascripts/repo/components/ide_side_bar_spec.js
new file mode 100644
index 00000000000..30e45169205
--- /dev/null
+++ b/spec/javascripts/repo/components/ide_side_bar_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import ideSidebar from '~/ide/components/ide_side_bar.vue';
+import { resetStore } from '../helpers';
+import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
+
+describe('IdeSidebar', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(ideSidebar);
+
+ vm = createComponentWithStore(Component, store).$mount();
+
+ vm.$store.state.leftPanelCollapsed = false;
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
+
+ it('renders a sidebar', () => {
+ expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull();
+ });
+
+ describe('collapsed', () => {
+ beforeEach((done) => {
+ vm.$store.state.leftPanelCollapsed = true;
+
+ Vue.nextTick(done);
+ });
+
+ it('adds collapsed class', () => {
+ expect(vm.$el.classList).toContain('is-collapsed');
+ });
+
+ it('shows correct icon', () => {
+ expect(vm.currentIcon).toBe('angle-double-right');
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_spec.js b/spec/javascripts/repo/components/ide_spec.js
index b32d2c13af8..20b8dc25dcb 100644
--- a/spec/javascripts/repo/components/repo_spec.js
+++ b/spec/javascripts/repo/components/ide_spec.js
@@ -1,14 +1,14 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repo from '~/repo/components/repo.vue';
+import store from '~/ide/stores';
+import ide from '~/ide/components/ide.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { file, resetStore } from '../helpers';
-describe('repo component', () => {
+describe('ide component', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(repo);
+ const Component = Vue.extend(ide);
vm = createComponentWithStore(Component, store).$mount();
});
@@ -24,7 +24,9 @@ describe('repo component', () => {
});
it('renders panel right when files are open', (done) => {
- vm.$store.state.tree.push(file());
+ vm.$store.state.trees['abcproject/mybranch'] = {
+ tree: [file()],
+ };
Vue.nextTick(() => {
expect(vm.$el.querySelector('.panel-right')).toBeNull();
diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js
index 9a705a1f0ed..cd1d073ec18 100644
--- a/spec/javascripts/repo/components/new_branch_form_spec.js
+++ b/spec/javascripts/repo/components/new_branch_form_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import newBranchForm from '~/repo/components/new_branch_form.vue';
+import store from '~/ide/stores';
+import newBranchForm from '~/ide/components/new_branch_form.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js
index 93b10fc1fee..b001c1655b4 100644
--- a/spec/javascripts/repo/components/new_dropdown/index_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import newDropdown from '~/repo/components/new_dropdown/index.vue';
+import store from '~/ide/stores';
+import newDropdown from '~/ide/components/new_dropdown/index.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
@@ -10,8 +10,12 @@ describe('new dropdown component', () => {
beforeEach(() => {
const component = Vue.extend(newDropdown);
- vm = createComponentWithStore(component, store);
+ vm = createComponentWithStore(component, store, {
+ branch: 'master',
+ path: '',
+ });
+ vm.$store.state.currentProjectId = 'abcproject';
vm.$store.state.path = '';
vm.$mount();
@@ -23,9 +27,10 @@ describe('new dropdown component', () => {
resetStore(vm.$store);
});
- it('renders new file and new directory links', () => {
+ it('renders new file, upload and new directory links', () => {
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
- expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('New directory');
+ expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
+ expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
});
describe('createNewItem', () => {
@@ -36,7 +41,7 @@ describe('new dropdown component', () => {
});
it('sets modalType to tree when new directory is clicked', () => {
- vm.$el.querySelectorAll('a')[1].click();
+ vm.$el.querySelectorAll('a')[2].click();
expect(vm.modalType).toBe('tree');
});
diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
index 1ff7590ec79..233cca06ed0 100644
--- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
@@ -1,12 +1,42 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import modal from '~/repo/components/new_dropdown/modal.vue';
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import modal from '~/ide/components/new_dropdown/modal.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { file, resetStore } from '../../helpers';
describe('new file modal component', () => {
const Component = Vue.extend(modal);
let vm;
+ let projectTree;
+
+ beforeEach(() => {
+ spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
+ data: {
+ id: '123',
+ },
+ }));
+
+ spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
+ commit: {
+ id: '123branch',
+ },
+ }));
+
+ spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
+ headers: {
+ 'page-title': 'test',
+ },
+ json: () => Promise.resolve({
+ last_commit_path: 'last_commit_path',
+ parent_tree_url: 'parent_tree_url',
+ path: '/',
+ trees: [{ name: 'tree' }],
+ blobs: [{ name: 'blob' }],
+ submodules: [{ name: 'submodule' }],
+ }),
+ }));
+ });
afterEach(() => {
vm.$destroy();
@@ -17,12 +47,26 @@ describe('new file modal component', () => {
['tree', 'blob'].forEach((type) => {
describe(type, () => {
beforeEach(() => {
+ store.state.projects.abcproject = {
+ web_url: '',
+ };
+ store.state.trees = [];
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+ projectTree = store.state.trees['abcproject/mybranch'];
+ store.state.currentProjectId = 'abcproject';
+
vm = createComponentWithStore(Component, store, {
type,
+ branchId: 'master',
path: '',
- }).$mount();
+ parent: projectTree,
+ });
vm.entryName = 'testing';
+
+ vm.$mount();
});
it(`sets modal title as ${type}`, () => {
@@ -50,6 +94,9 @@ describe('new file modal component', () => {
vm.createEntryInStore();
expect(vm.createTempEntry).toHaveBeenCalledWith({
+ projectId: 'abcproject',
+ branchId: 'master',
+ parent: projectTree,
name: 'testing',
type,
});
@@ -76,31 +123,18 @@ describe('new file modal component', () => {
});
it('opens newly created file', (done) => {
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(vm.$store.state.openFiles.length).toBe(1);
- expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
-
- done();
- });
- });
-
- it(`creates ${type} in the current stores path`, (done) => {
- vm.$store.state.path = 'app';
-
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(vm.$store.state.tree[0].path).toBe('app/testing');
- expect(vm.$store.state.tree[0].name).toBe('testing');
+ if (type === 'blob') {
+ vm.createEntryInStore();
- if (type === 'tree') {
- expect(vm.$store.state.tree[0].tree.length).toBe(1);
- }
+ setTimeout(() => {
+ expect(vm.$store.state.openFiles.length).toBe(1);
+ expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
+ done();
+ });
+ } else {
done();
- });
+ }
});
if (type === 'blob') {
@@ -108,25 +142,27 @@ describe('new file modal component', () => {
vm.createEntryInStore();
setTimeout(() => {
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('testing');
- expect(vm.$store.state.tree[0].type).toBe('blob');
- expect(vm.$store.state.tree[0].tempFile).toBeTruthy();
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('testing');
+ expect(baseTree[0].type).toBe('blob');
+ expect(baseTree[0].tempFile).toBeTruthy();
done();
});
});
it('does not create temp file when file already exists', (done) => {
- vm.$store.state.tree.push(file('testing', '1', type));
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ baseTree.push(file('testing', '1', type));
vm.createEntryInStore();
setTimeout(() => {
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('testing');
- expect(vm.$store.state.tree[0].type).toBe('blob');
- expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('testing');
+ expect(baseTree[0].type).toBe('blob');
+ expect(baseTree[0].tempFile).toBeFalsy();
done();
});
@@ -135,48 +171,47 @@ describe('new file modal component', () => {
it('creates new tree', () => {
vm.createEntryInStore();
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('testing');
- expect(vm.$store.state.tree[0].type).toBe('tree');
- expect(vm.$store.state.tree[0].tempFile).toBeTruthy();
- expect(vm.$store.state.tree[0].tree.length).toBe(1);
- expect(vm.$store.state.tree[0].tree[0].name).toBe('.gitkeep');
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('testing');
+ expect(baseTree[0].type).toBe('tree');
+ expect(baseTree[0].tempFile).toBeTruthy();
});
it('creates multiple trees when entryName has slashes', () => {
vm.entryName = 'app/test';
vm.createEntryInStore();
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('app');
- expect(vm.$store.state.tree[0].tree[0].name).toBe('test');
- expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep');
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('app');
});
it('creates tree in existing tree', () => {
- vm.$store.state.tree.push(file('app', '1', 'tree'));
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ baseTree.push(file('app', '1', 'tree'));
vm.entryName = 'app/test';
vm.createEntryInStore();
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('app');
- expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
- expect(vm.$store.state.tree[0].tree[0].tempFile).toBeTruthy();
- expect(vm.$store.state.tree[0].tree[0].name).toBe('test');
- expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep');
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('app');
+ expect(baseTree[0].tempFile).toBeFalsy();
+ expect(baseTree[0].tree[0].tempFile).toBeTruthy();
+ expect(baseTree[0].tree[0].name).toBe('test');
});
it('does not create new tree when already exists', () => {
- vm.$store.state.tree.push(file('app', '1', 'tree'));
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ baseTree.push(file('app', '1', 'tree'));
vm.entryName = 'app';
vm.createEntryInStore();
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe('app');
- expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
- expect(vm.$store.state.tree[0].tree.length).toBe(0);
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe('app');
+ expect(baseTree[0].tempFile).toBeFalsy();
+ expect(baseTree[0].tree.length).toBe(0);
});
}
});
@@ -188,6 +223,8 @@ describe('new file modal component', () => {
vm = createComponentWithStore(Component, store, {
type: 'tree',
+ projectId: 'abcproject',
+ branchId: 'master',
path: '',
}).$mount('.js-test');
diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
index bf7893029b1..788c08e5279 100644
--- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
@@ -1,19 +1,61 @@
import Vue from 'vue';
-import upload from '~/repo/components/new_dropdown/upload.vue';
-import store from '~/repo/stores';
+import upload from '~/ide/components/new_dropdown/upload.vue';
+import store from '~/ide/stores';
+import service from '~/ide/services';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('new dropdown upload', () => {
let vm;
+ let projectTree;
beforeEach(() => {
+ spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
+ data: {
+ id: '123',
+ },
+ }));
+
+ spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
+ commit: {
+ id: '123branch',
+ },
+ }));
+
+ spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
+ headers: {
+ 'page-title': 'test',
+ },
+ json: () => Promise.resolve({
+ last_commit_path: 'last_commit_path',
+ parent_tree_url: 'parent_tree_url',
+ path: '/',
+ trees: [{ name: 'tree' }],
+ blobs: [{ name: 'blob' }],
+ submodules: [{ name: 'submodule' }],
+ }),
+ }));
+
const Component = Vue.extend(upload);
+ store.state.projects.abcproject = {
+ web_url: '',
+ };
+ store.state.currentProjectId = 'abcproject';
+ store.state.trees = [];
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+ projectTree = store.state.trees['abcproject/mybranch'];
+
vm = createComponentWithStore(Component, store, {
+ branchId: 'master',
path: '',
+ parent: projectTree,
});
+ vm.entryName = 'testing';
+
vm.$mount();
});
@@ -65,23 +107,33 @@ describe('new dropdown upload', () => {
vm.createFile(target, file, true);
vm.$nextTick(() => {
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe(file.name);
- expect(vm.$store.state.tree[0].content).toBe(target.result);
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe(file.name);
+ expect(baseTree[0].content).toBe(target.result);
done();
});
});
it('creates new file in path', (done) => {
- vm.$store.state.path = 'testing';
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ const tree = {
+ type: 'tree',
+ name: 'testing',
+ path: 'testing',
+ tree: [],
+ };
+ baseTree.push(tree);
+
+ vm.parent = tree;
vm.createFile(target, file, true);
vm.$nextTick(() => {
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe(file.name);
- expect(vm.$store.state.tree[0].content).toBe(target.result);
- expect(vm.$store.state.tree[0].path).toBe(`testing/${file.name}`);
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].tree[0].name).toBe(file.name);
+ expect(baseTree[0].tree[0].content).toBe(target.result);
+ expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`);
done();
});
@@ -91,10 +143,11 @@ describe('new dropdown upload', () => {
vm.createFile(binaryTarget, file, false);
vm.$nextTick(() => {
- expect(vm.$store.state.tree.length).toBe(1);
- expect(vm.$store.state.tree[0].name).toBe(file.name);
- expect(vm.$store.state.tree[0].content).toBe(binaryTarget.result.split('base64,')[1]);
- expect(vm.$store.state.tree[0].base64).toBe(true);
+ const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].name).toBe(file.name);
+ expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]);
+ expect(baseTree[0].base64).toBe(true);
done();
});
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index 72712e058e5..cd93fb3ccbf 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/repo/stores';
-import service from '~/repo/services';
-import repoCommitSection from '~/repo/components/repo_commit_section.vue';
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import repoCommitSection from '~/ide/components/repo_commit_section.vue';
import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
@@ -16,6 +16,18 @@ describe('RepoCommitSection', () => {
store,
}).$mount();
+ comp.$store.state.currentProjectId = 'abcproject';
+ comp.$store.state.currentBranchId = 'master';
+ comp.$store.state.projects.abcproject = {
+ web_url: '',
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
+
+ comp.$store.state.rightPanelCollapsed = false;
comp.$store.state.currentBranch = 'master';
comp.$store.state.openFiles = [file(), file()];
comp.$store.state.openFiles.forEach(f => Object.assign(f, {
@@ -29,7 +41,19 @@ describe('RepoCommitSection', () => {
beforeEach((done) => {
vm = createComponent();
- vm.collapsed = false;
+ spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
+ headers: {
+ 'page-title': 'test',
+ },
+ json: () => Promise.resolve({
+ last_commit_path: 'last_commit_path',
+ parent_tree_url: 'parent_tree_url',
+ path: '/',
+ trees: [{ name: 'tree' }],
+ blobs: [{ name: 'blob' }],
+ submodules: [{ name: 'submodule' }],
+ }),
+ }));
Vue.nextTick(done);
});
@@ -45,7 +69,6 @@ describe('RepoCommitSection', () => {
const submitCommit = vm.$el.querySelector('form .btn');
expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
- expect(vm.$el.querySelector('.multi-file-commit-panel-section header').textContent.trim()).toEqual('Staged');
expect(changedFileElements.length).toEqual(2);
changedFileElements.forEach((changedFile, i) => {
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
index 44018464b35..2895b794506 100644
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ b/spec/javascripts/repo/components/repo_edit_button_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoEditButton from '~/repo/components/repo_edit_button.vue';
+import store from '~/ide/stores';
+import repoEditButton from '~/ide/components/repo_edit_button.vue';
import { file, resetStore } from '../helpers';
describe('RepoEditButton', () => {
@@ -32,7 +32,7 @@ describe('RepoEditButton', () => {
vm.$mount();
expect(vm.$el.querySelector('.btn')).not.toBeNull();
- expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
});
it('renders edit button with cancel text', () => {
@@ -50,7 +50,7 @@ describe('RepoEditButton', () => {
vm.$el.querySelector('.btn').click();
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
done();
});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 81158cad639..e7b2ed08acd 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoEditor from '~/repo/components/repo_editor.vue';
-import monacoLoader from '~/repo/monaco_loader';
+import store from '~/ide/stores';
+import repoEditor from '~/ide/components/repo_editor.vue';
+import monacoLoader from '~/ide/monaco_loader';
import { file, resetStore } from '../helpers';
describe('RepoEditor', () => {
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
index d6e255e4810..115569a9117 100644
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoFileButtons from '~/repo/components/repo_file_buttons.vue';
+import store from '~/ide/stores';
+import repoFileButtons from '~/ide/components/repo_file_buttons.vue';
import { file, resetStore } from '../helpers';
describe('RepoFileButtons', () => {
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index bf9181fb09c..e8b370f97b4 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoFile from '~/repo/components/repo_file.vue';
+import store from '~/ide/stores';
+import repoFile from '~/ide/components/repo_file.vue';
import { file, resetStore } from '../helpers';
describe('RepoFile', () => {
@@ -35,11 +35,10 @@ describe('RepoFile', () => {
const fileIcon = vm.$el.querySelector('.file-icon');
expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px');
- expect(name.href).toMatch(`/${vm.file.url}`);
+ expect(name.href).toMatch('');
expect(name.textContent.trim()).toEqual(vm.file.name);
expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy();
expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`);
- expect(vm.$el.querySelectorAll('.animation-container').length).toBe(2);
});
it('does render if hasFiles is true and is loading tree', () => {
@@ -75,16 +74,16 @@ describe('RepoFile', () => {
});
});
- it('fires clickedTreeRow when the link is clicked', () => {
+ it('fires clickFile when the link is clicked', () => {
vm = createComponent({
file: file(),
});
- spyOn(vm, 'clickedTreeRow');
+ spyOn(vm, 'clickFile');
vm.$el.click();
- expect(vm.clickedTreeRow).toHaveBeenCalledWith(vm.file);
+ expect(vm.clickFile).toHaveBeenCalledWith(vm.file);
});
describe('submodule', () => {
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
index 031f2a9c0b2..18366fb89bc 100644
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ b/spec/javascripts/repo/components/repo_loading_file_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoLoadingFile from '~/repo/components/repo_loading_file.vue';
+import store from '~/ide/stores';
+import repoLoadingFile from '~/ide/components/repo_loading_file.vue';
import { resetStore } from '../helpers';
describe('RepoLoadingFile', () => {
@@ -48,6 +48,7 @@ describe('RepoLoadingFile', () => {
it('renders 1 column of animated LoC if isMini', (done) => {
vm = createComponent();
+ vm.$store.state.leftPanelCollapsed = true;
vm.$store.state.openFiles.push('test');
vm.$nextTick(() => {
diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js
index 7f82ae36a64..ff26cab2262 100644
--- a/spec/javascripts/repo/components/repo_prev_directory_spec.js
+++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue';
+import store from '~/ide/stores';
+import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue';
import { resetStore } from '../helpers';
describe('RepoPrevDirectory', () => {
diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js
index 8d1a87494cf..e90837e4cb2 100644
--- a/spec/javascripts/repo/components/repo_preview_spec.js
+++ b/spec/javascripts/repo/components/repo_preview_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoPreview from '~/repo/components/repo_preview.vue';
+import store from '~/ide/stores';
+import repoPreview from '~/ide/components/repo_preview.vue';
import { file, resetStore } from '../helpers';
describe('RepoPreview', () => {
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index 7d2174196c9..507bca983df 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoTab from '~/repo/components/repo_tab.vue';
+import store from '~/ide/stores';
+import repoTab from '~/ide/components/repo_tab.vue';
import { file, resetStore } from '../helpers';
describe('RepoTab', () => {
@@ -31,16 +31,16 @@ describe('RepoTab', () => {
expect(name.textContent.trim()).toEqual(vm.tab.name);
});
- it('calls setFileActive when clicking tab', () => {
+ it('fires clickFile when the link is clicked', () => {
vm = createComponent({
tab: file(),
});
- spyOn(vm, 'setFileActive');
+ spyOn(vm, 'clickFile');
vm.$el.click();
- expect(vm.setFileActive).toHaveBeenCalledWith(vm.tab);
+ expect(vm.clickFile).toHaveBeenCalledWith(vm.tab);
});
it('calls closeFile when clicking close button', () => {
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index 1fb2242c051..0beaf643793 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import repoTabs from '~/repo/components/repo_tabs.vue';
+import store from '~/ide/stores';
+import repoTabs from '~/ide/components/repo_tabs.vue';
import { file, resetStore } from '../helpers';
describe('RepoTabs', () => {
diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js
index 820a44992b4..ac43d221198 100644
--- a/spec/javascripts/repo/helpers.js
+++ b/spec/javascripts/repo/helpers.js
@@ -1,5 +1,5 @@
-import { decorateData } from '~/repo/stores/utils';
-import state from '~/repo/stores/state';
+import { decorateData } from '~/ide/stores/utils';
+import state from '~/ide/stores/state';
export const resetStore = (store) => {
store.replaceState(state());
@@ -12,4 +12,5 @@ export const file = (name = 'name', id = name, type = '') => decorateData({
url: 'url',
name,
path: name,
+ lastCommit: {},
});
diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js
index 62c3913bf4d..af12ca15369 100644
--- a/spec/javascripts/repo/lib/common/disposable_spec.js
+++ b/spec/javascripts/repo/lib/common/disposable_spec.js
@@ -1,4 +1,4 @@
-import Disposable from '~/repo/lib/common/disposable';
+import Disposable from '~/ide/lib/common/disposable';
describe('Multi-file editor library disposable class', () => {
let instance;
diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js
index 8c134f178c0..563c2e33834 100644
--- a/spec/javascripts/repo/lib/common/model_manager_spec.js
+++ b/spec/javascripts/repo/lib/common/model_manager_spec.js
@@ -1,6 +1,6 @@
/* global monaco */
-import monacoLoader from '~/repo/monaco_loader';
-import ModelManager from '~/repo/lib/common/model_manager';
+import monacoLoader from '~/ide/monaco_loader';
+import ModelManager from '~/ide/lib/common/model_manager';
import { file } from '../../helpers';
describe('Multi-file editor library model manager', () => {
diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js
index d41ade237ca..878a4a3f3fe 100644
--- a/spec/javascripts/repo/lib/common/model_spec.js
+++ b/spec/javascripts/repo/lib/common/model_spec.js
@@ -1,6 +1,6 @@
/* global monaco */
-import monacoLoader from '~/repo/monaco_loader';
-import Model from '~/repo/lib/common/model';
+import monacoLoader from '~/ide/monaco_loader';
+import Model from '~/ide/lib/common/model';
import { file } from '../../helpers';
describe('Multi-file editor library model', () => {
diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js
index 2e32e8fa0bd..fea12d74dca 100644
--- a/spec/javascripts/repo/lib/decorations/controller_spec.js
+++ b/spec/javascripts/repo/lib/decorations/controller_spec.js
@@ -1,8 +1,8 @@
/* global monaco */
-import monacoLoader from '~/repo/monaco_loader';
-import editor from '~/repo/lib/editor';
-import DecorationsController from '~/repo/lib/decorations/controller';
-import Model from '~/repo/lib/common/model';
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
+import DecorationsController from '~/ide/lib/decorations/controller';
+import Model from '~/ide/lib/common/model';
import { file } from '../../helpers';
describe('Multi-file editor library decorations controller', () => {
diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js
index ed62e28d3a3..1d55c165260 100644
--- a/spec/javascripts/repo/lib/diff/controller_spec.js
+++ b/spec/javascripts/repo/lib/diff/controller_spec.js
@@ -1,10 +1,10 @@
/* global monaco */
-import monacoLoader from '~/repo/monaco_loader';
-import editor from '~/repo/lib/editor';
-import ModelManager from '~/repo/lib/common/model_manager';
-import DecorationsController from '~/repo/lib/decorations/controller';
-import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/repo/lib/diff/controller';
-import { computeDiff } from '~/repo/lib/diff/diff';
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
+import ModelManager from '~/ide/lib/common/model_manager';
+import DecorationsController from '~/ide/lib/decorations/controller';
+import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
+import { computeDiff } from '~/ide/lib/diff/diff';
import { file } from '../../helpers';
describe('Multi-file editor library dirty diff controller', () => {
diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js
index 3269ec5d2c9..57f3ac3d365 100644
--- a/spec/javascripts/repo/lib/diff/diff_spec.js
+++ b/spec/javascripts/repo/lib/diff/diff_spec.js
@@ -1,4 +1,4 @@
-import { computeDiff } from '~/repo/lib/diff/diff';
+import { computeDiff } from '~/ide/lib/diff/diff';
describe('Multi-file editor library diff calculator', () => {
describe('computeDiff', () => {
diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js
index b4887d063ed..edbf5450dce 100644
--- a/spec/javascripts/repo/lib/editor_options_spec.js
+++ b/spec/javascripts/repo/lib/editor_options_spec.js
@@ -1,4 +1,4 @@
-import editorOptions from '~/repo/lib/editor_options';
+import editorOptions from '~/ide/lib/editor_options';
describe('Multi-file editor library editor options', () => {
it('returns an array', () => {
diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js
index cd32832a232..8d51d48a782 100644
--- a/spec/javascripts/repo/lib/editor_spec.js
+++ b/spec/javascripts/repo/lib/editor_spec.js
@@ -1,6 +1,6 @@
/* global monaco */
-import monacoLoader from '~/repo/monaco_loader';
-import editor from '~/repo/lib/editor';
+import monacoLoader from '~/ide/monaco_loader';
+import editor from '~/ide/lib/editor';
import { file } from '../helpers';
describe('Multi-file editor library', () => {
diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js
index 887a80160fc..b8ac36972aa 100644
--- a/spec/javascripts/repo/monaco_loader_spec.js
+++ b/spec/javascripts/repo/monaco_loader_spec.js
@@ -1,5 +1,5 @@
import monacoContext from 'monaco-editor/dev/vs/loader';
-import monacoLoader from '~/repo/monaco_loader';
+import monacoLoader from '~/ide/monaco_loader';
describe('MonacoLoader', () => {
it('calls require.config and exports require', () => {
diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js
index af9d6835a67..00d16fd790d 100644
--- a/spec/javascripts/repo/stores/actions/branch_spec.js
+++ b/spec/javascripts/repo/stores/actions/branch_spec.js
@@ -1,5 +1,5 @@
-import store from '~/repo/stores';
-import service from '~/repo/services';
+import store from '~/ide/stores';
+import service from '~/ide/services';
import { resetStore } from '../../helpers';
describe('Multi-file store branch actions', () => {
@@ -16,19 +16,25 @@ describe('Multi-file store branch actions', () => {
}));
spyOn(history, 'pushState');
- store.state.project.id = 2;
- store.state.currentBranch = 'testing';
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'testing';
+ store.state.projects.abcproject = {
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
});
it('creates new branch', (done) => {
store.dispatch('createNewBranch', 'master')
.then(() => {
- expect(store.state.currentBranch).toBe('testing');
- expect(service.createBranch).toHaveBeenCalledWith(2, {
+ expect(store.state.currentBranchId).toBe('testing');
+ expect(service.createBranch).toHaveBeenCalledWith('abcproject', {
branch: 'master',
ref: 'testing',
});
- expect(history.pushState).toHaveBeenCalled();
done();
})
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
index 099c0556e71..8ce01d3bf12 100644
--- a/spec/javascripts/repo/stores/actions/file_spec.js
+++ b/spec/javascripts/repo/stores/actions/file_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import store from '~/repo/stores';
-import service from '~/repo/services';
+import store from '~/ide/stores';
+import service from '~/ide/services';
import { file, resetStore } from '../../helpers';
describe('Multi-file store file actions', () => {
@@ -24,8 +24,6 @@ describe('Multi-file store file actions', () => {
localFile.parentTreeUrl = 'parentTreeUrl';
store.state.openFiles.push(localFile);
-
- spyOn(history, 'pushState');
});
afterEach(() => {
@@ -82,15 +80,6 @@ describe('Multi-file store file actions', () => {
}).catch(done.fail);
});
- it('calls pushState when no open files are left', (done) => {
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'parentTreeUrl');
-
- done();
- }).catch(done.fail);
- });
-
it('sets next file as active', (done) => {
const f = file();
store.state.openFiles.push(f);
@@ -322,8 +311,26 @@ describe('Multi-file store file actions', () => {
});
describe('createTempFile', () => {
+ let projectTree;
+
beforeEach(() => {
document.body.innerHTML += '<div class="flash-container"></div>';
+
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = {
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
+
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+
+ projectTree = store.state.trees['abcproject/mybranch'];
});
afterEach(() => {
@@ -332,11 +339,13 @@ describe('Multi-file store file actions', () => {
it('creates temp file', (done) => {
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then((f) => {
expect(f.tempFile).toBeTruthy();
- expect(store.state.tree.length).toBe(1);
+ expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1);
done();
}).catch(done.fail);
@@ -344,8 +353,10 @@ describe('Multi-file store file actions', () => {
it('adds tmp file to open files', (done) => {
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then((f) => {
expect(store.state.openFiles.length).toBe(1);
expect(store.state.openFiles[0].name).toBe(f.name);
@@ -356,8 +367,10 @@ describe('Multi-file store file actions', () => {
it('sets tmp file as active', (done) => {
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then((f) => {
expect(f.active).toBeTruthy();
@@ -367,8 +380,10 @@ describe('Multi-file store file actions', () => {
it('enters edit mode if file is not base64', (done) => {
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then(() => {
expect(store.state.editMode).toBeTruthy();
@@ -376,24 +391,14 @@ describe('Multi-file store file actions', () => {
}).catch(done.fail);
});
- it('does not enter edit mode if file is base64', (done) => {
- store.dispatch('createTempFile', {
- tree: store.state,
- name: 'test',
- base64: true,
- }).then(() => {
- expect(store.state.editMode).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
it('creates flash message is file already exists', (done) => {
- store.state.tree.push(file('test', '1', 'blob'));
+ store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob'));
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then(() => {
expect(document.querySelector('.flash-alert')).not.toBeNull();
@@ -402,11 +407,13 @@ describe('Multi-file store file actions', () => {
});
it('increases level of file', (done) => {
- store.state.level = 1;
+ store.state.trees['abcproject/mybranch'].level = 1;
store.dispatch('createTempFile', {
- tree: store.state,
name: 'test',
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
}).then((f) => {
expect(f.level).toBe(2);
diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js
index 2bbc49d5a9f..65351dbb7d9 100644
--- a/spec/javascripts/repo/stores/actions/tree_spec.js
+++ b/spec/javascripts/repo/stores/actions/tree_spec.js
@@ -1,10 +1,30 @@
import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/repo/stores';
-import service from '~/repo/services';
+import store from '~/ide/stores';
+import service from '~/ide/services';
import { file, resetStore } from '../../helpers';
describe('Multi-file store tree actions', () => {
+ let projectTree;
+
+ const basicCallParameters = {
+ endpoint: 'rootEndpoint',
+ projectId: 'abcproject',
+ branch: 'master',
+ };
+
+ beforeEach(() => {
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = {
+ web_url: '',
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
+ });
+
afterEach(() => {
resetStore(store);
});
@@ -24,38 +44,32 @@ describe('Multi-file store tree actions', () => {
submodules: [{ name: 'submodule' }],
}),
}));
- spyOn(history, 'pushState');
-
- Object.assign(store.state.endpoints, {
- rootEndpoint: 'rootEndpoint',
- });
});
it('calls service getTreeData', (done) => {
- store.dispatch('getTreeData')
- .then(() => {
- expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
+ store.dispatch('getTreeData', basicCallParameters)
+ .then(() => {
+ expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
- done();
- }).catch(done.fail);
+ done();
+ }).catch(done.fail);
});
it('adds data into tree', (done) => {
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
- expect(store.state.tree.length).toBe(3);
- expect(store.state.tree[0].type).toBe('tree');
- expect(store.state.tree[1].type).toBe('submodule');
- expect(store.state.tree[2].type).toBe('blob');
+ projectTree = store.state.trees['abcproject/master'];
+ expect(projectTree.tree.length).toBe(3);
+ expect(projectTree.tree[0].type).toBe('tree');
+ expect(projectTree.tree[1].type).toBe('submodule');
+ expect(projectTree.tree[2].type).toBe('blob');
done();
}).catch(done.fail);
});
it('sets parent tree URL', (done) => {
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
expect(store.state.parentTreeUrl).toBe('parent_tree_url');
@@ -64,10 +78,9 @@ describe('Multi-file store tree actions', () => {
});
it('sets last commit path', (done) => {
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
- expect(store.state.lastCommitPath).toBe('last_commit_path');
+ expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path');
done();
}).catch(done.fail);
@@ -76,8 +89,7 @@ describe('Multi-file store tree actions', () => {
it('sets root if not currently at root', (done) => {
store.state.isInitialRoot = false;
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
expect(store.state.isInitialRoot).toBeTruthy();
expect(store.state.isRoot).toBeTruthy();
@@ -87,7 +99,7 @@ describe('Multi-file store tree actions', () => {
});
it('sets page title', (done) => {
- store.dispatch('getTreeData')
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
expect(document.title).toBe('test');
@@ -95,40 +107,15 @@ describe('Multi-file store tree actions', () => {
}).catch(done.fail);
});
- it('toggles loading', (done) => {
- store.dispatch('getTreeData')
- .then(() => {
- expect(store.state.loading).toBeTruthy();
-
- return Vue.nextTick();
- })
- .then(() => {
- expect(store.state.loading).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
- it('calls pushState with endpoint', (done) => {
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
- .then(() => {
- expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'rootEndpoint');
-
- done();
- }).catch(done.fail);
- });
-
it('calls getLastCommitData if prevLastCommitPath is not null', (done) => {
const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
store.state.prevLastCommitPath = 'test';
- store.dispatch('getTreeData')
- .then(Vue.nextTick)
+ store.dispatch('getTreeData', basicCallParameters)
.then(() => {
- expect(getLastCommitDataSpy).toHaveBeenCalledWith(store.state);
+ expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree);
store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
@@ -149,6 +136,8 @@ describe('Multi-file store tree actions', () => {
store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line
tree = {
+ projectId: 'abcproject',
+ branchId: 'master',
opened: false,
tree: [],
};
@@ -175,10 +164,11 @@ describe('Multi-file store tree actions', () => {
tree,
}).then(() => {
expect(getTreeDataSpy).toHaveBeenCalledWith({
+ projectId: 'abcproject',
+ branch: 'master',
endpoint: 'test',
tree,
});
- expect(store.state.previousUrl).toBe('test');
done();
}).catch(done.fail);
@@ -199,155 +189,29 @@ describe('Multi-file store tree actions', () => {
done();
}).catch(done.fail);
});
-
- it('pushes new state', (done) => {
- spyOn(history, 'pushState');
- Object.assign(tree, {
- opened: true,
- parentTreeUrl: 'testing',
- });
-
- store.dispatch('toggleTreeOpen', {
- endpoint: 'test',
- tree,
- }).then(() => {
- expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'testing');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('clickedTreeRow', () => {
- describe('tree', () => {
- let toggleTreeOpenSpy;
- let oldToggleTreeOpen;
-
- beforeEach(() => {
- toggleTreeOpenSpy = jasmine.createSpy('toggleTreeOpen');
-
- oldToggleTreeOpen = store._actions.toggleTreeOpen; // eslint-disable-line
- store._actions.toggleTreeOpen = [toggleTreeOpenSpy]; // eslint-disable-line
- });
-
- afterEach(() => {
- store._actions.toggleTreeOpen = oldToggleTreeOpen; // eslint-disable-line
- });
-
- it('opens tree', (done) => {
- const tree = {
- url: 'a',
- type: 'tree',
- };
-
- store.dispatch('clickedTreeRow', tree)
- .then(() => {
- expect(toggleTreeOpenSpy).toHaveBeenCalledWith({
- endpoint: tree.url,
- tree,
- });
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('submodule', () => {
- let row;
-
- beforeEach(() => {
- spyOn(urlUtils, 'visitUrl');
-
- row = {
- url: 'submoduleurl',
- type: 'submodule',
- loading: false,
- };
- });
-
- it('toggles loading for row', (done) => {
- store.dispatch('clickedTreeRow', row)
- .then(() => {
- expect(row.loading).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('opens submodule URL', (done) => {
- store.dispatch('clickedTreeRow', row)
- .then(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith('submoduleurl');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('blob', () => {
- let row;
-
- beforeEach(() => {
- row = {
- type: 'blob',
- opened: false,
- };
- });
-
- it('calls getFileData', (done) => {
- const getFileDataSpy = jasmine.createSpy('getFileData');
- const oldGetFileData = store._actions.getFileData; // eslint-disable-line
- store._actions.getFileData = [getFileDataSpy]; // eslint-disable-line
-
- store.dispatch('clickedTreeRow', row)
- .then(() => {
- expect(getFileDataSpy).toHaveBeenCalledWith(row);
-
- store._actions.getFileData = oldGetFileData; // eslint-disable-line
-
- done();
- }).catch(done.fail);
- });
-
- it('calls setFileActive when file is opened', (done) => {
- const setFileActiveSpy = jasmine.createSpy('setFileActive');
- const oldSetFileActive = store._actions.setFileActive; // eslint-disable-line
- store._actions.setFileActive = [setFileActiveSpy]; // eslint-disable-line
-
- row.opened = true;
-
- store.dispatch('clickedTreeRow', row)
- .then(() => {
- expect(setFileActiveSpy).toHaveBeenCalledWith(row);
-
- store._actions.setFileActive = oldSetFileActive; // eslint-disable-line
-
- done();
- }).catch(done.fail);
- });
- });
});
describe('createTempTree', () => {
- it('creates temp tree', (done) => {
- store.dispatch('createTempTree', 'test')
- .then(() => {
- expect(store.state.tree[0].tempFile).toBeTruthy();
- expect(store.state.tree[0].name).toBe('test');
- expect(store.state.tree[0].type).toBe('tree');
-
- done();
- }).catch(done.fail);
+ beforeEach(() => {
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+ projectTree = store.state.trees['abcproject/mybranch'];
});
- it('creates .gitkeep file in temp tree', (done) => {
- store.dispatch('createTempTree', 'test')
- .then(() => {
- expect(store.state.tree[0].tree[0].tempFile).toBeTruthy();
- expect(store.state.tree[0].tree[0].name).toBe('.gitkeep');
+ it('creates temp tree', (done) => {
+ store.dispatch('createTempTree', {
+ projectId: store.state.currentProjectId,
+ branchId: store.state.currentBranchId,
+ name: 'test',
+ parent: projectTree,
+ })
+ .then(() => {
+ expect(projectTree.tree[0].name).toBe('test');
+ expect(projectTree.tree[0].type).toBe('tree');
- done();
- }).catch(done.fail);
+ done();
+ }).catch(done.fail);
});
it('creates new folder inside another tree', (done) => {
@@ -357,35 +221,46 @@ describe('Multi-file store tree actions', () => {
tree: [],
};
- store.state.tree.push(tree);
+ projectTree.tree.push(tree);
- store.dispatch('createTempTree', 'testing/test')
- .then(() => {
- expect(store.state.tree[0].name).toBe('testing');
- expect(store.state.tree[0].tree[0].tempFile).toBeTruthy();
- expect(store.state.tree[0].tree[0].name).toBe('test');
- expect(store.state.tree[0].tree[0].type).toBe('tree');
+ store.dispatch('createTempTree', {
+ projectId: store.state.currentProjectId,
+ branchId: store.state.currentBranchId,
+ name: 'testing/test',
+ parent: projectTree,
+ })
+ .then(() => {
+ expect(projectTree.tree[0].name).toBe('testing');
+ expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy();
+ expect(projectTree.tree[0].tree[0].name).toBe('test');
+ expect(projectTree.tree[0].tree[0].type).toBe('tree');
- done();
- }).catch(done.fail);
+ done();
+ }).catch(done.fail);
});
it('does not create new tree if already exists', (done) => {
const tree = {
type: 'tree',
name: 'testing',
+ endpoint: 'test',
tree: [],
};
- store.state.tree.push(tree);
+ projectTree.tree.push(tree);
- store.dispatch('createTempTree', 'testing/test')
- .then(() => {
- expect(store.state.tree[0].name).toBe('testing');
- expect(store.state.tree[0].tempFile).toBeUndefined();
+ store.dispatch('createTempTree', {
+ projectId: store.state.currentProjectId,
+ branchId: store.state.currentBranchId,
+ name: 'testing/test',
+ parent: projectTree,
+ })
+ .then(() => {
+ expect(projectTree.tree[0].name).toBe('testing');
+ expect(projectTree.tree[0].tempFile).toBeUndefined();
- done();
- }).catch(done.fail);
+ done();
+ }).catch(done.fail);
});
});
@@ -405,12 +280,17 @@ describe('Multi-file store tree actions', () => {
}]),
}));
- store.state.tree.push(file('testing', '1', 'tree'));
- store.state.lastCommitPath = 'lastcommitpath';
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+
+ projectTree = store.state.trees['abcproject/mybranch'];
+ projectTree.tree.push(file('testing', '1', 'tree'));
+ projectTree.lastCommitPath = 'lastcommitpath';
});
it('calls service with lastCommitPath', (done) => {
- store.dispatch('getLastCommitData')
+ store.dispatch('getLastCommitData', projectTree)
.then(() => {
expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
@@ -419,22 +299,22 @@ describe('Multi-file store tree actions', () => {
});
it('updates trees last commit data', (done) => {
- store.dispatch('getLastCommitData')
- .then(Vue.nextTick)
+ store.dispatch('getLastCommitData', projectTree)
+ .then(Vue.nextTick)
.then(() => {
- expect(store.state.tree[0].lastCommit.message).toBe('commit message');
+ expect(projectTree.tree[0].lastCommit.message).toBe('commit message');
done();
}).catch(done.fail);
});
it('does not update entry if not found', (done) => {
- store.state.tree[0].name = 'a';
+ projectTree.tree[0].name = 'a';
- store.dispatch('getLastCommitData')
+ store.dispatch('getLastCommitData', projectTree)
.then(Vue.nextTick)
.then(() => {
- expect(store.state.tree[0].lastCommit.message).not.toBe('commit message');
+ expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
done();
}).catch(done.fail);
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 21d87e46216..0b0d34f072a 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/repo/stores';
-import service from '~/repo/services';
+import store from '~/ide/stores';
+import service from '~/ide/services';
import { resetStore, file } from '../helpers';
describe('Multi-file store actions', () => {
@@ -110,6 +110,7 @@ describe('Multi-file store actions', () => {
it('can force closed if there are changed files', (done) => {
store.state.editMode = true;
+
store.state.openFiles.push(file());
store.state.openFiles[0].changed = true;
@@ -125,7 +126,6 @@ describe('Multi-file store actions', () => {
it('discards file changes', (done) => {
const f = file();
store.state.editMode = true;
- store.state.tree.push(f);
store.state.openFiles.push(f);
f.changed = true;
@@ -141,8 +141,6 @@ describe('Multi-file store actions', () => {
describe('toggleBlobView', () => {
it('sets edit mode view if in edit mode', (done) => {
- store.state.editMode = true;
-
store.dispatch('toggleBlobView')
.then(() => {
expect(store.state.currentBlobView).toBe('repo-editor');
@@ -153,6 +151,8 @@ describe('Multi-file store actions', () => {
});
it('sets preview mode view if not in edit mode', (done) => {
+ store.state.editMode = false;
+
store.dispatch('toggleBlobView')
.then(() => {
expect(store.state.currentBlobView).toBe('repo-preview');
@@ -165,9 +165,15 @@ describe('Multi-file store actions', () => {
describe('checkCommitStatus', () => {
beforeEach(() => {
- store.state.project.id = 2;
- store.state.currentBranch = 'master';
- store.state.currentRef = '1';
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = {
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
});
it('calls service', (done) => {
@@ -177,7 +183,7 @@ describe('Multi-file store actions', () => {
store.dispatch('checkCommitStatus')
.then(() => {
- expect(service.getBranchData).toHaveBeenCalledWith(2, 'master');
+ expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
@@ -221,7 +227,17 @@ describe('Multi-file store actions', () => {
document.body.innerHTML += '<div class="flash-container"></div>';
- store.state.project.id = 123;
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = {
+ web_url: 'webUrl',
+ branches: {
+ master: {
+ workingReference: '1',
+ },
+ },
+ };
+
payload = {
branch: 'master',
};
@@ -248,7 +264,7 @@ describe('Multi-file store actions', () => {
it('calls service', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
- expect(service.commit).toHaveBeenCalledWith(123, payload);
+ expect(service.commit).toHaveBeenCalledWith('abcproject', payload);
done();
}).catch(done.fail);
@@ -284,17 +300,6 @@ describe('Multi-file store actions', () => {
}).catch(done.fail);
});
- it('toggles edit mode', (done) => {
- store.state.editMode = true;
-
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- expect(store.state.editMode).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
it('closes all files', (done) => {
store.state.openFiles.push(file());
store.state.openFiles[0].opened = true;
@@ -317,23 +322,12 @@ describe('Multi-file store actions', () => {
}).catch(done.fail);
});
- it('updates commit ref', (done) => {
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- expect(store.state.currentRef).toBe('123456');
-
- done();
- }).catch(done.fail);
- });
-
it('redirects to new merge request page', (done) => {
spyOn(urlUtils, 'visitUrl');
- store.state.endpoints.newMergeRequestUrl = 'newMergeRequestUrl?branch=';
-
store.dispatch('commitChanges', { payload, newMr: true })
.then(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith('newMergeRequestUrl?branch=master');
+ expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master');
done();
}).catch(done.fail);
@@ -363,15 +357,30 @@ describe('Multi-file store actions', () => {
});
describe('createTempEntry', () => {
+ beforeEach(() => {
+ store.state.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+ store.state.projects.abcproject = {
+ web_url: '',
+ };
+ });
+
it('creates a temp tree', (done) => {
+ const projectTree = store.state.trees['abcproject/mybranch'];
+
store.dispatch('createTempEntry', {
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
name: 'test',
type: 'tree',
})
.then(() => {
- expect(store.state.tree.length).toBe(1);
- expect(store.state.tree[0].tempFile).toBeTruthy();
- expect(store.state.tree[0].type).toBe('tree');
+ const baseTree = projectTree.tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].tempFile).toBeTruthy();
+ expect(baseTree[0].type).toBe('tree');
done();
})
@@ -379,14 +388,20 @@ describe('Multi-file store actions', () => {
});
it('creates temp file', (done) => {
+ const projectTree = store.state.trees['abcproject/mybranch'];
+
store.dispatch('createTempEntry', {
+ projectId: 'abcproject',
+ branchId: 'mybranch',
+ parent: projectTree,
name: 'test',
type: 'blob',
})
.then(() => {
- expect(store.state.tree.length).toBe(1);
- expect(store.state.tree[0].tempFile).toBeTruthy();
- expect(store.state.tree[0].type).toBe('blob');
+ const baseTree = projectTree.tree;
+ expect(baseTree.length).toBe(1);
+ expect(baseTree[0].tempFile).toBeTruthy();
+ expect(baseTree[0].type).toBe('blob');
done();
})
diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js
index 952b8ec3a59..d0d5934f29a 100644
--- a/spec/javascripts/repo/stores/getters_spec.js
+++ b/spec/javascripts/repo/stores/getters_spec.js
@@ -1,5 +1,5 @@
-import * as getters from '~/repo/stores/getters';
-import state from '~/repo/stores/state';
+import * as getters from '~/ide/stores/getters';
+import state from '~/ide/stores/state';
import { file } from '../helpers';
describe('Multi-file store getters', () => {
@@ -9,20 +9,6 @@ describe('Multi-file store getters', () => {
localState = state();
});
- describe('treeList', () => {
- it('returns flat tree list', () => {
- localState.tree.push(file('1'));
- localState.tree[0].tree.push(file('2'));
- localState.tree[0].tree[0].tree.push(file('3'));
-
- const treeList = getters.treeList(localState);
-
- expect(treeList.length).toBe(3);
- expect(treeList[1].name).toBe(localState.tree[0].tree[0].name);
- expect(treeList[2].name).toBe(localState.tree[0].tree[0].tree[0].name);
- });
- });
-
describe('changedFiles', () => {
it('returns a list of changed opened files', () => {
localState.openFiles.push(file());
@@ -49,7 +35,7 @@ describe('Multi-file store getters', () => {
localState.openFiles.push(file());
localState.openFiles.push(file('active'));
- expect(getters.activeFile(localState)).toBeUndefined();
+ expect(getters.activeFile(localState)).toBeNull();
});
});
@@ -67,18 +53,6 @@ describe('Multi-file store getters', () => {
});
});
- describe('isCollapsed', () => {
- it('returns true if state has open files', () => {
- localState.openFiles.push(file());
-
- expect(getters.isCollapsed(localState)).toBeTruthy();
- });
-
- it('returns false if state has no open files', () => {
- expect(getters.isCollapsed(localState)).toBeFalsy();
- });
- });
-
describe('canEditFile', () => {
beforeEach(() => {
localState.onTopOfBranch = true;
@@ -109,12 +83,6 @@ describe('Multi-file store getters', () => {
expect(getters.canEditFile(localState)).toBeFalsy();
});
-
- it('returns false if user can commit but on a branch', () => {
- localState.onTopOfBranch = false;
-
- expect(getters.canEditFile(localState)).toBeFalsy();
- });
});
describe('modifiedFiles', () => {
diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/repo/stores/mutations/branch_spec.js
index 3c06794d5e3..a7167537ef2 100644
--- a/spec/javascripts/repo/stores/mutations/branch_spec.js
+++ b/spec/javascripts/repo/stores/mutations/branch_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/repo/stores/mutations/branch';
-import state from '~/repo/stores/state';
+import mutations from '~/ide/stores/mutations/branch';
+import state from '~/ide/stores/state';
describe('Multi-file store branch mutations', () => {
let localState;
@@ -12,7 +12,7 @@ describe('Multi-file store branch mutations', () => {
it('sets currentBranch', () => {
mutations.SET_CURRENT_BRANCH(localState, 'master');
- expect(localState.currentBranch).toBe('master');
+ expect(localState.currentBranchId).toBe('master');
});
});
});
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
index 2f2835dde1f..947a60587df 100644
--- a/spec/javascripts/repo/stores/mutations/file_spec.js
+++ b/spec/javascripts/repo/stores/mutations/file_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/repo/stores/mutations/file';
-import state from '~/repo/stores/state';
+import mutations from '~/ide/stores/mutations/file';
+import state from '~/ide/stores/state';
import { file } from '../../helpers';
describe('Multi-file store file mutations', () => {
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
index 1c76cfed9c8..cf1248ba28b 100644
--- a/spec/javascripts/repo/stores/mutations/tree_spec.js
+++ b/spec/javascripts/repo/stores/mutations/tree_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/repo/stores/mutations/tree';
-import state from '~/repo/stores/state';
+import mutations from '~/ide/stores/mutations/tree';
+import state from '~/ide/stores/state';
import { file } from '../../helpers';
describe('Multi-file store tree mutations', () => {
diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js
index d1c9885e01d..5fd8ad94972 100644
--- a/spec/javascripts/repo/stores/mutations_spec.js
+++ b/spec/javascripts/repo/stores/mutations_spec.js
@@ -1,5 +1,5 @@
-import mutations from '~/repo/stores/mutations';
-import state from '~/repo/stores/state';
+import mutations from '~/ide/stores/mutations';
+import state from '~/ide/stores/state';
import { file } from '../helpers';
describe('Multi-file store mutations', () => {
@@ -65,11 +65,11 @@ describe('Multi-file store mutations', () => {
it('toggles editMode', () => {
mutations.TOGGLE_EDIT_MODE(localState);
- expect(localState.editMode).toBeTruthy();
+ expect(localState.editMode).toBeFalsy();
mutations.TOGGLE_EDIT_MODE(localState);
- expect(localState.editMode).toBeFalsy();
+ expect(localState.editMode).toBeTruthy();
});
});
@@ -85,14 +85,6 @@ describe('Multi-file store mutations', () => {
});
});
- describe('SET_COMMIT_REF', () => {
- it('sets currentRef', () => {
- mutations.SET_COMMIT_REF(localState, '123');
-
- expect(localState.currentRef).toBe('123');
- });
- });
-
describe('SET_ROOT', () => {
it('sets isRoot & initialRoot', () => {
mutations.SET_ROOT(localState, true);
@@ -107,11 +99,27 @@ describe('Multi-file store mutations', () => {
});
});
- describe('SET_PREVIOUS_URL', () => {
- it('sets previousUrl', () => {
- mutations.SET_PREVIOUS_URL(localState, 'testing');
+ describe('SET_LEFT_PANEL_COLLAPSED', () => {
+ it('sets left panel collapsed', () => {
+ mutations.SET_LEFT_PANEL_COLLAPSED(localState, true);
+
+ expect(localState.leftPanelCollapsed).toBeTruthy();
+
+ mutations.SET_LEFT_PANEL_COLLAPSED(localState, false);
+
+ expect(localState.leftPanelCollapsed).toBeFalsy();
+ });
+ });
+
+ describe('SET_RIGHT_PANEL_COLLAPSED', () => {
+ it('sets right panel collapsed', () => {
+ mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true);
+
+ expect(localState.rightPanelCollapsed).toBeTruthy();
+
+ mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false);
- expect(localState.previousUrl).toBe('testing');
+ expect(localState.rightPanelCollapsed).toBeFalsy();
});
});
});
diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js
index 37287c587d7..89745a2029e 100644
--- a/spec/javascripts/repo/stores/utils_spec.js
+++ b/spec/javascripts/repo/stores/utils_spec.js
@@ -1,4 +1,6 @@
-import * as utils from '~/repo/stores/utils';
+import * as utils from '~/ide/stores/utils';
+import state from '~/ide/stores/state';
+import { file } from '../helpers';
describe('Multi-file store utils', () => {
describe('setPageTitle', () => {
@@ -9,13 +11,28 @@ describe('Multi-file store utils', () => {
});
});
- describe('pushState', () => {
- it('calls history.pushState', () => {
- spyOn(history, 'pushState');
+ describe('treeList', () => {
+ let localState;
- utils.pushState('test');
+ beforeEach(() => {
+ localState = state();
+ });
+
+ it('returns flat tree list', () => {
+ localState.trees = [];
+ localState.trees['abcproject/mybranch'] = {
+ tree: [],
+ };
+ const baseTree = localState.trees['abcproject/mybranch'].tree;
+ baseTree.push(file('1'));
+ baseTree[0].tree.push(file('2'));
+ baseTree[0].tree[0].tree.push(file('3'));
+
+ const treeList = utils.treeList(localState, 'abcproject/mybranch');
- expect(history.pushState).toHaveBeenCalledWith({ url: 'test' }, '', 'test');
+ expect(treeList.length).toBe(3);
+ expect(treeList[1].name).toBe(baseTree[0].tree[0].name);
+ expect(treeList[2].name).toBe(baseTree[0].tree[0].tree[0].name);
});
});
@@ -52,10 +69,10 @@ describe('Multi-file store utils', () => {
});
describe('findIndexOfFile', () => {
- let state;
+ let localState;
beforeEach(() => {
- state = [{
+ localState = [{
path: '1',
}, {
path: '2',
@@ -63,7 +80,7 @@ describe('Multi-file store utils', () => {
});
it('finds in the index of an entry by path', () => {
- const index = utils.findIndexOfFile(state, {
+ const index = utils.findIndexOfFile(localState, {
path: '2',
});
@@ -72,10 +89,10 @@ describe('Multi-file store utils', () => {
});
describe('findEntry', () => {
- let state;
+ let localState;
beforeEach(() => {
- state = {
+ localState = {
tree: [{
type: 'tree',
name: 'test',
@@ -87,14 +104,14 @@ describe('Multi-file store utils', () => {
});
it('returns an entry found by name', () => {
- const foundEntry = utils.findEntry(state, 'tree', 'test');
+ const foundEntry = utils.findEntry(localState.tree, 'tree', 'test');
expect(foundEntry.type).toBe('tree');
expect(foundEntry.name).toBe('test');
});
it('returns undefined when no entry found', () => {
- const foundEntry = utils.findEntry(state, 'blob', 'test');
+ const foundEntry = utils.findEntry(localState.tree, 'blob', 'test');
expect(foundEntry).toBeUndefined();
});
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index d5754aaa9e7..fdead874209 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -10,7 +10,7 @@ describe('Commit component', () => {
CommitComponent = Vue.extend(commitComp);
});
- it('should render a code-fork icon if it does not represent a tag', () => {
+ it('should render a fork icon if it does not represent a tag', () => {
component = new CommitComponent({
propsData: {
tag: false,
@@ -30,7 +30,7 @@ describe('Commit component', () => {
},
}).$mount();
- expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork');
+ expect(component.$el.querySelector('.icon-container').children).toContain('svg');
});
describe('Given all the props', () => {
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index b0b78e34e0f..1465ef5855f 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -19,6 +19,22 @@ describe('Pagination component', () => {
});
describe('render', () => {
+ it('should not render anything', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 1,
+ page: 1,
+ perPage: 20,
+ previousPage: null,
+ total: 15,
+ totalPages: 1,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.innerHTML).not.toBeDefined();
+ });
+
describe('prev button', () => {
it('should be disabled and non clickable', () => {
component = mountComponet({
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 08beede62db..ef306f1cd4a 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -5,6 +5,7 @@ describe Banzai::Filter::RelativeLinkFilter do
contexts.reverse_merge!({
commit: commit,
project: project,
+ group: group,
project_wiki: project_wiki,
ref: ref,
requested_path: requested_path
@@ -25,7 +26,12 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<a href="#{path}">#{path}</a>)
end
+ def nested(element)
+ %(<div>#{element}</div>)
+ end
+
let(:project) { create(:project, :repository) }
+ let(:group) { nil }
let(:project_path) { project.full_path }
let(:ref) { 'markdown' }
let(:commit) { project.commit(ref) }
@@ -223,4 +229,79 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { nil } # force filter to use ref instead of commit
include_examples :valid_repository
end
+
+ context 'with a /upload/ URL' do
+ # not needed
+ let(:commit) { nil }
+ let(:ref) { nil }
+ let(:requested_path) { nil }
+
+ context 'to a project upload' do
+ it 'rebuilds relative URL for a link' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href'])
+ .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+
+ doc = filter(nested(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
+ expect(doc.at_css('a')['href'])
+ .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'rebuilds relative URL for an image' do
+ doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('img')['src'])
+ .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+
+ doc = filter(nested(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
+ expect(doc.at_css('img')['src'])
+ .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it 'supports Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class)
+ .to receive(:file_exists?).and_return(true)
+ allow_any_instance_of(described_class)
+ .to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match "/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png"
+ end
+ end
+
+ context 'to a group upload' do
+ let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
+ let(:group) { create(:group) }
+ let(:project) { nil }
+ let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+
+ it 'rewrites the link correctly' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(relative_path)
+ end
+
+ it 'rewrites the link correctly for subgroup' do
+ group.update!(parent: create(:group))
+
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(relative_path)
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
deleted file mode 100644
index 76bc0c36ab7..00000000000
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::UploadLinkFilter do
- def filter(doc, contexts = {})
- contexts.reverse_merge!({
- project: project
- })
-
- raw_filter(doc, contexts)
- end
-
- def raw_filter(doc, contexts = {})
- described_class.call(doc, contexts)
- end
-
- def image(path)
- %(<img src="#{path}" />)
- end
-
- def link(path)
- %(<a href="#{path}">#{path}</a>)
- end
-
- def nested_image(path)
- %(<div><img src="#{path}" /></div>)
- end
-
- def nested_link(path)
- %(<div><a href="#{path}">#{path}</a></div>)
- end
-
- let(:project) { create(:project) }
-
- shared_examples :preserve_unchanged do
- it 'does not modify any relative URL in anchor' do
- doc = filter(link('README.md'))
- expect(doc.at_css('a')['href']).to eq 'README.md'
- end
-
- it 'does not modify any relative URL in image' do
- doc = filter(image('files/images/logo-black.png'))
- expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
- end
- end
-
- it 'does not raise an exception on invalid URIs' do
- act = link("://foo")
- expect { filter(act) }.not_to raise_error
- end
-
- context 'with a valid repository' do
- it 'rebuilds relative URL for a link' do
- doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href'])
- .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
-
- doc = filter(nested_link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href'])
- .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
- end
-
- it 'rebuilds relative URL for an image' do
- doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('img')['src'])
- .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
-
- doc = filter(nested_image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('img')['src'])
- .to eq "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
- end
-
- it 'does not modify absolute URL' do
- doc = filter(link('http://example.com'))
- expect(doc.at_css('a')['href']).to eq 'http://example.com'
- end
-
- it 'supports Unicode filenames' do
- path = '/uploads/한글.png'
- escaped = Addressable::URI.escape(path)
-
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class)
- .to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class)
- .to receive(:image?).with(path).and_return(true)
-
- doc = filter(image(escaped))
- expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png"
- end
- end
-
- context 'in group context' do
- let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
- let(:group) { create(:group) }
- let(:filter_context) { { project: nil, group: group } }
- let(:relative_path) { "groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
-
- it 'rewrites the link correctly' do
- doc = raw_filter(upload_link, filter_context)
-
- expect(doc.at_css('a')['href']).to eq("#{Gitlab.config.gitlab.url}/#{relative_path}")
- end
-
- it 'rewrites the link correctly for subgroup' do
- subgroup = create(:group, parent: group)
- relative_path = "groups/#{subgroup.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
-
- doc = raw_filter(upload_link, { project: nil, group: subgroup })
-
- expect(doc.at_css('a')['href']).to eq("#{Gitlab.config.gitlab.url}/#{relative_path}")
- end
-
- it 'does not modify absolute URL' do
- doc = filter(link('http://example.com'), filter_context)
-
- expect(doc.at_css('a')['href']).to eq 'http://example.com'
- end
- end
-
- context 'when project or group context does not exist' do
- let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
-
- it 'does not raise error' do
- expect { raw_filter(upload_link, project: nil) }.not_to raise_error
- end
-
- it 'does not rewrite link' do
- doc = raw_filter(upload_link, project: nil)
-
- expect(doc.to_html).to eq upload_link
- end
- end
-end
diff --git a/spec/lib/gitlab/kubernetes/helm_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 15f99b0401f..69112fe90b1 100644
--- a/spec/lib/gitlab/kubernetes/helm_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -1,22 +1,23 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm do
+describe Gitlab::Kubernetes::Helm::Api do
let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) }
- let(:namespace) { Gitlab::Kubernetes::Namespace.new(described_class::NAMESPACE, client) }
+ let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
+ let(:namespace) { Gitlab::Kubernetes::Namespace.new(gitlab_namespace, client) }
let(:install_helm) { true }
let(:chart) { 'stable/a_chart' }
let(:application_name) { 'app_name' }
- let(:command) { Gitlab::Kubernetes::Helm::InstallCommand.new(application_name, install_helm, chart) }
+ let(:command) { Gitlab::Kubernetes::Helm::InstallCommand.new(application_name, install_helm: install_helm, chart: chart) }
subject { helm }
before do
- allow(Gitlab::Kubernetes::Namespace).to receive(:new).with(described_class::NAMESPACE, client).and_return(namespace)
+ allow(Gitlab::Kubernetes::Namespace).to receive(:new).with(gitlab_namespace, client).and_return(namespace)
end
describe '#initialize' do
it 'creates a namespace object' do
- expect(Gitlab::Kubernetes::Namespace).to receive(:new).with(described_class::NAMESPACE, client)
+ expect(Gitlab::Kubernetes::Namespace).to receive(:new).with(gitlab_namespace, client)
subject
end
@@ -41,7 +42,7 @@ describe Gitlab::Kubernetes::Helm do
let(:pod) { Kubeclient::Resource.new(status: { phase: phase }) } # partial representation
it 'fetches POD phase from kubernetes cluster' do
- expect(client).to receive(:get_pod).with(command.pod_name, described_class::NAMESPACE).once.and_return(pod)
+ expect(client).to receive(:get_pod).with(command.pod_name, gitlab_namespace).once.and_return(pod)
expect(subject.installation_status(command.pod_name)).to eq(phase)
end
@@ -52,7 +53,7 @@ describe Gitlab::Kubernetes::Helm do
let(:response) { RestClient::Response.new(log) }
it 'fetches POD phase from kubernetes cluster' do
- expect(client).to receive(:get_pod_log).with(command.pod_name, described_class::NAMESPACE).once.and_return(response)
+ expect(client).to receive(:get_pod_log).with(command.pod_name, gitlab_namespace).once.and_return(response)
expect(subject.installation_log(command.pod_name)).to eq(log)
end
@@ -60,41 +61,9 @@ describe Gitlab::Kubernetes::Helm do
describe '#delete_installation_pod!' do
it 'deletes the POD from kubernetes cluster' do
- expect(client).to receive(:delete_pod).with(command.pod_name, described_class::NAMESPACE).once
+ expect(client).to receive(:delete_pod).with(command.pod_name, gitlab_namespace).once
subject.delete_installation_pod!(command.pod_name)
end
end
-
- describe '#helm_init_command' do
- subject { helm.send(:helm_init_command, command) }
-
- context 'when command.install_helm is true' do
- let(:install_helm) { true }
-
- it { is_expected.to eq('helm init >/dev/null') }
- end
-
- context 'when command.install_helm is false' do
- let(:install_helm) { false }
-
- it { is_expected.to eq('helm init --client-only >/dev/null') }
- end
- end
-
- describe '#helm_install_command' do
- subject { helm.send(:helm_install_command, command) }
-
- context 'when command.chart is nil' do
- let(:chart) { nil }
-
- it { is_expected.to be_nil }
- end
-
- context 'when command.chart is set' do
- let(:chart) { 'stable/a_chart' }
-
- it { is_expected.to eq("helm install #{chart} --name #{application_name} --namespace #{namespace.name} >/dev/null")}
- end
- end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
new file mode 100644
index 00000000000..4afe48e72ad
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -0,0 +1,111 @@
+require 'rails_helper'
+
+describe Gitlab::Kubernetes::Helm::InstallCommand do
+ let(:prometheus) { create(:clusters_applications_prometheus) }
+
+ describe "#initialize" do
+ context "With all the params" do
+ subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
+
+ it 'should assign all parameters' do
+ expect(subject.name).to eq(prometheus.name)
+ expect(subject.install_helm).to be_truthy
+ expect(subject.chart).to eq(prometheus.chart)
+ expect(subject.chart_values_file).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
+ end
+ end
+
+ context 'when install_helm is not set' do
+ subject { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: true) }
+
+ it 'should set install_helm as false' do
+ expect(subject.install_helm).to be_falsy
+ end
+ end
+
+ context 'when chart is not set' do
+ subject { described_class.new(prometheus.name, install_helm: true) }
+
+ it 'should set chart as nil' do
+ expect(subject.chart).to be_falsy
+ end
+ end
+
+ context 'when chart_values_file is not set' do
+ subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart) }
+
+ it 'should set chart_values_file as nil' do
+ expect(subject.chart_values_file).to be_falsy
+ end
+ end
+ end
+
+ describe "#generate_script" do
+ let(:install_command) { described_class.new(prometheus.name, install_helm: install_helm) }
+ let(:client) { double('kubernetes client') }
+ let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
+ subject { install_command.send(:generate_script, namespace.name) }
+
+ context 'when install helm is true' do
+ let(:install_helm) { true }
+ let(:command) do
+ <<~MSG
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+
+ helm init >/dev/null
+ MSG
+ end
+
+ it 'should return appropriate command' do
+ is_expected.to eq(command)
+ end
+ end
+
+ context 'when install helm is false' do
+ let(:install_helm) { false }
+ let(:command) do
+ <<~MSG
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+
+ helm init --client-only >/dev/null
+ MSG
+ end
+
+ it 'should return appropriate command' do
+ is_expected.to eq(command)
+ end
+ end
+
+ context 'when chart is present' do
+ let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart) }
+ let(:command) do
+ <<~MSG.chomp
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+
+ helm init --client-only >/dev/null
+ helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} >/dev/null
+ MSG
+ end
+
+ it 'should return appropriate command' do
+ is_expected.to eq(command)
+ end
+ end
+ end
+
+ describe "#pod_name" do
+ let(:install_command) { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: true) }
+ subject { install_command.send(:pod_name) }
+
+ it { is_expected.to eq('install-prometheus') }
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
new file mode 100644
index 00000000000..906b10b96d4
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -0,0 +1,86 @@
+require 'rails_helper'
+
+describe Gitlab::Kubernetes::Helm::Pod do
+ describe '#generate' do
+ let(:cluster) { create(:cluster) }
+ let(:app) { create(:clusters_applications_prometheus, cluster: cluster) }
+ let(:command) { app.install_command }
+ let(:client) { double('kubernetes client') }
+ let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
+ subject { described_class.new(command, namespace.name, client) }
+
+ before do
+ allow(client).to receive(:create_config_map).and_return(nil)
+ end
+
+ shared_examples 'helm pod' do
+ it 'should generate a Kubeclient::Resource' do
+ expect(subject.generate).to be_a_kind_of(Kubeclient::Resource)
+ end
+
+ it 'should generate the appropriate metadata' do
+ metadata = subject.generate.metadata
+ expect(metadata.name).to eq("install-#{app.name}")
+ expect(metadata.namespace).to eq('gitlab-managed-apps')
+ expect(metadata.labels['gitlab.org/action']).to eq('install')
+ expect(metadata.labels['gitlab.org/application']).to eq(app.name)
+ end
+
+ it 'should generate a container spec' do
+ spec = subject.generate.spec
+ expect(spec.containers.count).to eq(1)
+ end
+
+ it 'should generate the appropriate specifications for the container' do
+ container = subject.generate.spec.containers.first
+ expect(container.name).to eq('helm')
+ expect(container.image).to eq('alpine:3.6')
+ expect(container.env.count).to eq(3)
+ expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
+ expect(container.command).to match_array(["/bin/sh"])
+ expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"])
+ end
+
+ it 'should include a never restart policy' do
+ spec = subject.generate.spec
+ expect(spec.restartPolicy).to eq('Never')
+ end
+ end
+
+ context 'with a configuration file' do
+ it_behaves_like 'helm pod'
+
+ it 'should include volumes for the container' do
+ container = subject.generate.spec.containers.first
+ expect(container.volumeMounts.first['name']).to eq('config-volume')
+ expect(container.volumeMounts.first['mountPath']).to eq('/etc/config')
+ end
+
+ it 'should include a volume inside the specification' do
+ spec = subject.generate.spec
+ expect(spec.volumes.first['name']).to eq('config-volume')
+ end
+
+ it 'should mount configMap specification in the volume' do
+ spec = subject.generate.spec
+ expect(spec.volumes.first.configMap['name']).to eq('values-config')
+ end
+ end
+
+ context 'without a configuration file' do
+ let(:app) { create(:clusters_applications_ingress, cluster: cluster) }
+
+ it_behaves_like 'helm pod'
+
+ it 'should not include volumeMounts inside the container' do
+ container = subject.generate.spec.containers.first
+ expect(container.volumeMounts).to be_nil
+ end
+
+ it 'should not a volume inside the specification' do
+ spec = subject.generate.spec
+ expect(spec.volumes).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 78767d06462..41a9d1d9c90 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -96,14 +96,17 @@ describe Gitlab::Metrics::MethodCall do
describe '#to_metric' do
it 'returns a Metric instance' do
+ expect(method_call).to receive(:real_time).and_return(4.0001)
+ expect(method_call).to receive(:cpu_time).and_return(3.0001)
+
method_call.measure { 'foo' }
metric = method_call.to_metric
expect(metric).to be_an_instance_of(Gitlab::Metrics::Metric)
expect(metric.series).to eq('rails_method_calls')
- expect(metric.values[:duration]).to be_a_kind_of(Numeric)
- expect(metric.values[:cpu_duration]).to be_a_kind_of(Numeric)
+ expect(metric.values[:duration]).to eq(4000)
+ expect(metric.values[:cpu_duration]).to eq(3000)
expect(metric.values[:call_count]).to be_an(Integer)
expect(metric.tags).to eq({ method: 'Foo#bar' })
@@ -116,13 +119,13 @@ describe Gitlab::Metrics::MethodCall do
end
it 'returns false when the total call time is not above the threshold' do
- expect(method_call).to receive(:real_time).and_return(9)
+ expect(method_call).to receive(:real_time).and_return(0.009)
expect(method_call.above_threshold?).to eq(false)
end
it 'returns true when the total call time is above the threshold' do
- expect(method_call).to receive(:real_time).and_return(9000)
+ expect(method_call).to receive(:real_time).and_return(9)
expect(method_call.above_threshold?).to eq(true)
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 4d94d8705fb..14afcdf5daa 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -29,19 +29,19 @@ describe Gitlab::Metrics::System do
describe '.cpu_time' do
it 'returns a Fixnum' do
- expect(described_class.cpu_time).to be_an(Integer)
+ expect(described_class.cpu_time).to be_an(Float)
end
end
describe '.real_time' do
it 'returns a Fixnum' do
- expect(described_class.real_time).to be_an(Integer)
+ expect(described_class.real_time).to be_an(Float)
end
end
describe '.monotonic_time' do
- it 'returns a Fixnum' do
- expect(described_class.monotonic_time).to be_an(Integer)
+ it 'returns a Float' do
+ expect(described_class.monotonic_time).to be_an(Float)
end
end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index e44a7c23452..a958baab44f 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -51,6 +51,17 @@ describe Gitlab::SearchResults do
expect(results.objects('merge_requests')).to include merge_request_2
end
+
+ it 'includes project filter by default' do
+ expect(results).to receive(:project_ids_relation).and_call_original
+ results.objects('merge_requests')
+ end
+
+ it 'it skips project filter if default is used' do
+ allow(results).to receive(:default_project_filter).and_return(true)
+ expect(results).not_to receive(:project_ids_relation)
+ results.objects('merge_requests')
+ end
end
it 'does not list issues on private projects' do
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index f8855079842..eb57abaf6ef 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -40,13 +40,13 @@ describe Clusters::Applications::Helm do
describe '#install_command' do
it 'has all the needed information' do
- expect(subject.install_command).to have_attributes(name: subject.name, install_helm: true, chart: nil)
+ expect(subject.install_command).to have_attributes(name: subject.name, install_helm: true)
end
end
describe 'status state machine' do
describe '#make_installing' do
- subject { create(:cluster_applications_helm, :scheduled) }
+ subject { create(:clusters_applications_helm, :scheduled) }
it 'is installing' do
subject.make_installing!
@@ -56,7 +56,7 @@ describe Clusters::Applications::Helm do
end
describe '#make_installed' do
- subject { create(:cluster_applications_helm, :installing) }
+ subject { create(:clusters_applications_helm, :installing) }
it 'is installed' do
subject.make_installed
@@ -66,7 +66,7 @@ describe Clusters::Applications::Helm do
end
describe '#make_errored' do
- subject { create(:cluster_applications_helm, :installing) }
+ subject { create(:clusters_applications_helm, :installing) }
let(:reason) { 'some errors' }
it 'is errored' do
@@ -78,7 +78,7 @@ describe Clusters::Applications::Helm do
end
describe '#make_scheduled' do
- subject { create(:cluster_applications_helm, :installable) }
+ subject { create(:clusters_applications_helm, :installable) }
it 'is scheduled' do
subject.make_scheduled
@@ -87,7 +87,7 @@ describe Clusters::Applications::Helm do
end
describe 'when was errored' do
- subject { create(:cluster_applications_helm, :errored) }
+ subject { create(:clusters_applications_helm, :errored) }
it 'clears #status_reason' do
expect(subject.status_reason).not_to be_nil
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index b83472e1944..619c088b0bf 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -4,105 +4,5 @@ describe Clusters::Applications::Ingress do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
- describe '#name' do
- it 'is .application_name' do
- expect(subject.name).to eq(described_class.application_name)
- end
-
- it 'is recorded in Clusters::Cluster::APPLICATIONS' do
- expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
- end
- end
-
- describe '#status' do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
-
- subject { described_class.new(cluster: cluster) }
-
- it 'defaults to :not_installable' do
- expect(subject.status_name).to be(:not_installable)
- end
-
- context 'when application helm is scheduled' do
- before do
- create(:cluster_applications_helm, :scheduled, cluster: cluster)
- end
-
- it 'defaults to :not_installable' do
- expect(subject.status_name).to be(:not_installable)
- end
- end
-
- context 'when application helm is installed' do
- before do
- create(:cluster_applications_helm, :installed, cluster: cluster)
- end
-
- it 'defaults to :installable' do
- expect(subject.status_name).to be(:installable)
- end
- end
- end
-
- describe '#install_command' do
- it 'has all the needed information' do
- expect(subject.install_command).to have_attributes(name: subject.name, install_helm: false, chart: subject.chart)
- end
- end
-
- describe 'status state machine' do
- describe '#make_installing' do
- subject { create(:cluster_applications_ingress, :scheduled) }
-
- it 'is installing' do
- subject.make_installing!
-
- expect(subject).to be_installing
- end
- end
-
- describe '#make_installed' do
- subject { create(:cluster_applications_ingress, :installing) }
-
- it 'is installed' do
- subject.make_installed
-
- expect(subject).to be_installed
- end
- end
-
- describe '#make_errored' do
- subject { create(:cluster_applications_ingress, :installing) }
- let(:reason) { 'some errors' }
-
- it 'is errored' do
- subject.make_errored(reason)
-
- expect(subject).to be_errored
- expect(subject.status_reason).to eq(reason)
- end
- end
-
- describe '#make_scheduled' do
- subject { create(:cluster_applications_ingress, :installable) }
-
- it 'is scheduled' do
- subject.make_scheduled
-
- expect(subject).to be_scheduled
- end
-
- describe 'when was errored' do
- subject { create(:cluster_applications_ingress, :errored) }
-
- it 'clears #status_reason' do
- expect(subject.status_reason).not_to be_nil
-
- subject.make_scheduled!
-
- expect(subject.status_reason).to be_nil
- end
- end
- end
- end
+ include_examples 'cluster application specs', described_class
end
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
new file mode 100644
index 00000000000..696099f7cf7
--- /dev/null
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+describe Clusters::Applications::Prometheus do
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to validate_presence_of(:cluster) }
+
+ include_examples 'cluster application specs', described_class
+
+ describe "#chart_values_file" do
+ subject { create(:clusters_applications_prometheus).chart_values_file }
+
+ it 'should return chart values file path' do
+ expect(subject).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
+ end
+ end
+end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 2683d21ddbe..799d7ced116 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -5,6 +5,9 @@ describe Clusters::Cluster do
it { is_expected.to have_many(:projects) }
it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:platform_kubernetes) }
+ it { is_expected.to have_one(:application_helm) }
+ it { is_expected.to have_one(:application_ingress) }
+ it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
@@ -190,11 +193,12 @@ describe Clusters::Cluster do
end
context 'when applications are created' do
- let!(:helm) { create(:cluster_applications_helm, cluster: cluster) }
- let!(:ingress) { create(:cluster_applications_ingress, cluster: cluster) }
+ let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
+ let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
it 'returns a list of created applications' do
- is_expected.to contain_exactly(helm, ingress)
+ is_expected.to contain_exactly(helm, ingress, prometheus)
end
end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 4d0b3245a13..2705421e540 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -112,22 +112,6 @@ describe DiffNote do
end
end
- describe "#for_line?" do
- context "when provided the correct diff line" do
- it "returns true" do
- expect(subject.for_line?(subject.diff_line)).to be true
- end
- end
-
- context "when provided a different diff line" do
- it "returns false" do
- some_line = subject.diff_file.diff_lines.first
-
- expect(subject.for_line?(some_line)).to be false
- end
- end
- end
-
describe "#active?" do
context "when noteable is a commit" do
subject { build(:diff_note_on_commit, project: project, position: position) }
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index a45a6088831..7c66c98231b 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -44,4 +44,31 @@ describe Identity do
end
end
end
+
+ context 'callbacks' do
+ context 'before_save' do
+ describe 'normalizes extern uid' do
+ let!(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com') }
+
+ it 'if extern_uid changes' do
+ expect(ldap_identity).not_to receive(:ensure_normalized_extern_uid)
+ ldap_identity.save
+ end
+
+ it 'if current_uid is nil' do
+ expect(ldap_identity).to receive(:ensure_normalized_extern_uid)
+
+ ldap_identity.update(extern_uid: nil)
+
+ expect(ldap_identity.extern_uid).to be_nil
+ end
+
+ it 'if extern_uid changed and not nil' do
+ ldap_identity.update(extern_uid: 'uid=john1,ou=PEOPLE,dc=example,dc=com')
+
+ expect(ldap_identity.extern_uid).to eq 'uid=john1,ou=people,dc=example,dc=com'
+ end
+ end
+ end
+ end
end
diff --git a/spec/rubocop/cop/active_record_dependent_spec.rb b/spec/rubocop/cop/active_record_dependent_spec.rb
deleted file mode 100644
index 599a032bfc5..00000000000
--- a/spec/rubocop/cop/active_record_dependent_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/active_record_dependent'
-
-describe RuboCop::Cop::ActiveRecordDependent do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- context 'inside the app/models directory' do
- it 'registers an offense when dependent: is used' do
- allow(cop).to receive(:in_model?).and_return(true)
-
- inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- end
- end
- end
-
- context 'outside the app/models directory' do
- it 'does nothing' do
- allow(cop).to receive(:in_model?).and_return(false)
-
- inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
-
- expect(cop.offenses).to be_empty
- end
- end
-end
diff --git a/spec/rubocop/cop/active_record_serialize_spec.rb b/spec/rubocop/cop/active_record_serialize_spec.rb
deleted file mode 100644
index b94b25cecd0..00000000000
--- a/spec/rubocop/cop/active_record_serialize_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/active_record_serialize'
-
-describe RuboCop::Cop::ActiveRecordSerialize do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- context 'inside the app/models directory' do
- it 'registers an offense when serialize is used' do
- allow(cop).to receive(:in_model?).and_return(true)
-
- inspect_source(cop, 'serialize :foo')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- end
- end
- end
-
- context 'outside the app/models directory' do
- it 'does nothing' do
- allow(cop).to receive(:in_model?).and_return(false)
-
- inspect_source(cop, 'serialize :foo')
-
- expect(cop.offenses).to be_empty
- end
- end
-end
diff --git a/spec/rubocop/cop/custom_error_class_spec.rb b/spec/rubocop/cop/custom_error_class_spec.rb
deleted file mode 100644
index 381d7871a40..00000000000
--- a/spec/rubocop/cop/custom_error_class_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-
-require 'rubocop'
-require 'rubocop/rspec/support'
-
-require_relative '../../../rubocop/cop/custom_error_class'
-
-describe RuboCop::Cop::CustomErrorClass do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- context 'when a class has a body' do
- it 'does nothing' do
- inspect_source(cop, 'class CustomError < StandardError; def foo; end; end')
-
- expect(cop.offenses).to be_empty
- end
- end
-
- context 'when a class has no explicit superclass' do
- it 'does nothing' do
- inspect_source(cop, 'class CustomError; end')
-
- expect(cop.offenses).to be_empty
- end
- end
-
- context 'when a class has a superclass that does not end in Error' do
- it 'does nothing' do
- inspect_source(cop, 'class CustomError < BasicObject; end')
-
- expect(cop.offenses).to be_empty
- end
- end
-
- context 'when a class is empty and inherits from a class ending in Error' do
- context 'when the class is on a single line' do
- let(:source) do
- <<-SOURCE
- module Foo
- class CustomError < Bar::Baz::BaseError; end
- end
- SOURCE
- end
-
- let(:expected) do
- <<-EXPECTED
- module Foo
- CustomError = Class.new(Bar::Baz::BaseError)
- end
- EXPECTED
- end
-
- it 'registers an offense' do
- expected_highlights = source.split("\n")[1].strip
-
- inspect_source(cop, source)
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([2])
- expect(cop.highlights).to contain_exactly(expected_highlights)
- end
- end
-
- it 'autocorrects to the right version' do
- autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
-
- expect(autocorrected).to eq(expected)
- end
- end
-
- context 'when the class is on multiple lines' do
- let(:source) do
- <<-SOURCE
- module Foo
- class CustomError < Bar::Baz::BaseError
- end
- end
- SOURCE
- end
-
- let(:expected) do
- <<-EXPECTED
- module Foo
- CustomError = Class.new(Bar::Baz::BaseError)
- end
- EXPECTED
- end
-
- it 'registers an offense' do
- expected_highlights = source.split("\n")[1..2].join("\n").strip
-
- inspect_source(cop, source)
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([2])
- expect(cop.highlights).to contain_exactly(expected_highlights)
- end
- end
-
- it 'autocorrects to the right version' do
- autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
-
- expect(autocorrected).to eq(expected)
- end
- end
- end
-end
diff --git a/spec/rubocop/cop/gem_fetcher_spec.rb b/spec/rubocop/cop/gem_fetcher_spec.rb
deleted file mode 100644
index c07f6a831dc..00000000000
--- a/spec/rubocop/cop/gem_fetcher_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-require 'rubocop'
-require 'rubocop/rspec/support'
-
-require_relative '../../../rubocop/cop/gem_fetcher'
-
-describe RuboCop::Cop::GemFetcher do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- context 'in Gemfile' do
- before do
- allow(cop).to receive(:gemfile?).and_return(true)
- end
-
- it 'registers an offense when a gem uses `git`' do
- inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- expect(cop.highlights).to eq(['git: "https://gitlab.com/foo/bar.git"'])
- end
- end
-
- it 'registers an offense when a gem uses `github`' do
- inspect_source(cop, 'gem "foo", github: "foo/bar.git"')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- expect(cop.highlights).to eq(['github: "foo/bar.git"'])
- end
- end
- end
-
- context 'outside of Gemfile' do
- it 'registers no offense' do
- inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
-
- expect(cop.offenses.size).to eq(0)
- end
- end
-end
diff --git a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
index 1fd40653f79..8e2d5f70353 100644
--- a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
+++ b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
@@ -12,7 +12,7 @@ describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do
let(:offending_lines) { options[:offending_lines] }
it 'registers an offense when instance variable is used in a module' do
- inspect_source(cop, source)
+ inspect_source(source)
aggregate_failures do
expect(cop.offenses.size).to eq(offending_lines.size)
@@ -23,7 +23,7 @@ describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do
shared_examples('not registering offense') do
it 'does not register offenses' do
- inspect_source(cop, source)
+ inspect_source(source)
expect(cop.offenses).to be_empty
end
diff --git a/spec/rubocop/cop/in_batches_spec.rb b/spec/rubocop/cop/in_batches_spec.rb
deleted file mode 100644
index 072481984c6..00000000000
--- a/spec/rubocop/cop/in_batches_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/in_batches'
-
-describe RuboCop::Cop::InBatches do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- it 'registers an offense when in_batches is used' do
- inspect_source(cop, 'foo.in_batches do; end')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- end
- end
-end
diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
index 7f406535dda..f5109287876 100644
--- a/spec/rubocop/cop/include_sidekiq_worker_spec.rb
+++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
+
require 'rubocop'
require 'rubocop/rspec/support'
+
require_relative '../../../rubocop/cop/include_sidekiq_worker'
describe RuboCop::Cop::IncludeSidekiqWorker do
@@ -13,7 +15,7 @@ describe RuboCop::Cop::IncludeSidekiqWorker do
let(:correct_source) { 'include ApplicationWorker' }
it 'registers an offense ' do
- inspect_source(cop, source)
+ inspect_source(source)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -23,7 +25,7 @@ describe RuboCop::Cop::IncludeSidekiqWorker do
end
it 'autocorrects to the right version' do
- autocorrected = autocorrect_source(cop, source)
+ autocorrected = autocorrect_source(source)
expect(autocorrected).to eq(correct_source)
end
diff --git a/spec/rubocop/cop/line_break_after_guard_clauses_spec.rb b/spec/rubocop/cop/line_break_after_guard_clauses_spec.rb
deleted file mode 100644
index 8899dc85384..00000000000
--- a/spec/rubocop/cop/line_break_after_guard_clauses_spec.rb
+++ /dev/null
@@ -1,160 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/line_break_after_guard_clauses'
-
-describe RuboCop::Cop::LineBreakAfterGuardClauses do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- shared_examples 'examples with guard clause' do |title|
- %w[if unless].each do |conditional|
- it "flags violation for #{title} #{conditional} without line breaks" do
- source = <<~RUBY
- #{title} #{conditional} condition
- do_stuff
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses.size).to eq(1)
- offense = cop.offenses.first
-
- expect(offense.line).to eq(1)
- expect(cop.highlights).to eq(["#{title} #{conditional} condition"])
- expect(offense.message).to eq('Add a line break after guard clauses')
- end
-
- it "doesn't flag violation for #{title} #{conditional} with line break" do
- source = <<~RUBY
- #{title} #{conditional} condition
-
- do_stuff
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} on multiple lines without line break" do
- source = <<~RUBY
- #{conditional} condition
- #{title}
- end
- do_stuff
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by end keyword" do
- source = <<~RUBY
- def test
- #{title} #{conditional} condition
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by elsif keyword" do
- source = <<~RUBY
- if model
- #{title} #{conditional} condition
- elsif
- do_something
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by else keyword" do
- source = <<~RUBY
- if model
- #{title} #{conditional} condition
- else
- do_something
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by when keyword" do
- source = <<~RUBY
- case model
- when condition_a
- #{title} #{conditional} condition
- when condition_b
- do_something
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by rescue keyword" do
- source = <<~RUBY
- begin
- #{title} #{conditional} condition
- rescue StandardError
- do_something
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by ensure keyword" do
- source = <<~RUBY
- def foo
- #{title} #{conditional} condition
- ensure
- do_something
- end
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} #{conditional} without line breaks when followed by another guard clause" do
- source = <<~RUBY
- #{title} #{conditional} condition
- #{title} #{conditional} condition
-
- do_stuff
- RUBY
- inspect_source(cop, source)
-
- expect(cop.offenses).to be_empty
- end
-
- it "autocorrects #{title} #{conditional} guard clauses without line break" do
- source = <<~RUBY
- #{title} #{conditional} condition
- do_stuff
- RUBY
- autocorrected = autocorrect_source(cop, source)
-
- expected_source = <<~RUBY
- #{title} #{conditional} condition
-
- do_stuff
- RUBY
- expect(autocorrected).to eql(expected_source)
- end
- end
- end
-
- %w[return fail raise next break throw].each do |example|
- it_behaves_like 'examples with guard clause', example
- end
-end
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
index 7cb24dc5646..1df1fffb94e 100644
--- a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
+
require 'rubocop'
require 'rubocop/rspec/support'
+
require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
@@ -10,7 +12,7 @@ describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
context 'outside of a migration' do
it 'does not register any offenses' do
- inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+ inspect_source('def up; add_foreign_key(:projects, :users, column: :user_id); end')
expect(cop.offenses).to be_empty
end
@@ -22,7 +24,7 @@ describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
end
it 'registers an offense when using add_foreign_key' do
- inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+ inspect_source('def up; add_foreign_key(:projects, :users, column: :user_id); end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
diff --git a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
index 19a5718b0b1..9c1ebcc0ced 100644
--- a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
@@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::AddConcurrentIndex do
end
it 'registers an offense when add_concurrent_index is used inside a change method' do
- inspect_source(cop, 'def change; add_concurrent_index :table, :column; end')
+ inspect_source('def change; add_concurrent_index :table, :column; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::AddConcurrentIndex do
end
it 'registers no offense when add_concurrent_index is used inside an up method' do
- inspect_source(cop, 'def up; add_concurrent_index :table, :column; end')
+ inspect_source('def up; add_concurrent_index :table, :column; end')
expect(cop.offenses.size).to eq(0)
end
@@ -33,7 +33,7 @@ describe RuboCop::Cop::Migration::AddConcurrentIndex do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, 'def change; add_concurrent_index :table, :column; end')
+ inspect_source('def change; add_concurrent_index :table, :column; end')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb
index 18df62dec3e..3a41c91add2 100644
--- a/spec/rubocop/cop/migration/add_timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb
@@ -53,7 +53,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
end
it 'registers an offense when the "add_timestamps" method is used' do
- inspect_source(cop, migration_with_add_timestamps)
+ inspect_source(migration_with_add_timestamps)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -62,7 +62,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
end
it 'does not register an offense when the "add_timestamps" method is not used' do
- inspect_source(cop, migration_without_add_timestamps)
+ inspect_source(migration_without_add_timestamps)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -70,7 +70,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
end
it 'does not register an offense when the "add_timestamps_with_timezone" method is used' do
- inspect_source(cop, migration_with_add_timestamps_with_timezone)
+ inspect_source(migration_with_add_timestamps_with_timezone)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -80,9 +80,9 @@ describe RuboCop::Cop::Migration::AddTimestamps do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, migration_with_add_timestamps)
- inspect_source(cop, migration_without_add_timestamps)
- inspect_source(cop, migration_with_add_timestamps_with_timezone)
+ inspect_source(migration_with_add_timestamps)
+ inspect_source(migration_without_add_timestamps)
+ inspect_source(migration_with_add_timestamps_with_timezone)
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb
index b1dfcf1b048..9e844325371 100644
--- a/spec/rubocop/cop/migration/datetime_spec.rb
+++ b/spec/rubocop/cop/migration/datetime_spec.rb
@@ -67,7 +67,7 @@ describe RuboCop::Cop::Migration::Datetime do
end
it 'registers an offense when the ":datetime" data type is used' do
- inspect_source(cop, migration_with_datetime)
+ inspect_source(migration_with_datetime)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -77,7 +77,7 @@ describe RuboCop::Cop::Migration::Datetime do
end
it 'registers an offense when the ":timestamp" data type is used' do
- inspect_source(cop, migration_with_timestamp)
+ inspect_source(migration_with_timestamp)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -87,7 +87,7 @@ describe RuboCop::Cop::Migration::Datetime do
end
it 'does not register an offense when the ":datetime" data type is not used' do
- inspect_source(cop, migration_without_datetime)
+ inspect_source(migration_without_datetime)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -95,7 +95,7 @@ describe RuboCop::Cop::Migration::Datetime do
end
it 'does not register an offense when the ":datetime_with_timezone" data type is used' do
- inspect_source(cop, migration_with_datetime_with_timezone)
+ inspect_source(migration_with_datetime_with_timezone)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -105,10 +105,10 @@ describe RuboCop::Cop::Migration::Datetime do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, migration_with_datetime)
- inspect_source(cop, migration_with_timestamp)
- inspect_source(cop, migration_without_datetime)
- inspect_source(cop, migration_with_datetime_with_timezone)
+ inspect_source(migration_with_datetime)
+ inspect_source(migration_with_timestamp)
+ inspect_source(migration_without_datetime)
+ inspect_source(migration_with_datetime_with_timezone)
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/hash_index_spec.rb b/spec/rubocop/cop/migration/hash_index_spec.rb
index 9a8576a19e5..5d53dde9a79 100644
--- a/spec/rubocop/cop/migration/hash_index_spec.rb
+++ b/spec/rubocop/cop/migration/hash_index_spec.rb
@@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::HashIndex do
end
it 'registers an offense when creating a hash index' do
- inspect_source(cop, 'def change; add_index :table, :column, using: :hash; end')
+ inspect_source('def change; add_index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::HashIndex do
end
it 'registers an offense when creating a concurrent hash index' do
- inspect_source(cop, 'def change; add_concurrent_index :table, :column, using: :hash; end')
+ inspect_source('def change; add_concurrent_index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -34,7 +34,7 @@ describe RuboCop::Cop::Migration::HashIndex do
end
it 'registers an offense when creating a hash index using t.index' do
- inspect_source(cop, 'def change; t.index :table, :column, using: :hash; end')
+ inspect_source('def change; t.index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -45,7 +45,7 @@ describe RuboCop::Cop::Migration::HashIndex do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, 'def change; index :table, :column, using: :hash; end')
+ inspect_source('def change; index :table, :column, using: :hash; end')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/remove_column_spec.rb b/spec/rubocop/cop/migration/remove_column_spec.rb
index 89112f01723..f1a64f431bd 100644
--- a/spec/rubocop/cop/migration/remove_column_spec.rb
+++ b/spec/rubocop/cop/migration/remove_column_spec.rb
@@ -21,7 +21,7 @@ describe RuboCop::Cop::Migration::RemoveColumn do
end
it 'registers an offense when remove_column is used in the change method' do
- inspect_source(cop, source('change'))
+ inspect_source(source('change'))
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -30,7 +30,7 @@ describe RuboCop::Cop::Migration::RemoveColumn do
end
it 'registers an offense when remove_column is used in the up method' do
- inspect_source(cop, source('up'))
+ inspect_source(source('up'))
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -39,7 +39,7 @@ describe RuboCop::Cop::Migration::RemoveColumn do
end
it 'registers no offense when remove_column is used in the down method' do
- inspect_source(cop, source('down'))
+ inspect_source(source('down'))
expect(cop.offenses.size).to eq(0)
end
@@ -52,7 +52,7 @@ describe RuboCop::Cop::Migration::RemoveColumn do
end
it 'registers no offense' do
- inspect_source(cop, source)
+ inspect_source(source)
expect(cop.offenses.size).to eq(0)
end
@@ -60,7 +60,7 @@ describe RuboCop::Cop::Migration::RemoveColumn do
context 'outside of a migration' do
it 'registers no offense' do
- inspect_source(cop, source)
+ inspect_source(source)
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
index a714bf4e5d5..a23d5d022e3 100644
--- a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
@@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
end
it 'registers an offense when remove_concurrent_index is used inside a change method' do
- inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end')
+ inspect_source('def change; remove_concurrent_index :table, :column; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
end
it 'registers no offense when remove_concurrent_index is used inside an up method' do
- inspect_source(cop, 'def up; remove_concurrent_index :table, :column; end')
+ inspect_source('def up; remove_concurrent_index :table, :column; end')
expect(cop.offenses.size).to eq(0)
end
@@ -33,7 +33,7 @@ describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end')
+ inspect_source('def change; remove_concurrent_index :table, :column; end')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb
index 31923cb7429..bbf2227e512 100644
--- a/spec/rubocop/cop/migration/remove_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_index_spec.rb
@@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::RemoveIndex do
end
it 'registers an offense when remove_index is used' do
- inspect_source(cop, 'def change; remove_index :table, :column; end')
+ inspect_source('def change; remove_index :table, :column; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::RemoveIndex do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, 'def change; remove_index :table, :column; end')
+ inspect_source('def change; remove_index :table, :column; end')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/reversible_add_column_with_default_spec.rb b/spec/rubocop/cop/migration/reversible_add_column_with_default_spec.rb
index 3723d635083..ba8cd2c6c4a 100644
--- a/spec/rubocop/cop/migration/reversible_add_column_with_default_spec.rb
+++ b/spec/rubocop/cop/migration/reversible_add_column_with_default_spec.rb
@@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::ReversibleAddColumnWithDefault do
end
it 'registers an offense when add_column_with_default is used inside a change method' do
- inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+ inspect_source('def change; add_column_with_default :table, :column, default: false; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::ReversibleAddColumnWithDefault do
end
it 'registers no offense when add_column_with_default is used inside an up method' do
- inspect_source(cop, 'def up; add_column_with_default :table, :column, default: false; end')
+ inspect_source('def up; add_column_with_default :table, :column, default: false; end')
expect(cop.offenses.size).to eq(0)
end
@@ -33,7 +33,7 @@ describe RuboCop::Cop::Migration::ReversibleAddColumnWithDefault do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+ inspect_source('def change; add_column_with_default :table, :column, default: false; end')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
index 1006594499a..1c4f18fbcc3 100644
--- a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
+++ b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
@@ -32,7 +32,7 @@ describe RuboCop::Cop::Migration::SaferBooleanColumn do
sources_and_offense.each do |source, offense|
context "given the source \"#{source}\"" do
it "registers the offense matching \"#{offense}\"" do
- inspect_source(cop, source)
+ inspect_source(source)
aggregate_failures do
expect(cop.offenses.first.message).to match(offense)
@@ -49,7 +49,7 @@ describe RuboCop::Cop::Migration::SaferBooleanColumn do
inoffensive_sources.each do |source|
context "given the source \"#{source}\"" do
it "registers no offense" do
- inspect_source(cop, source)
+ inspect_source(source)
aggregate_failures do
expect(cop.offenses).to be_empty
@@ -61,14 +61,14 @@ describe RuboCop::Cop::Migration::SaferBooleanColumn do
end
it 'registers no offense for tables not listed in SMALL_TABLES' do
- inspect_source(cop, "add_column :large_table, :column, :boolean")
+ inspect_source("add_column :large_table, :column, :boolean")
expect(cop.offenses).to be_empty
end
it 'registers no offense for non-boolean columns' do
table = described_class::SMALL_TABLES.sample
- inspect_source(cop, "add_column :#{table}, :column, :string")
+ inspect_source("add_column :#{table}, :column, :string")
expect(cop.offenses).to be_empty
end
@@ -77,7 +77,7 @@ describe RuboCop::Cop::Migration::SaferBooleanColumn do
context 'outside of migration' do
it 'registers no offense' do
table = described_class::SMALL_TABLES.sample
- inspect_source(cop, "add_column :#{table}, :column, :boolean")
+ inspect_source("add_column :#{table}, :column, :boolean")
expect(cop.offenses).to be_empty
end
diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb
index cdf1423d0c5..685bdb21803 100644
--- a/spec/rubocop/cop/migration/timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/timestamps_spec.rb
@@ -62,7 +62,7 @@ describe RuboCop::Cop::Migration::Timestamps do
end
it 'registers an offense when the "timestamps" method is used' do
- inspect_source(cop, migration_with_timestamps)
+ inspect_source(migration_with_timestamps)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -71,7 +71,7 @@ describe RuboCop::Cop::Migration::Timestamps do
end
it 'does not register an offense when the "timestamps" method is not used' do
- inspect_source(cop, migration_without_timestamps)
+ inspect_source(migration_without_timestamps)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -79,7 +79,7 @@ describe RuboCop::Cop::Migration::Timestamps do
end
it 'does not register an offense when the "timestamps_with_timezone" method is used' do
- inspect_source(cop, migration_with_timestamps_with_timezone)
+ inspect_source(migration_with_timestamps_with_timezone)
aggregate_failures do
expect(cop.offenses.size).to eq(0)
@@ -89,9 +89,9 @@ describe RuboCop::Cop::Migration::Timestamps do
context 'outside of migration' do
it 'registers no offense' do
- inspect_source(cop, migration_with_timestamps)
- inspect_source(cop, migration_without_timestamps)
- inspect_source(cop, migration_with_timestamps_with_timezone)
+ inspect_source(migration_with_timestamps)
+ inspect_source(migration_without_timestamps)
+ inspect_source(migration_with_timestamps_with_timezone)
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 38b8f439a55..1c8ab0ad5d2 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
+
require 'rubocop'
require 'rubocop/rspec/support'
+
require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
describe RuboCop::Cop::Migration::UpdateColumnInBatches do
@@ -25,7 +27,7 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
context 'outside of a migration' do
it 'does not register any offenses' do
- inspect_source(cop, migration_code)
+ inspect_source(migration_code)
expect(cop.offenses).to be_empty
end
@@ -49,7 +51,7 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
let(:relative_spec_filepath) { Pathname.new(spec_filepath).relative_path_from(tmp_rails_root) }
it 'registers an offense when using update_column_in_batches' do
- inspect_source(cop, migration_code, @migration_file)
+ inspect_source(migration_code, @migration_file)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -72,7 +74,7 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
end
it 'does not register any offenses' do
- inspect_source(cop, migration_code, @migration_file)
+ inspect_source(migration_code, @migration_file)
expect(cop.offenses).to be_empty
end
diff --git a/spec/rubocop/cop/migration/update_large_table_spec.rb b/spec/rubocop/cop/migration/update_large_table_spec.rb
index 17b19e139e4..ef724fc8bad 100644
--- a/spec/rubocop/cop/migration/update_large_table_spec.rb
+++ b/spec/rubocop/cop/migration/update_large_table_spec.rb
@@ -18,7 +18,7 @@ describe RuboCop::Cop::Migration::UpdateLargeTable do
shared_examples 'large tables' do |update_method|
described_class::LARGE_TABLES.each do |table|
it "registers an offense for the #{table} table" do
- inspect_source(cop, "#{update_method} :#{table}, :column, default: true")
+ inspect_source("#{update_method} :#{table}, :column, default: true")
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -37,7 +37,7 @@ describe RuboCop::Cop::Migration::UpdateLargeTable do
end
it 'registers no offense for non-blacklisted tables' do
- inspect_source(cop, "add_column_with_default :table, :column, default: true")
+ inspect_source("add_column_with_default :table, :column, default: true")
expect(cop.offenses).to be_empty
end
@@ -45,7 +45,7 @@ describe RuboCop::Cop::Migration::UpdateLargeTable do
it 'registers no offense for non-blacklisted methods' do
table = described_class::LARGE_TABLES.sample
- inspect_source(cop, "some_other_method :#{table}, :column, default: true")
+ inspect_source("some_other_method :#{table}, :column, default: true")
expect(cop.offenses).to be_empty
end
@@ -55,13 +55,13 @@ describe RuboCop::Cop::Migration::UpdateLargeTable do
let(:table) { described_class::LARGE_TABLES.sample }
it 'registers no offense for add_column_with_default' do
- inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
+ inspect_source("add_column_with_default :#{table}, :column, default: true")
expect(cop.offenses).to be_empty
end
it 'registers no offense for update_column_in_batches' do
- inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
+ inspect_source("add_column_with_default :#{table}, :column, default: true")
expect(cop.offenses).to be_empty
end
diff --git a/spec/rubocop/cop/polymorphic_associations_spec.rb b/spec/rubocop/cop/polymorphic_associations_spec.rb
deleted file mode 100644
index 49959aa6419..00000000000
--- a/spec/rubocop/cop/polymorphic_associations_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/polymorphic_associations'
-
-describe RuboCop::Cop::PolymorphicAssociations do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- context 'inside the app/models directory' do
- it 'registers an offense when polymorphic: true is used' do
- allow(cop).to receive(:in_model?).and_return(true)
-
- inspect_source(cop, 'belongs_to :foo, polymorphic: true')
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- end
- end
- end
-
- context 'outside the app/models directory' do
- it 'does nothing' do
- allow(cop).to receive(:in_model?).and_return(false)
-
- inspect_source(cop, 'belongs_to :foo, polymorphic: true')
-
- expect(cop.offenses).to be_empty
- end
- end
-end
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
index bc47b45cad7..84e6eb7d87f 100644
--- a/spec/rubocop/cop/project_path_helper_spec.rb
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -15,7 +15,7 @@ describe RuboCop::Cop::ProjectPathHelper do
let(:correct_source) { 'edit_project_issue_path(@issue.project, @issue)' }
it 'registers an offense' do
- inspect_source(cop, source)
+ inspect_source(source)
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -25,7 +25,7 @@ describe RuboCop::Cop::ProjectPathHelper do
end
it 'autocorrects to the right version' do
- autocorrected = autocorrect_source(cop, source)
+ autocorrected = autocorrect_source(source)
expect(autocorrected).to eq(correct_source)
end
@@ -33,7 +33,7 @@ describe RuboCop::Cop::ProjectPathHelper do
context 'when using namespace_project with a different namespace' do
it 'registers no offense' do
- inspect_source(cop, 'edit_namespace_project_issue_path(namespace, project)')
+ inspect_source('edit_namespace_project_issue_path(namespace, project)')
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/redirect_with_status_spec.rb b/spec/rubocop/cop/redirect_with_status_spec.rb
deleted file mode 100644
index 5ad63567f84..00000000000
--- a/spec/rubocop/cop/redirect_with_status_spec.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'spec_helper'
-
-require 'rubocop'
-require 'rubocop/rspec/support'
-
-require_relative '../../../rubocop/cop/redirect_with_status'
-
-describe RuboCop::Cop::RedirectWithStatus do
- include CopHelper
-
- subject(:cop) { described_class.new }
- let(:controller_fixture_without_status) do
- %q(
- class UserController < ApplicationController
- def show
- user = User.find(params[:id])
- redirect_to user_path if user.name == 'John Wick'
- end
-
- def destroy
- user = User.find(params[:id])
-
- if user.destroy
- redirect_to root_path
- else
- render :show
- end
- end
- end
- )
- end
-
- let(:controller_fixture_with_status) do
- %q(
- class UserController < ApplicationController
- def show
- user = User.find(params[:id])
- redirect_to user_path if user.name == 'John Wick'
- end
-
- def destroy
- user = User.find(params[:id])
-
- if user.destroy
- redirect_to root_path, status: 302
- else
- render :show
- end
- end
- end
- )
- end
-
- context 'in controller' do
- before do
- allow(cop).to receive(:in_controller?).and_return(true)
- end
-
- it 'registers an offense when a "destroy" action uses "redirect_to" without "status"' do
- inspect_source(cop, controller_fixture_without_status)
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([12]) # 'redirect_to' is located on 12th line in controller_fixture.
- expect(cop.highlights).to eq(['redirect_to'])
- end
- end
-
- it 'does not register an offense when a "destroy" action uses "redirect_to" with "status"' do
- inspect_source(cop, controller_fixture_with_status)
-
- aggregate_failures do
- expect(cop.offenses.size).to eq(0)
- end
- end
- end
-
- context 'outside of controller' do
- it 'registers no offense' do
- inspect_source(cop, controller_fixture_without_status)
- inspect_source(cop, controller_fixture_with_status)
-
- expect(cop.offenses.size).to eq(0)
- end
- end
-end
diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb
index 4e859b6f6fa..659633f6467 100644
--- a/spec/rubocop/cop/rspec/env_assignment_spec.rb
+++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb
@@ -17,7 +17,7 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
shared_examples 'an offensive ENV#[]= call' do |content|
it "registers an offense for `#{content}`" do
- inspect_source(cop, content, source_file)
+ inspect_source(content, source_file)
expect(cop.offenses.size).to eq(1)
expect(cop.offenses.map(&:line)).to eq([1])
@@ -27,7 +27,7 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
shared_examples 'an autocorrected ENV#[]= call' do |content, autocorrected_content|
it "registers an offense for `#{content}` and autocorrects it to `#{autocorrected_content}`" do
- autocorrected = autocorrect_source(cop, content, source_file)
+ autocorrected = autocorrect_source(content, source_file)
expect(autocorrected).to eql(autocorrected_content)
end
@@ -51,7 +51,7 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
context 'outside of a spec file' do
it "does not register an offense for `#{OFFENSE_CALL_SINGLE_QUOTES_KEY}` in a non-spec file" do
- inspect_source(cop, OFFENSE_CALL_SINGLE_QUOTES_KEY)
+ inspect_source(OFFENSE_CALL_SINGLE_QUOTES_KEY)
expect(cop.offenses.size).to eq(0)
end
diff --git a/spec/rubocop/cop/rspec/single_line_hook_spec.rb b/spec/rubocop/cop/rspec/single_line_hook_spec.rb
deleted file mode 100644
index 6cf0831d3ad..00000000000
--- a/spec/rubocop/cop/rspec/single_line_hook_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'spec_helper'
-
-require 'rubocop'
-require 'rubocop/rspec/support'
-
-require_relative '../../../../rubocop/cop/rspec/single_line_hook'
-
-describe RuboCop::Cop::RSpec::SingleLineHook do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- # Override `CopHelper#inspect_source` to always appear to be in a spec file,
- # so that our RSpec-only cop actually runs
- def inspect_source(*args)
- super(*args, 'foo_spec.rb')
- end
-
- it 'registers an offense for a single-line `before` block' do
- inspect_source(cop, 'before { do_something }')
-
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- expect(cop.highlights).to eq(['before { do_something }'])
- end
-
- it 'registers an offense for a single-line `after` block' do
- inspect_source(cop, 'after(:each) { undo_something }')
-
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- expect(cop.highlights).to eq(['after(:each) { undo_something }'])
- end
-
- it 'registers an offense for a single-line `around` block' do
- inspect_source(cop, 'around { |ex| do_something_else }')
-
- expect(cop.offenses.size).to eq(1)
- expect(cop.offenses.map(&:line)).to eq([1])
- expect(cop.highlights).to eq(['around { |ex| do_something_else }'])
- end
-
- it 'ignores a multi-line `before` block' do
- inspect_source(cop, ['before do',
- ' do_something',
- 'end'])
-
- expect(cop.offenses.size).to eq(0)
- end
-
- it 'ignores a multi-line `after` block' do
- inspect_source(cop, ['after(:each) do',
- ' undo_something',
- 'end'])
-
- expect(cop.offenses.size).to eq(0)
- end
-
- it 'ignores a multi-line `around` block' do
- inspect_source(cop, ['around do |ex|',
- ' do_something_else',
- 'end'])
-
- expect(cop.offenses.size).to eq(0)
- end
-end
diff --git a/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb b/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb
deleted file mode 100644
index 278662d32ea..00000000000
--- a/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'spec_helper'
-require 'rubocop'
-require 'rubocop/rspec/support'
-require_relative '../../../../rubocop/cop/rspec/verbose_include_metadata'
-
-describe RuboCop::Cop::RSpec::VerboseIncludeMetadata do
- include CopHelper
-
- subject(:cop) { described_class.new }
-
- let(:source_file) { 'foo_spec.rb' }
-
- # Override `CopHelper#inspect_source` to always appear to be in a spec file,
- # so that our RSpec-only cop actually runs
- def inspect_source(*args)
- super(*args, source_file)
- end
-
- shared_examples 'examples with include syntax' do |title|
- it "flags violation for #{title} examples that uses verbose include syntax" do
- inspect_source(cop, "#{title} 'Test', js: true do; end")
-
- expect(cop.offenses.size).to eq(1)
- offense = cop.offenses.first
-
- expect(offense.line).to eq(1)
- expect(cop.highlights).to eq(["#{title} 'Test', js: true"])
- expect(offense.message).to eq('Use `:js` instead of `js: true`.')
- end
-
- it "doesn't flag violation for #{title} examples that uses compact include syntax", :aggregate_failures do
- inspect_source(cop, "#{title} 'Test', :js do; end")
-
- expect(cop.offenses).to be_empty
- end
-
- it "doesn't flag violation for #{title} examples that uses flag: symbol" do
- inspect_source(cop, "#{title} 'Test', flag: :symbol do; end")
-
- expect(cop.offenses).to be_empty
- end
-
- it "autocorrects #{title} examples that uses verbose syntax into compact syntax" do
- autocorrected = autocorrect_source(cop, "#{title} 'Test', js: true do; end", source_file)
-
- expect(autocorrected).to eql("#{title} 'Test', :js do; end")
- end
- end
-
- %w(describe context feature example_group it specify example scenario its).each do |example|
- it_behaves_like 'examples with include syntax', example
- end
-end
diff --git a/spec/rubocop/cop/sidekiq_options_queue_spec.rb b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
index a31de381631..7f237d5ffbb 100644
--- a/spec/rubocop/cop/sidekiq_options_queue_spec.rb
+++ b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
+
require 'rubocop'
require 'rubocop/rspec/support'
+
require_relative '../../../rubocop/cop/sidekiq_options_queue'
describe RuboCop::Cop::SidekiqOptionsQueue do
@@ -9,7 +11,7 @@ describe RuboCop::Cop::SidekiqOptionsQueue do
subject(:cop) { described_class.new }
it 'registers an offense when `sidekiq_options` is used with the `queue` option' do
- inspect_source(cop, 'sidekiq_options queue: "some_queue"')
+ inspect_source('sidekiq_options queue: "some_queue"')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
@@ -19,7 +21,7 @@ describe RuboCop::Cop::SidekiqOptionsQueue do
end
it 'does not register an offense when `sidekiq_options` is used with another option' do
- inspect_source(cop, 'sidekiq_options retry: false')
+ inspect_source('sidekiq_options retry: false')
expect(cop.offenses).to be_empty
end
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index 87c7b2ad36e..b5a55b4ef6e 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe ClusterApplicationEntity do
describe '#as_json' do
- let(:application) { build(:cluster_applications_helm) }
+ let(:application) { build(:clusters_applications_helm) }
subject { described_class.new(application).as_json }
it 'has name' do
@@ -18,7 +18,7 @@ describe ClusterApplicationEntity do
end
context 'when application is errored' do
- let(:application) { build(:cluster_applications_helm, :errored) }
+ let(:application) { build(:clusters_applications_helm, :errored) }
it 'has corresponded data' do
expect(subject[:status]).to eq(:errored)
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index 1ad974c774b..b259cb92962 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -36,8 +36,8 @@ describe MergeRequestSerializer do
context 'no serializer' do
let(:serializer) { nil }
- it 'raises an error' do
- expect { json_entity }.to raise_error(NoMethodError)
+ it 'falls back to the widget entity' do
+ expect(json_entity).to match_schema('entities/merge_request_widget')
end
end
end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 75fc05d36e9..6894c1797b0 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Clusters::Applications::CheckInstallationProgressService do
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
- let(:application) { create(:cluster_applications_helm, :installing) }
+ let(:application) { create(:clusters_applications_helm, :installing) }
let(:service) { described_class.new(application) }
let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN }
let(:errors) { nil }
@@ -33,7 +33,7 @@ describe Clusters::Applications::CheckInstallationProgressService do
end
context 'when timeouted' do
- let(:application) { create(:cluster_applications_helm, :timeouted) }
+ let(:application) { create(:clusters_applications_helm, :timeouted) }
it_behaves_like 'a terminated installation'
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 054a49ffedf..ad175226e92 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -2,17 +2,19 @@ require 'spec_helper'
describe Clusters::Applications::InstallService do
describe '#execute' do
- let(:application) { create(:cluster_applications_helm, :scheduled) }
+ let(:application) { create(:clusters_applications_helm, :scheduled) }
+ let!(:install_command) { application.install_command }
let(:service) { described_class.new(application) }
- let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm) }
+ let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
before do
+ allow(service).to receive(:install_command).and_return(install_command)
allow(service).to receive(:helm_api).and_return(helm_client)
end
context 'when there are no errors' do
before do
- expect(helm_client).to receive(:install).with(application.install_command)
+ expect(helm_client).to receive(:install).with(install_command)
allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil)
end
@@ -33,7 +35,7 @@ describe Clusters::Applications::InstallService do
context 'when k8s cluster communication fails' do
before do
error = KubeException.new(500, 'system failure', nil)
- expect(helm_client).to receive(:install).with(application.install_command).and_raise(error)
+ expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
it 'make the application errored' do
@@ -45,7 +47,7 @@ describe Clusters::Applications::InstallService do
end
context 'when application cannot be persisted' do
- let(:application) { build(:cluster_applications_helm, :scheduled) }
+ let(:application) { build(:clusters_applications_helm, :scheduled) }
it 'make the application errored' do
expect(application).to receive(:make_installing!).once.and_raise(ActiveRecord::RecordInvalid)
diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb
index 047a6e44dab..473dbcbb692 100644
--- a/spec/services/clusters/applications/schedule_installation_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_installation_service_spec.rb
@@ -34,7 +34,7 @@ describe Clusters::Applications::ScheduleInstallationService do
end
context 'when installation is already in progress' do
- let(:application) { create(:cluster_applications_helm, :installing) }
+ let(:application) { create(:clusters_applications_helm, :installing) }
let(:cluster) { application.cluster }
it_behaves_like 'a failing service'
diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb
new file mode 100644
index 00000000000..e9f8f0efe6b
--- /dev/null
+++ b/spec/services/files/delete_service_spec.rb
@@ -0,0 +1,64 @@
+require "spec_helper"
+
+describe Files::DeleteService do
+ subject { described_class.new(project, user, commit_params) }
+
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:branch_name) { project.default_branch }
+ let(:last_commit_sha) { nil }
+
+ let(:commit_params) do
+ {
+ file_path: file_path,
+ commit_message: "Delete File",
+ last_commit_sha: last_commit_sha,
+ start_project: project,
+ start_branch: project.default_branch,
+ branch_name: branch_name
+ }
+ end
+
+ shared_examples 'successfully deletes the file' do
+ it 'returns a hash with the :success status' do
+ results = subject.execute
+
+ expect(results[:status]).to match(:success)
+ end
+
+ it 'deletes the file' do
+ subject.execute
+
+ blob = project.repository.blob_at_branch(project.default_branch, file_path)
+
+ expect(blob).to be_nil
+ end
+ end
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe "#execute" do
+ context "when the file's last commit sha does not match the supplied last_commit_sha" do
+ let(:last_commit_sha) { "foo" }
+
+ it "returns a hash with the correct error message and a :error status " do
+ expect { subject.execute }
+ .to raise_error(Files::UpdateService::FileChangedError,
+ "You are attempting to delete a file that has been previously updated.")
+ end
+ end
+
+ context "when the file's last commit sha does match the supplied last_commit_sha" do
+ let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).sha }
+
+ it_behaves_like 'successfully deletes the file'
+ end
+
+ context "when the last_commit_sha is not supplied" do
+ it_behaves_like 'successfully deletes the file'
+ end
+ end
+end
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
new file mode 100644
index 00000000000..085a28d267f
--- /dev/null
+++ b/spec/services/files/multi_service_spec.rb
@@ -0,0 +1,144 @@
+require "spec_helper"
+
+describe Files::MultiService do
+ subject { described_class.new(project, user, commit_params) }
+
+ let(:project) { create(: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(: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
+ }
+ ]
+ end
+
+ let(:commit_params) do
+ {
+ commit_message: "Update File",
+ branch_name: branch_name,
+ start_branch: branch_name,
+ actions: actions
+ }
+ end
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe '#execute' do
+ context 'with a valid action' do
+ it 'returns a hash with the :success status ' do
+ results = subject.execute
+
+ expect(results[:status]).to eq(:success)
+ end
+ end
+
+ context 'with an invalid action' do
+ let(:action) { 'rename' }
+
+ it 'returns a hash with the :error status ' do
+ results = subject.execute
+
+ expect(results[:status]).to eq(:error)
+ expect(results[:message]).to match(/Unknown action/)
+ end
+ end
+
+ describe 'Updating files' do
+ context 'when the file has been previously updated' do
+ before do
+ update_file(original_file_path)
+ end
+
+ it 'rejects the commit' do
+ results = subject.execute
+
+ expect(results[:status]).to eq(:error)
+ expect(results[:message]).to match(new_file_path)
+ end
+ end
+
+ context 'when the file have not been modified' do
+ it 'accepts the commit' do
+ results = subject.execute
+
+ expect(results[:status]).to eq(:success)
+ end
+ end
+ end
+
+ context 'when moving a file' do
+ let(:action) { 'move' }
+ let(:new_file_path) { 'files/ruby/new_popen.rb' }
+
+ context 'when original file has been updated' do
+ before do
+ update_file(original_file_path)
+ end
+
+ it 'rejects the commit' do
+ results = subject.execute
+
+ expect(results[:status]).to eq(:error)
+ expect(results[:message]).to match(original_file_path)
+ end
+ end
+
+ context 'when original file have not been updated' do
+ it 'moves the file' do
+ results = subject.execute
+ blob = project.repository.blob_at_branch(branch_name, new_file_path)
+
+ expect(results[:status]).to eq(:success)
+ expect(blob).to be_present
+ end
+ end
+ end
+
+ context 'when file status validation is skipped' do
+ let(:action) { 'create' }
+ let(:new_file_path) { 'files/ruby/new_file.rb' }
+
+ it 'does not check the last commit' do
+ expect(Gitlab::Git::Commit).not_to receive(:last_for_path)
+
+ subject.execute
+ end
+
+ it 'creates the file' do
+ subject.execute
+
+ blob = project.repository.blob_at_branch(branch_name, new_file_path)
+
+ expect(blob).to be_present
+ end
+ end
+ end
+
+ def update_file(path)
+ params = {
+ file_path: path,
+ start_branch: branch_name,
+ branch_name: branch_name,
+ commit_message: 'Update file',
+ file_content: 'New content'
+ }
+
+ Files::UpdateService.new(project, user, params).execute
+ end
+end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index f2b35a8fadf..2cad695d7c4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -289,6 +289,18 @@ describe Issues::MoveService do
.to raise_error(StandardError, /Cannot move issue/)
end
end
+
+ context 'project issue hooks' do
+ let(:hook) { create(:project_hook, project: old_project, issues_events: true) }
+
+ it 'executes project issue hooks' do
+ # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
+ # but since the entire spec run takes place in a transaction, we never
+ # actually get to the `after_commit` hook that queues these jobs.
+ expect { move_service.execute(old_issue, new_project) }
+ .not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
+ end
+ end
end
describe 'move permissions' do
diff --git a/spec/support/cluster_application_spec.rb b/spec/support/cluster_application_spec.rb
new file mode 100644
index 00000000000..ab77910a050
--- /dev/null
+++ b/spec/support/cluster_application_spec.rb
@@ -0,0 +1,105 @@
+shared_examples 'cluster application specs' do
+ let(:factory_name) { described_class.to_s.downcase.gsub("::", "_") }
+
+ describe '#name' do
+ it 'is .application_name' do
+ expect(subject.name).to eq(described_class.application_name)
+ end
+
+ it 'is recorded in Clusters::Cluster::APPLICATIONS' do
+ expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
+ end
+ end
+
+ describe '#status' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ subject { described_class.new(cluster: cluster) }
+
+ it 'defaults to :not_installable' do
+ expect(subject.status_name).to be(:not_installable)
+ end
+
+ context 'when application helm is scheduled' do
+ before do
+ create(factory_name, :scheduled, cluster: cluster)
+ end
+
+ it 'defaults to :not_installable' do
+ expect(subject.status_name).to be(:not_installable)
+ end
+ end
+
+ context 'when application helm is installed' do
+ before do
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+ end
+
+ it 'defaults to :installable' do
+ expect(subject.status_name).to be(:installable)
+ end
+ end
+ end
+
+ describe '#install_command' do
+ it 'has all the needed information' do
+ expect(subject.install_command).to have_attributes(name: subject.name, install_helm: false)
+ end
+ end
+
+ describe 'status state machine' do
+ describe '#make_installing' do
+ subject { create(factory_name, :scheduled) }
+
+ it 'is installing' do
+ subject.make_installing!
+
+ expect(subject).to be_installing
+ end
+ end
+
+ describe '#make_installed' do
+ subject { create(factory_name, :installing) }
+
+ it 'is installed' do
+ subject.make_installed
+
+ expect(subject).to be_installed
+ end
+ end
+
+ describe '#make_errored' do
+ subject { create(factory_name, :installing) }
+ let(:reason) { 'some errors' }
+
+ it 'is errored' do
+ subject.make_errored(reason)
+
+ expect(subject).to be_errored
+ expect(subject.status_reason).to eq(reason)
+ end
+ end
+
+ describe '#make_scheduled' do
+ subject { create(factory_name, :installable) }
+
+ it 'is scheduled' do
+ subject.make_scheduled
+
+ expect(subject).to be_scheduled
+ end
+
+ describe 'when was errored' do
+ subject { create(factory_name, :errored) }
+
+ it 'clears #status_reason' do
+ expect(subject.status_reason).not_to be_nil
+
+ subject.make_scheduled!
+
+ expect(subject.status_reason).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
new file mode 100644
index 00000000000..dd9496deb4d
--- /dev/null
+++ b/vendor/prometheus/values.yaml
@@ -0,0 +1,134 @@
+alertmanager: |
+ enabled: false
+
+kubeStateMetrics: |
+ enabled: 'false'
+
+nodeExporter: |
+ enabled: 'false'
+
+pushgateway: |
+ enabled: 'false'
+
+serverFiles: |
+ alerts: ''
+ rules: ''
+
+ prometheus.yml: |-
+ rule_files: |
+ - /etc/config/rules
+ - /etc/config/alerts
+ scrape_configs: |
+ - job_name: prometheus
+ static_configs: |
+ - targets:
+ - localhost:9090
+
+ - job_name: 'kubernetes-apiservers'
+ kubernetes_sd_configs: |
+ - role: endpoints
+ scheme: https
+
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ insecure_skip_verify: true
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: default;kubernetes;https
+ - job_name: 'kubernetes-nodes'
+ scheme: https
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ insecure_skip_verify: true
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ kubernetes_sd_configs:
+ - role: node
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels: [__meta_kubernetes_node_name]
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics
+
+ - job_name: 'kubernetes-service-endpoints'
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs: |
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
+ action: keep
+ regex: 'true'
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
+ action: replace
+ target_label: __scheme__
+ regex: (https?)
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
+ action: replace
+ target_label: __metrics_path__
+ regex: (.+)
+ - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
+ action: replace
+ target_label: __address__
+ regex: (.+)(?::\d+);(\d+)
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels: [__meta_kubernetes_namespace]
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels: [__meta_kubernetes_service_name]
+ action: replace
+ target_label: kubernetes_name
+ - job_name: 'prometheus-pushgateway'
+ honor_labels: true
+ kubernetes_sd_configs: |
+ - role: service
+ relabel_configs: |
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
+ action: keep
+ regex: pushgateway
+ - job_name: 'kubernetes-services'
+ metrics_path: /probe
+ params: |
+ module: [http_2xx]
+ kubernetes_sd_configs: |
+ - role: service
+ relabel_configs: |
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
+ action: keep
+ regex: 'true'
+ - source_labels: [__address__]
+ target_label: __param_target
+ - target_label: __address__
+ replacement: blackbox
+ - source_labels: [__param_target]
+ target_label: instance
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: kubernetes_namespace
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: kubernetes_name
+ - job_name: 'kubernetes-pods'
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
+ action: keep
+ regex: 'true'
+ - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
+ action: replace
+ target_label: __metrics_path__
+ regex: (.+)
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels: [__meta_kubernetes_namespace]
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ action: replace
+ target_label: kubernetes_pod_name
diff --git a/yarn.lock b/yarn.lock
index 55d0d33c9f2..358a1baec42 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.3.0":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.3.0.tgz#07f2aa75d6e0e857eaa20c38a3bad7e6c22c420c"
+"@gitlab-org/gitlab-svgs@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.4.0.tgz#83c0a76485c1378babf2e83456b4d2442efa98e8"
"@types/jquery@^2.0.40":
version "2.0.48"
@@ -6528,6 +6528,10 @@ vue-resource@^1.3.4:
dependencies:
got "^7.0.0"
+vue-router@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
+
vue-style-loader@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.0.3.tgz#623658f81506aef9d121cdc113a4f5c9cac32df7"