summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsbigelow <sbigelow@gitlab.com>2018-11-29 10:51:15 -0500
committersbigelow <sbigelow@gitlab.com>2018-11-29 10:51:15 -0500
commit413817bc32e2e12fbe4d49beb0b957237ce094cf (patch)
tree85e0cf116206a933ecb3236f64fe11ffd796fe19
parent1adf5137198b338bbf63f886fd8405685a02d904 (diff)
parent6775dafa3816239f6fa1b12428df42572be5a158 (diff)
downloadgitlab-ce-48496-merge-request-refactor-does-not-highlight-selected-line.tar.gz
Merge master into 48496-merge-request-refactor-does-not-highlight-selected-line48496-merge-request-refactor-does-not-highlight-selected-line
-rw-r--r--.gitlab-ci.yml158
-rw-r--r--CHANGELOG.md92
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock17
-rw-r--r--Gemfile.rails4.lock17
-rw-r--r--app/assets/javascripts/api.js30
-rw-r--r--app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js3
-rw-r--r--app/assets/javascripts/behaviors/secret_values.js8
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js3
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js8
-rw-r--r--app/assets/javascripts/boards/index.js4
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js4
-rw-r--r--app/assets/javascripts/build_artifacts.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js4
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue57
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js3
-rw-r--r--app/assets/javascripts/contextual_sidebar.js3
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue1
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue21
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue17
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue43
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue14
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue5
-rw-r--r--app/assets/javascripts/diffs/store/actions.js10
-rw-r--r--app/assets/javascripts/diffs/store/getters.js34
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js4
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js30
-rw-r--r--app/assets/javascripts/diffs/store/utils.js4
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js6
-rw-r--r--app/assets/javascripts/environments/index.js8
-rw-r--r--app/assets/javascripts/groups/index.js3
-rw-r--r--app/assets/javascripts/header.js3
-rw-r--r--app/assets/javascripts/ide/index.js4
-rw-r--r--app/assets/javascripts/ide/services/index.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js12
-rw-r--r--app/assets/javascripts/importer_status.js4
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/app.vue96
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/item.vue137
-rw-r--r--app/assets/javascripts/issuable_suggestions/index.js38
-rw-r--r--app/assets/javascripts/issuable_suggestions/queries/issues.graphql26
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue10
-rw-r--r--app/assets/javascripts/landing.js3
-rw-r--r--app/assets/javascripts/lib/graphql.js9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js18
-rw-r--r--app/assets/javascripts/merge_request_tabs.js31
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue48
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js4
-rw-r--r--app/assets/javascripts/namespace_select.js3
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue87
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue28
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue29
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue12
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue2
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue159
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue3
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue98
-rw-r--r--app/assets/javascripts/notes/stores/actions.js20
-rw-r--r--app/assets/javascripts/notes/stores/getters.js44
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js3
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js38
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js3
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js5
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue3
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js4
-rw-r--r--app/assets/javascripts/performance_bar/services/performance_bar_service.js4
-rw-r--r--app/assets/javascripts/profile/profile.js3
-rw-r--r--app/assets/javascripts/toggle_buttons.js6
-rw-r--r--app/assets/javascripts/usage_ping_consent.js6
-rw-r--r--app/assets/stylesheets/bootstrap.scss2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss9
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/flash.scss10
-rw-r--r--app/assets/stylesheets/framework/header.scss20
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss41
-rw-r--r--app/assets/stylesheets/pages/environments.scss15
-rw-r--r--app/assets/stylesheets/pages/issuable.scss34
-rw-r--r--app/assets/stylesheets/pages/notes.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss5
-rw-r--r--app/controllers/admin/impersonations_controller.rb13
-rw-r--r--app/controllers/admin/users_controller.rb5
-rw-r--r--app/controllers/application_controller.rb48
-rw-r--r--app/controllers/concerns/notes_actions.rb10
-rw-r--r--app/controllers/concerns/sessionless_authentication.rb28
-rw-r--r--app/controllers/dashboard/projects_controller.rb1
-rw-r--r--app/controllers/dashboard/todos_controller.rb10
-rw-r--r--app/controllers/dashboard_controller.rb3
-rw-r--r--app/controllers/graphql_controller.rb1
-rw-r--r--app/controllers/groups_controller.rb3
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/projects/commits_controller.rb1
-rw-r--r--app/controllers/projects/imports_controller.rb16
-rw-r--r--app/controllers/projects/issues_controller.rb24
-rw-r--r--app/controllers/projects/milestones_controller.rb21
-rw-r--r--app/controllers/projects/network_controller.rb8
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/users_controller.rb1
-rw-r--r--app/graphql/resolvers/issues_resolver.rb25
-rw-r--r--app/graphql/types/issue_type.rb47
-rw-r--r--app/graphql/types/label_type.rb12
-rw-r--r--app/graphql/types/milestone_type.rb17
-rw-r--r--app/graphql/types/order.rb8
-rw-r--r--app/graphql/types/permission_types/issue.rb14
-rw-r--r--app/graphql/types/project_type.rb5
-rw-r--r--app/graphql/types/sort.rb10
-rw-r--r--app/graphql/types/user_type.rb14
-rw-r--r--app/helpers/milestones_helper.rb13
-rw-r--r--app/helpers/tree_helper.rb4
-rw-r--r--app/helpers/users_helper.rb4
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/models/ci/build_trace_chunk.rb21
-rw-r--r--app/models/clusters/applications/ingress.rb4
-rw-r--r--app/models/clusters/applications/knative.rb32
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/environment_status.rb17
-rw-r--r--app/models/merge_request_diff.rb3
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/pool_repository.rb19
-rw-r--r--app/models/project.rb194
-rw-r--r--app/models/project_import_state.rb29
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/remote_mirror.rb2
-rw-r--r--app/models/repository.rb11
-rw-r--r--app/models/site_statistic.rb76
-rw-r--r--app/models/storage/hashed_project.rb8
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/wiki_page.rb6
-rw-r--r--app/policies/commit_policy.rb2
-rw-r--r--app/policies/milestone_policy.rb5
-rw-r--r--app/policies/note_policy.rb9
-rw-r--r--app/presenters/issue_presenter.rb9
-rw-r--r--app/presenters/user_presenter.rb9
-rw-r--r--app/services/access_token_validation_service.rb6
-rw-r--r--app/services/ci/archive_trace_service.rb35
-rw-r--r--app/services/clusters/applications/base_helm_service.rb19
-rw-r--r--app/services/clusters/applications/check_ingress_ip_address_service.rb2
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb14
-rw-r--r--app/services/clusters/applications/install_service.rb6
-rw-r--r--app/services/files/multi_service.rb11
-rw-r--r--app/services/projects/create_service.rb4
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/devise/mailer/email_changed.html.haml12
-rw-r--r--app/views/devise/mailer/email_changed.text.erb10
-rw-r--r--app/views/groups/labels/edit.html.haml4
-rw-r--r--app/views/groups/labels/new.html.haml5
-rw-r--r--app/views/groups/milestones/edit.html.haml5
-rw-r--r--app/views/groups/milestones/new.html.haml15
-rw-r--r--app/views/import/bitbucket/status.html.haml5
-rw-r--r--app/views/import/bitbucket_server/status.html.haml5
-rw-r--r--app/views/import/fogbugz/status.html.haml5
-rw-r--r--app/views/import/gitlab/status.html.haml5
-rw-r--r--app/views/import/google_code/status.html.haml5
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/header/_new_dropdown.haml26
-rw-r--r--app/views/notify/note_project_snippet_email.html.haml (renamed from app/views/notify/note_snippet_email.html.haml)0
-rw-r--r--app/views/notify/note_project_snippet_email.text.erb (renamed from app/views/notify/note_snippet_email.text.erb)0
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml94
-rw-r--r--app/views/projects/diffs/_diffs.html.haml6
-rw-r--r--app/views/projects/diffs/_file.html.haml3
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/new.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/milestones/edit.html.haml3
-rw-r--r--app/views/projects/milestones/new.html.haml3
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml2
-rw-r--r--app/views/projects/new.html.haml4
-rw-r--r--app/views/projects/wikis/_sidebar_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml10
-rw-r--r--app/views/projects/wikis/history.html.haml4
-rw-r--r--app/views/projects/wikis/show.html.haml6
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml4
-rw-r--r--app/views/snippets/_actions.html.haml26
-rw-r--r--app/views/snippets/_snippets.html.haml2
-rw-r--r--app/views/snippets/_snippets_scope_menu.html.haml8
-rw-r--r--app/views/snippets/edit.html.haml5
-rw-r--r--app/views/snippets/index.html.haml6
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/notes/_actions.html.haml4
-rw-r--r--app/views/snippets/show.html.haml4
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/workers/archive_trace_worker.rb2
-rw-r--r--app/workers/ci/archive_traces_cron_worker.rb14
-rw-r--r--app/workers/concerns/gitlab/github_import/stage_methods.rb2
-rw-r--r--app/workers/concerns/project_import_options.rb2
-rw-r--r--app/workers/concerns/project_start_import.rb6
-rw-r--r--app/workers/gitlab/github_import/advance_stage_worker.rb11
-rw-r--r--app/workers/gitlab/github_import/refresh_import_jid_worker.rb14
-rw-r--r--app/workers/gitlab/github_import/stage/import_base_data_worker.rb2
-rw-r--r--app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb2
-rw-r--r--app/workers/repository_fork_worker.rb4
-rw-r--r--app/workers/repository_import_worker.rb4
-rw-r--r--app/workers/stuck_import_jobs_worker.rb2
-rw-r--r--changelogs/archive.md2
-rw-r--r--changelogs/unreleased/33705-merge-request-rebase-api.yml5
-rw-r--r--changelogs/unreleased/38495-calendar-activities-in-timezone.yml5
-rw-r--r--changelogs/unreleased/40385-prohibit_impersonation.yml5
-rw-r--r--changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml6
-rw-r--r--changelogs/unreleased/50839-webide-mr-dropdown-filter.yml5
-rw-r--r--changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml5
-rw-r--r--changelogs/unreleased/51083-fix-move-api.yml5
-rw-r--r--changelogs/unreleased/51792-dont-delete-failed-install-pods.yml5
-rw-r--r--changelogs/unreleased/52276-jump-to-top-in-merge-request.yml5
-rw-r--r--changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml5
-rw-r--r--changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml5
-rw-r--r--changelogs/unreleased/53763-fix-encrypt-columns-data-loss.yml5
-rw-r--r--changelogs/unreleased/53778-remove-site-statistics.yml5
-rw-r--r--changelogs/unreleased/53874-navbar-lowres.yml5
-rw-r--r--changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml5
-rw-r--r--changelogs/unreleased/54571-runner-tags.yml5
-rw-r--r--changelogs/unreleased/_acet-fix-flash-styling.yml5
-rw-r--r--changelogs/unreleased/bvl-use-shell-writeref.yml5
-rw-r--r--changelogs/unreleased/ce-54109-fix_user_by_any_email.yml5
-rw-r--r--changelogs/unreleased/check-if-fetched-data-does-is-complete.yml5
-rw-r--r--changelogs/unreleased/fix-deadlock-chunked-io.yml5
-rw-r--r--changelogs/unreleased/force-reload-arguments-1.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-snippets.yml5
-rw-r--r--changelogs/unreleased/gt-update-env-metrics-empty-state.yml5
-rw-r--r--changelogs/unreleased/include-new-link-in-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/lock-trace-writes.yml5
-rw-r--r--changelogs/unreleased/non-webkit-scrollbar-fixing.yml5
-rw-r--r--changelogs/unreleased/remove-deployment-status-hack-from-backend.yml5
-rw-r--r--changelogs/unreleased/security-182-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/security-2736-prometheus-ssrf.yml5
-rw-r--r--changelogs/unreleased/security-bvl-exposure-in-commits-list.yml5
-rw-r--r--changelogs/unreleased/security-email-change-notification.yml5
-rw-r--r--changelogs/unreleased/security-fix-pat-web-access.yml5
-rw-r--r--changelogs/unreleased/security-fix-uri-xss-applications.yml5
-rw-r--r--changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml5
-rw-r--r--changelogs/unreleased/security-fj-crlf-injection.yml5
-rw-r--r--changelogs/unreleased/security-guest-comments.yml5
-rw-r--r--changelogs/unreleased/security-guest-comments_2.yml5
-rw-r--r--changelogs/unreleased/security-issue_51301.yml5
-rw-r--r--changelogs/unreleased/security-mermaid-xss.yml5
-rw-r--r--changelogs/unreleased/security-pages-toctou-race.yml6
-rw-r--r--changelogs/unreleased/security-private-group.yml6
-rw-r--r--changelogs/unreleased/security-stored-xss-for-environments.yml5
-rw-r--r--changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml5
-rw-r--r--changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml5
-rw-r--r--changelogs/unreleased/sh-fix-hash-filename-handling.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-38317.yml5
-rw-r--r--changelogs/unreleased/sh-handle-string-null-bytes.yml5
-rw-r--r--changelogs/unreleased/tc-backfill-full-path-config.yml5
-rw-r--r--changelogs/unreleased/triggermesh-phase2-external-ip.yml5
-rw-r--r--changelogs/unreleased/triggermesh-phase2-knative-description.yml5
-rw-r--r--changelogs/unreleased/unicorn-monkey-patch.yml5
-rw-r--r--changelogs/unreleased/upgrade_kubeclient_400.yml5
-rw-r--r--changelogs/unreleased/winh-merge-request-commit-discussion.yml5
-rw-r--r--config.ru4
-rw-r--r--config/application.rb3
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/attr_encrypted_no_db_connection.rb24
-rw-r--r--config/initializers/devise.rb3
-rw-r--r--config/initializers/doorkeeper.rb7
-rw-r--r--config/initializers/kubeclient.rb15
-rw-r--r--config/initializers/rack_attack_global.rb10
-rw-r--r--config/webpack.config.js12
-rw-r--r--db/migrate/20181108091549_cleanup_environments_external_url.rb18
-rw-r--r--db/migrate/20181116050532_knative_external_ip.rb14
-rw-r--r--db/migrate/20181120082911_rename_repositories_pool_repositories.rb11
-rw-r--r--db/migrate/20181123042307_drop_site_statistics.rb22
-rw-r--r--db/migrate/20181123135036_drop_not_null_constraint_pool_repository_disk_path.rb9
-rw-r--r--db/migrate/20181126153547_remove_notes_index_on_updated_at.rb32
-rw-r--r--db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb27
-rw-r--r--db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb32
-rw-r--r--db/schema.rb26
-rw-r--r--doc/README.md104
-rw-r--r--doc/administration/high_availability/gitlab.md2
-rw-r--r--doc/administration/high_availability/redis.md4
-rw-r--r--doc/administration/logs.md19
-rw-r--r--doc/api/README.md39
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/discussions.md8
-rw-r--r--doc/api/merge_requests.md65
-rw-r--r--doc/ci/docker/using_docker_images.md3
-rw-r--r--doc/ci/examples/README.md12
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md2
-rw-r--r--doc/ci/img/pipelines-goal.pngbin0 -> 36933 bytes
-rw-r--r--doc/ci/img/pipelines-goal.svg4
-rw-r--r--doc/ci/img/types-of-pipelines.pngbin0 -> 31245 bytes
-rw-r--r--doc/ci/img/types-of-pipelines.svg4
-rw-r--r--doc/ci/interactive_web_terminal/index.md9
-rw-r--r--doc/ci/junit_test_reports.md6
-rw-r--r--doc/ci/pipelines.md4
-rw-r--r--doc/ci/services/redis.md2
-rw-r--r--doc/ci/yaml/README.md22
-rw-r--r--doc/development/architecture.md4
-rw-r--r--doc/development/code_review.md71
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/development/db_dump.md2
-rw-r--r--doc/development/ee_features.md2
-rw-r--r--doc/development/fe_guide/droplab/droplab.md2
-rw-r--r--doc/development/fe_guide/graphql.md83
-rw-r--r--doc/development/fe_guide/index.md3
-rw-r--r--doc/development/github_importer.md2
-rw-r--r--doc/development/logging.md10
-rw-r--r--doc/development/testing_guide/best_practices.md8
-rw-r--r--doc/development/testing_guide/review_apps.md84
-rw-r--r--doc/development/ui_guide.md2
-rw-r--r--doc/development/ux_guide/animation.md2
-rw-r--r--doc/development/ux_guide/basics.md2
-rw-r--r--doc/development/ux_guide/components.md2
-rw-r--r--doc/development/ux_guide/copy.md2
-rw-r--r--doc/development/ux_guide/features.md2
-rw-r--r--doc/development/ux_guide/illustrations.md2
-rw-r--r--doc/development/ux_guide/index.md2
-rw-r--r--doc/development/ux_guide/principles.md2
-rw-r--r--doc/development/ux_guide/resources.md2
-rw-r--r--doc/development/ux_guide/surfaces.md2
-rw-r--r--doc/development/ux_guide/tips.md2
-rw-r--r--doc/development/ux_guide/users.md2
-rw-r--r--doc/install/aws/index.md4
-rw-r--r--doc/install/azure/index.md2
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md2
-rw-r--r--doc/integration/github.md34
-rw-r--r--doc/integration/img/github_app.pngbin29330 -> 128040 bytes
-rw-r--r--doc/integration/img/github_app_entry.pngbin0 -> 83603 bytes
-rw-r--r--doc/integration/img/github_register_app.pngbin0 -> 120981 bytes
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/ssh/README.md2
-rw-r--r--doc/topics/authentication/index.md1
-rw-r--r--doc/topics/git/how_to_install_git/index.md2
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/update/8.17-to-9.0.md2
-rw-r--r--doc/update/9.0-to-9.1.md2
-rw-r--r--doc/update/9.1-to-9.2.md2
-rw-r--r--doc/update/9.2-to-9.3.md2
-rw-r--r--doc/update/9.3-to-9.4.md2
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md2
-rw-r--r--doc/user/project/clusters/index.md13
-rw-r--r--doc/user/project/img/issue_board.pngbin327718 -> 289964 bytes
-rw-r--r--doc/user/project/img/issue_boards_core.pngbin61230 -> 119989 bytes
-rw-r--r--doc/user/project/img/issue_boards_premium.pngbin72434 -> 99171 bytes
-rw-r--r--doc/user/project/issues/img/issue_board.pngbin55931 -> 86095 bytes
-rw-r--r--doc/user/project/labels.md2
-rw-r--r--doc/user/project/merge_requests/img/merge_request_widget.pngbin11036 -> 8936 bytes
-rw-r--r--doc/user/project/merge_requests/resolve_conflicts.md36
-rw-r--r--doc/user/project/milestones/index.md2
-rw-r--r--doc/user/project/pages/getting_started_part_three.md2
-rw-r--r--doc/user/project/pages/index.md2
-rw-r--r--doc/user/project/pages/introduction.md4
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/api_guard.rb6
-rw-r--r--lib/api/entities.rb13
-rw-r--r--lib/api/merge_requests.rb35
-rw-r--r--lib/banzai/filter/spaced_link_filter.rb3
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb5
-rw-r--r--lib/extracts_path.rb5
-rw-r--r--lib/gitlab/auth/request_authenticator.rb14
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb42
-rw-r--r--lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb209
-rw-r--r--lib/gitlab/background_migration/encrypt_columns.rb14
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb2
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb2
-rw-r--r--lib/gitlab/ci/trace.rb63
-rw-r--r--lib/gitlab/ci/trace/chunked_io.rb29
-rw-r--r--lib/gitlab/ci/trace/stream.rb11
-rw-r--r--lib/gitlab/database.rb11
-rw-r--r--lib/gitlab/exclusive_lease_helpers.rb2
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/git_ref_validator.rb6
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb3
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb11
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb2
-rw-r--r--lib/gitlab/github_import/parallel_importer.rb3
-rw-r--r--lib/gitlab/graphql/loaders/batch_model_loader.rb29
-rw-r--r--lib/gitlab/http_io.rb8
-rw-r--r--lib/gitlab/import_export/import_export.yml5
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb6
-rw-r--r--lib/gitlab/kubernetes/logger.rb11
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb3
-rw-r--r--lib/gitlab/url_blocker.rb37
-rw-r--r--lib/tasks/gitlab/cleanup.rake4
-rw-r--r--lib/tasks/gitlab/site_statistics.rake15
-rw-r--r--lib/tasks/import.rake4
-rw-r--r--locale/gitlab.pot89
-rw-r--r--package.json13
-rw-r--r--qa/README.md9
-rw-r--r--qa/qa/runtime/browser.rb9
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--rubocop/cop/migration/add_reference.rb9
-rwxr-xr-xscripts/review_apps/review-apps.sh145
-rw-r--r--spec/controllers/admin/users_controller_spec.rb12
-rw-r--r--spec/controllers/application_controller_spec.rb151
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb5
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb10
-rw-r--r--spec/controllers/dashboard_controller_spec.rb31
-rw-r--r--spec/controllers/graphql_controller_spec.rb47
-rw-r--r--spec/controllers/groups_controller_spec.rb20
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb2
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb17
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb138
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb9
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb9
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb36
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb33
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb103
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb22
-rw-r--r--spec/controllers/projects_controller_spec.rb22
-rw-r--r--spec/controllers/users_controller_spec.rb8
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb1
-rw-r--r--spec/factories/import_state.rb7
-rw-r--r--spec/factories/pool_repositories.rb5
-rw-r--r--spec/factories/projects.rb9
-rw-r--r--spec/factories/shards.rb5
-rw-r--r--spec/factories/site_statistics.rb6
-rw-r--r--spec/features/admin/admin_users_spec.rb101
-rw-r--r--spec/features/explore/new_menu_spec.rb167
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb12
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb8
-rw-r--r--spec/features/issues_spec.rb12
-rw-r--r--spec/features/markdown/mermaid_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb15
-rw-r--r--spec/features/merge_request/user_sees_breadcrumb_links_spec.rb8
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb15
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb21
-rw-r--r--spec/features/milestones/user_promotes_milestone_spec.rb32
-rw-r--r--spec/features/milestones/user_sees_breadcrumb_links_spec.rb19
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb23
-rw-r--r--spec/features/projects/environments/environment_spec.rb10
-rw-r--r--spec/features/projects/jobs_spec.rb10
-rw-r--r--spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb17
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb16
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb8
-rw-r--r--spec/features/signed_commits_spec.rb94
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb40
-rw-r--r--spec/graphql/types/issue_type_spec.rb7
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb12
-rw-r--r--spec/graphql/types/project_type_spec.rb4
-rw-r--r--spec/helpers/tree_helper_spec.rb28
-rw-r--r--spec/initializers/attr_encrypted_no_db_connection_spec.rb30
-rw-r--r--spec/javascripts/api_spec.js12
-rw-r--r--spec/javascripts/blob_edit/blob_bundle_spec.js30
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js1
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js6
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js1
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js8
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js71
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js61
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js56
-rw-r--r--spec/javascripts/issuable_suggestions/components/app_spec.js96
-rw-r--r--spec/javascripts/issuable_suggestions/components/item_spec.js139
-rw-r--r--spec/javascripts/issuable_suggestions/mock_data.js26
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js12
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js18
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js34
-rw-r--r--spec/javascripts/notes/components/diff_with_note_spec.js3
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js37
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js60
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js26
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js47
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb12
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb18
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb94
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb85
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb63
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb25
-rw-r--r--spec/lib/gitlab/database_spec.rb22
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb8
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb13
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb28
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb29
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb120
-rw-r--r--spec/mailers/notify_spec.rb2
-rw-r--r--spec/migrations/backfill_store_project_full_path_in_repo_spec.rb98
-rw-r--r--spec/migrations/cleanup_environments_external_url_spec.rb28
-rw-r--r--spec/migrations/migrate_forbidden_redirect_uris_spec.rb48
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb12
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb122
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb47
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb4
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/pool_repository_spec.rb24
-rw-r--r--spec/models/project_import_state_spec.rb112
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb17
-rw-r--r--spec/models/project_spec.rb323
-rw-r--r--spec/models/remote_mirror_spec.rb8
-rw-r--r--spec/models/repository_spec.rb40
-rw-r--r--spec/models/site_statistic_spec.rb81
-rw-r--r--spec/models/user_spec.rb36
-rw-r--r--spec/policies/note_policy_spec.rb79
-rw-r--r--spec/requests/api/applications_spec.rb12
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb59
-rw-r--r--spec/requests/api/helpers_spec.rb13
-rw-r--r--spec/requests/api/merge_requests_spec.rb30
-rw-r--r--spec/requests/api/project_import_spec.rb10
-rw-r--r--spec/requests/api/runner_spec.rb24
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb12
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb51
-rw-r--r--spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb38
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb28
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb70
-rw-r--r--spec/services/files/multi_service_spec.rb39
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb2
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb92
-rw-r--r--spec/support/helpers/gpg_helpers.rb6
-rw-r--r--spec/support/helpers/prometheus_helpers.rb4
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb17
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb33
-rw-r--r--spec/tasks/gitlab/site_statistics_rake_spec.rb23
-rw-r--r--spec/validators/url_validator_spec.rb26
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb134
-rw-r--r--spec/workers/archive_trace_worker_spec.rb8
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb10
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb6
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb14
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb30
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb22
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb3
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb5
-rw-r--r--spec/workers/repository_import_worker_spec.rb29
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
-rw-r--r--yarn.lock525
544 files changed, 7390 insertions, 3137 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2c521b03863..79ec1b881d4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -439,6 +439,50 @@ setup-test-env:
- config/secrets.yml
- vendor/gitaly-ruby
+# GitLab Review apps
+.review-base: &review-base
+ <<: *dedicated-no-docs-no-db-pull-cache-job
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: test
+ cache: {}
+ dependencies: []
+ environment: &review-environment
+ name: review/${CI_COMMIT_REF_NAME}
+ url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ refs:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+ before_script: []
+
+.review-docker: &review-docker
+ <<: *review-base
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
+ services:
+ - docker:stable-dind
+ tags:
+ - gitlab-org
+ - docker
+ variables: &review-docker-variables
+ GIT_DEPTH: "1"
+ DOCKER_DRIVER: overlay2
+ DOCKER_HOST: tcp://docker:2375
+ LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
+ QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
+
+build-qa-image:
+ <<: *review-docker
+ stage: prepare
+ script:
+ - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
+ - echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
+ - time docker push ${QA_IMAGE}
+
danger-review:
<<: *pull-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
@@ -876,85 +920,99 @@ no_ee_check:
- //@gitlab-org/gitlab-ce
# GitLab Review apps
-review:
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: test
- allow_failure: true
- before_script:
- - gem install gitlab --no-document
+review-deploy:
+ <<: *review-base
variables:
GIT_DEPTH: "1"
- HOST_SUFFIX: "$CI_ENVIRONMENT_SLUG"
- DOMAIN: "-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN"
+ HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
+ DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master"
+ API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
+ environment:
+ <<: *review-environment
+ on_stop: review-stop
+ before_script:
+ - apk update && apk add jq
+ - gem install gitlab --no-document
script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- source ./scripts/review_apps/review-apps.sh
+ - wait_for_job_to_be_done "gitlab:assets:compile"
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
- check_kube_domain
- download_gitlab_chart
- ensure_namespace
- install_tiller
- install_external_dns
- - deploy
- environment:
- name: review/$CI_COMMIT_REF_NAME
- url: https://gitlab-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN
- on_stop: stop_review
- only:
- refs:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- kubernetes: active
- except:
- refs:
- - master
- - /(^docs[\/-].*|.*-docs$)/
+ - time deploy
+ - add_license
+
+.review-qa-base: &review-qa-base
+ <<: *review-docker
+ variables:
+ <<: *review-docker-variables
+ API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
+ QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
+ QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
+ GITLAB_USERNAME: "root"
+ GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITLAB_ADMIN_USERNAME: "root"
+ GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
+ EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
+ QA_DEBUG: "true"
+ artifacts:
+ paths:
+ - ./qa/gitlab-qa-run-*
+ expire_in: 7 days
+ when: always
+ before_script:
+ - echo "${QA_IMAGE}"
+ - echo "${CI_ENVIRONMENT_URL}"
+ - apk update && apk add curl jq
+ - source ./scripts/review_apps/review-apps.sh
+ - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
+ - wait_for_job_to_be_done "review-deploy"
+
+review-qa-smoke:
+ <<: *review-qa-base
+ retry: 2
+ script:
+ - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+
+review-qa-all:
+ <<: *review-qa-base
+ script:
+ - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+ when: manual
-stop_review:
+review-stop:
+ <<: *review-base
<<: *single-script-job
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: test
allow_failure: true
- cache: {}
- dependencies: []
variables:
+ <<: *single-script-job-variables
SCRIPT_NAME: "review_apps/review-apps.sh"
+ when: manual
+ environment:
+ <<: *review-environment
+ action: stop
script:
- source $(basename "${SCRIPT_NAME}")
- delete
- cleanup
- when: manual
- environment:
- name: review/$CI_COMMIT_REF_NAME
- action: stop
- only:
- refs:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- kubernetes: active
- except:
- - master
- - /(^docs[\/-].*|.*-docs$)/
-schedule:review_apps_cleanup:
- <<: *dedicated-no-docs-pull-cache-job
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+schedule:review-cleanup:
+ <<: *review-base
stage: build
allow_failure: true
- cache: {}
- dependencies: []
- before_script:
- - gem install gitlab --no-document
variables:
GIT_DEPTH: "1"
- script:
- - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
environment:
name: review/auto-cleanup
- action: stop
only:
refs:
- schedules@gitlab-org/gitlab-ce
@@ -963,3 +1021,7 @@ schedule:review_apps_cleanup:
except:
- tags
- /(^docs[\/-].*|.*-docs$)/
+ before_script:
+ - gem install gitlab --no-document
+ script:
+ - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c65b584e3c1..57e946befb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,29 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.5.1 (2018-11-26)
+
+### Security (17 changes)
+
+- Escape user fullname while rendering autocomplete template to prevent XSS.
+- Fix CRLF vulnerability in Project hooks.
+- Fix possible XSS attack in Markdown urls with spaces.
+- Redact sensitive information on gitlab-workhorse log.
+- Do not follow redirects in Prometheus service when making http requests to the configured api url.
+- Don't expose confidential information in commit message list.
+- Provide email notification when a user changes their email address.
+- Restrict Personal Access Tokens to API scope on web requests.
+- Resolve reflected XSS in Ouath authorize window.
+- Fix SSRF in project integrations.
+- Fixed ability to comment on locked/confidential issues.
+- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
+- Fix milestone promotion authorization check.
+- Configure mermaid to not render HTML content in diagrams.
+- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
+- Removed ability to see private group names when the group id is entered in the url.
+- Fix stored XSS for Environments.
+
+
## 11.5.0 (2018-11-22)
### Security (10 changes, 1 of them is from the community)
@@ -264,6 +287,36 @@ entry.
- Disables stop environment button while the deploy is in progress.
+## 11.4.8 (2018-11-27)
+
+### Security (24 changes)
+
+- Escape entity title while autocomplete template rendering to prevent XSS. !2571
+- Resolve reflected XSS in Ouath authorize window.
+- Fix XSS in merge request source branch name.
+- Escape user fullname while rendering autocomplete template to prevent XSS.
+- Fix CRLF vulnerability in Project hooks.
+- Fix possible XSS attack in Markdown urls with spaces.
+- Redact sensitive information on gitlab-workhorse log.
+- Do not follow redirects in Prometheus service when making http requests to the configured api url.
+- Persist only SHA digest of PersonalAccessToken#token.
+- Don't expose confidential information in commit message list.
+- Provide email notification when a user changes their email address.
+- Restrict Personal Access Tokens to API scope on web requests.
+- Redact personal tokens in unsubscribe links.
+- Fix SSRF in project integrations.
+- Fixed ability to comment on locked/confidential issues.
+- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
+- Fix milestone promotion authorization check.
+- Monkey kubeclient to not follow any redirects.
+- Configure mermaid to not render HTML content in diagrams.
+- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
+- Removed ability to see private group names when the group id is entered in the url.
+- Fix stored XSS for Environments.
+- Prevent SSRF attacks in HipChat integration.
+- Validate Wiki attachments are valid temporary files.
+
+
## 11.4.7 (2018-11-20)
- No changes.
@@ -544,6 +597,45 @@ entry.
- Check frozen string in style builds. (gfyoung)
+## 11.3.11 (2018-11-26)
+
+### Security (33 changes)
+
+- Filter user sensitive data from discussions JSON. !2537
+- Escape entity title while autocomplete template rendering to prevent XSS. !2557
+- Restrict Personal Access Tokens to API scope on web requests.
+- Fix XSS in merge request source branch name.
+- Escape user fullname while rendering autocomplete template to prevent XSS.
+- Fix CRLF vulnerability in Project hooks.
+- Fix possible XSS attack in Markdown urls with spaces.
+- Redact sensitive information on gitlab-workhorse log.
+- Set timeout for syntax highlighting.
+- Do not follow redirects in Prometheus service when making http requests to the configured api url.
+- Persist only SHA digest of PersonalAccessToken#token.
+- Sanitize JSON data properly to fix XSS on Issue details page.
+- Don't expose confidential information in commit message list.
+- Markdown API no longer displays confidential title references unless authorized.
+- Provide email notification when a user changes their email address.
+- Properly filter private references from system notes.
+- Redact personal tokens in unsubscribe links.
+- Resolve reflected XSS in Ouath authorize window.
+- Fix SSRF in project integrations.
+- Fix stored XSS in merge requests from imported repository.
+- Fixed ability to comment on locked/confidential issues.
+- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
+- Fix milestone promotion authorization check.
+- Monkey kubeclient to not follow any redirects.
+- Configure mermaid to not render HTML content in diagrams.
+- Redact confidential events in the API.
+- Fix xss vulnerability sourced from package.json.
+- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
+- Removed ability to see private group names when the group id is entered in the url.
+- Fix stored XSS for Environments.
+- Block loopback addresses in UrlBlocker.
+- Prevent SSRF attacks in HipChat integration.
+- Validate Wiki attachments are valid temporary files.
+
+
## 11.3.10 (2018-11-18)
### Security (1 change)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 3eefcb9dd5b..26aaba0e866 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.0.0
+1.2.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index f0bb29e7638..3a3cd8cc8b0 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.3.0
+1.3.1
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 6da4de57dc6..016dac34bf9 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.4.1
+8.4.3
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 21c8c7b46b8..b26a34e4705 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-7.1.1
+7.2.1
diff --git a/Gemfile b/Gemfile
index 3fa1989afb9..32e319f4d69 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,11 @@ gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
+
+# The 2.0.6 version of rack requires monkeypatch to be present in
+# `config.ru`. This can be removed once a new update for Rack
+# is available that contains https://github.com/rack/rack/pull/1201.
+gem_versions['rack'] = rails5? ? '2.0.6' : '1.6.11'
# --- The end of special code for migrating to Rails 5.0 ---
source 'https://rubygems.org'
@@ -154,6 +159,8 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0'
# Application server
+gem 'rack', gem_versions['rack']
+
group :unicorn do
gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.4'
@@ -235,7 +242,7 @@ gem 'asana', '~> 0.8.1'
gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration
-gem 'kubeclient', '~> 3.1.0'
+gem 'kubeclient', '~> 4.0.0'
# Sanitize user input
gem 'sanitize', '~> 4.6'
@@ -425,7 +432,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.123.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.2.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 20231696795..d7ef3b114d3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -273,7 +273,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.123.0)
+ gitaly-proto (1.2.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@@ -367,14 +367,14 @@ GEM
html2text (0.2.0)
nokogiri (~> 1.6)
htmlentities (4.3.4)
- http (2.2.2)
+ http (3.3.0)
addressable (~> 2.3)
http-cookie (~> 1.0)
- http-form_data (~> 1.0.1)
+ http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
- http-form_data (1.0.3)
+ http-form_data (2.1.1)
http_parser.rb (0.6.0)
httparty (0.13.7)
json (~> 1.8)
@@ -418,8 +418,8 @@ GEM
kgio (2.10.0)
knapsack (1.17.0)
rake
- kubeclient (3.1.0)
- http (~> 2.2.2)
+ kubeclient (4.0.0)
+ http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0)
launchy (2.4.3)
@@ -1006,7 +1006,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.123.0)
+ gitaly-proto (~> 1.2.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5)
@@ -1042,7 +1042,7 @@ DEPENDENCIES
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.17)
- kubeclient (~> 3.1.0)
+ kubeclient (~> 4.0.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 5.4)
licensee (~> 8.9)
@@ -1088,6 +1088,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
+ rack (= 2.0.6)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
diff --git a/Gemfile.rails4.lock b/Gemfile.rails4.lock
index caaa886987b..ea248dfdabe 100644
--- a/Gemfile.rails4.lock
+++ b/Gemfile.rails4.lock
@@ -272,7 +272,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.123.0)
+ gitaly-proto (1.2.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-markup (1.6.5)
@@ -364,14 +364,14 @@ GEM
html2text (0.2.0)
nokogiri (~> 1.6)
htmlentities (4.3.4)
- http (2.2.2)
+ http (3.3.0)
addressable (~> 2.3)
http-cookie (~> 1.0)
- http-form_data (~> 1.0.1)
+ http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
- http-form_data (1.0.3)
+ http-form_data (2.1.1)
http_parser.rb (0.6.0)
httparty (0.13.7)
json (~> 1.8)
@@ -415,8 +415,8 @@ GEM
kgio (2.10.0)
knapsack (1.17.0)
rake
- kubeclient (3.1.0)
- http (~> 2.2.2)
+ kubeclient (4.0.0)
+ http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0)
launchy (2.4.3)
@@ -998,7 +998,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.123.0)
+ gitaly-proto (~> 1.2.0)
github-markup (~> 1.7.0)
gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher
@@ -1033,7 +1033,7 @@ DEPENDENCIES
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.17)
- kubeclient (~> 3.1.0)
+ kubeclient (~> 4.0.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 5.4)
licensee (~> 8.9)
@@ -1079,6 +1079,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
+ rack (= 1.6.11)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 3f7a1ef1bfc..0da7ae1b229 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -10,10 +10,10 @@ const Api = {
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels',
- mergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
+ projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
+ projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
+ projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
mergeRequestsPath: '/api/:version/merge_requests',
- mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
- mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
@@ -99,36 +99,36 @@ const Api = {
},
// Return Merge Request for project
- mergeRequest(projectPath, mergeRequestId, params = {}) {
- const url = Api.buildUrl(Api.mergeRequestPath)
+ projectMergeRequest(projectPath, mergeRequestId, params = {}) {
+ const url = Api.buildUrl(Api.projectMergeRequestPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url, { params });
},
- mergeRequests(params = {}) {
- const url = Api.buildUrl(Api.mergeRequestsPath);
-
- return axios.get(url, { params });
- },
-
- mergeRequestChanges(projectPath, mergeRequestId) {
- const url = Api.buildUrl(Api.mergeRequestChangesPath)
+ projectMergeRequestChanges(projectPath, mergeRequestId) {
+ const url = Api.buildUrl(Api.projectMergeRequestChangesPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url);
},
- mergeRequestVersions(projectPath, mergeRequestId) {
- const url = Api.buildUrl(Api.mergeRequestVersionsPath)
+ projectMergeRequestVersions(projectPath, mergeRequestId) {
+ const url = Api.buildUrl(Api.projectMergeRequestVersionsPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':mrid', mergeRequestId);
return axios.get(url);
},
+ mergeRequests(params = {}) {
+ const url = Api.buildUrl(Api.mergeRequestsPath);
+
+ return axios.get(url, { params });
+ },
+
newLabel(namespacePath, projectPath, data, callback) {
let url;
diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
index a303e504cc7..55c68139ded 100644
--- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
+++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
@@ -1,11 +1,11 @@
import $ from 'jquery';
-import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean } from '~/lib/utils/common_utils';
import GfmAutoComplete from '~/gfm_auto_complete';
export default function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
- const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
+ const enableGFM = parseBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 720f30e18e6..35380ca49fb 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -26,6 +26,9 @@ export default function renderMermaid($els) {
},
// mermaidAPI options
theme: 'neutral',
+ flowchart: {
+ htmlLabels: false,
+ },
});
$els.each((i, el) => {
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
index f6bf62d734e..5b92608d536 100644
--- a/app/assets/javascripts/behaviors/secret_values.js
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -1,5 +1,5 @@
import { n__ } from '../locale';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { parseBoolean } from '../lib/utils/common_utils';
export default class SecretValues {
constructor({
@@ -16,7 +16,7 @@ export default class SecretValues {
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
if (this.revealButton) {
- const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
+ const isRevealed = parseBoolean(this.revealButton.dataset.secretRevealStatus);
this.updateDom(isRevealed);
this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
@@ -24,9 +24,7 @@ export default class SecretValues {
}
onRevealButtonClicked() {
- const previousIsRevealed = convertPermissionToBoolean(
- this.revealButton.dataset.secretRevealStatus,
- );
+ const previousIsRevealed = parseBoolean(this.revealButton.dataset.secretRevealStatus);
this.updateDom(!previousIsRevealed);
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 8b5a3c1c69d..eade1283513 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -4,6 +4,7 @@ import Mousetrap from 'mousetrap';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
+import { parseBoolean } from '~/lib/utils/common_utils';
const defaultStopCallback = Mousetrap.stopCallback;
Mousetrap.stopCallback = (e, element, combo) => {
@@ -61,7 +62,7 @@ export default class Shortcuts {
static onTogglePerfBar(e) {
e.preventDefault();
const performanceBarCookieName = 'perf_bar_enabled';
- if (Cookies.get(performanceBarCookieName) === 'true') {
+ if (parseBoolean(Cookies.get(performanceBarCookieName))) {
Cookies.set(performanceBarCookieName, 'false', { path: '/' });
} else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index ec27ae8c291..9f547471170 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -16,9 +16,17 @@ export default () => {
const filePath = editBlobForm.data('blobFilename');
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
+ const commitButton = $('.js-commit-button');
+
+ commitButton.on('click', () => {
+ window.onbeforeunload = null;
+ });
new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
new NewCommitForm(editBlobForm);
+
+ // returning here blocks page navigation
+ window.onbeforeunload = () => '';
}
if (uploadBlobForm.length) {
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 61a3072ac27..f88e9b55988 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -24,7 +24,7 @@ import BoardSidebar from './components/board_sidebar';
import initNewListDropdown from './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
-import { NavigationType } from '~/lib/utils/common_utils';
+import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
let issueBoardsApp;
@@ -60,7 +60,7 @@ export default () => {
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
listsEndpoint: $boardApp.dataset.listsEndpoint,
boardId: $boardApp.dataset.boardId,
- disabled: $boardApp.dataset.disabled === 'true',
+ disabled: parseBoolean($boardApp.dataset.disabled),
issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index eefe14a1d79..cf88a973d33 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -5,7 +5,7 @@ import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import Cookies from 'js-cookie';
-import { getUrlParamsArray } from '~/lib/utils/common_utils';
+import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
const boardsStore = {
disabled: false,
@@ -78,7 +78,7 @@ const boardsStore = {
});
},
welcomeIsHidden() {
- return Cookies.get('issue_board_welcome_hidden') === 'true';
+ return parseBoolean(Cookies.get('issue_board_welcome_hidden'));
},
removeList(id, type = 'blank') {
const list = this.findList('id', id, type);
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index 97a1645aa51..b2c88e8c14e 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -2,7 +2,7 @@
import $ from 'jquery';
import { visitUrl } from './lib/utils/url_utility';
-import { convertPermissionToBoolean } from './lib/utils/common_utils';
+import { parseBoolean } from './lib/utils/common_utils';
export default class BuildArtifacts {
constructor() {
@@ -22,7 +22,7 @@ export default class BuildArtifacts {
// eslint-disable-next-line class-methods-use-this
setupEntryClick() {
return $('.tree-holder').on('click', 'tr[data-link]', function() {
- visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
+ visitUrl(this.dataset.link, parseBoolean(this.dataset.externalLink));
});
}
// eslint-disable-next-line class-methods-use-this
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index dffabbfe1b8..592e1fd1c31 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -2,7 +2,7 @@ import _ from 'underscore';
import axios from '../lib/utils/axios_utils';
import { s__ } from '../locale';
import Flash from '../flash';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { parseBoolean } from '../lib/utils/common_utils';
import statusCodes from '../lib/utils/http_status';
import VariableList from './ci_variable_list';
@@ -101,7 +101,7 @@ export default class AjaxVariableList {
// If we submitted a row that was destroyed, remove it so we don't try
// to destroy it again which would cause a BE error
const destroyInput = row.querySelector('.js-ci-variable-input-destroy');
- if (convertPermissionToBoolean(destroyInput.value)) {
+ if (parseBoolean(destroyInput.value)) {
row.remove();
// Update the ID input so any future edits and `_destroy` will apply on the BE
} else {
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 7bdc18ce03e..ee0f7cda189 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { parseBoolean } from '../lib/utils/common_utils';
import { s__ } from '../locale';
import setupToggleButtons from '../toggle_buttons';
import CreateItemDropdown from '../create_item_dropdown';
@@ -150,7 +150,7 @@ export default class VariableList {
removeRow(row) {
const $row = $(row);
- const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
+ const isPersisted = parseBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 8354c28778c..a37cb4def28 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -168,6 +168,9 @@ export default {
knativeInstalled() {
return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
},
+ knativeExternalIp() {
+ return this.applications.knative.externalIp;
+ },
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
@@ -408,12 +411,11 @@ export default {
<div slot="description">
<p>
{{
- s__(`ClusterIntegration|A Knative build extends Kubernetes
- and utilizes existing Kubernetes primitives to provide you with
- the ability to run on-cluster container builds from source.
- For example, you can write a build that uses Kubernetes-native
- resources to obtain your source code from a repository,
- build it into container a image, and then run that image.`)
+ s__(`ClusterIntegration|Knative (pronounced kay-nay-tiv) extends
+ Kubernetes to provide a set of middleware components that are
+ essential to build modern, source-centric, and container-based
+ applications that can run anywhere: on premises, in the cloud, or
+ even in a third-party data center.`)
}}
</p>
@@ -444,6 +446,49 @@ export default {
/>
</div>
</template>
+ <template v-if="knativeInstalled">
+ <div class="form-group">
+ <label for="knative-ip-address">
+ {{ s__('ClusterIntegration|Knative IP Address:') }}
+ </label>
+ <div v-if="knativeExternalIp" class="input-group">
+ <input
+ id="knative-ip-address"
+ :value="knativeExternalIp"
+ type="text"
+ class="form-control js-ip-address"
+ readonly
+ />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="knativeExternalIp"
+ :title="s__('ClusterIntegration|Copy Knative IP Address to clipboard')"
+ class="input-group-text js-clipboard-btn"
+ />
+ </span>
+ </div>
+ <input v-else type="text" class="form-control js-ip-address" readonly value="?" />
+ </div>
+
+ <p v-if="!knativeExternalIp" class="settings-message js-no-ip-message">
+ {{
+ s__(`ClusterIntegration|The IP address is in
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
+ }}
+ </p>
+
+ <p>
+ {{
+ s__(`ClusterIntegration|Point a wildcard DNS to this
+ generated IP address in order to access
+ your application after it has been deployed.`)
+ }}
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
+ </p>
+ </template>
</div>
</application-row>
</div>
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 3678be59d24..2d69da8eaec 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -60,6 +60,7 @@ export default class ClusterStore {
requestStatus: null,
requestReason: null,
hostname: null,
+ externalIp: null,
},
},
};
@@ -111,6 +112,8 @@ export default class ClusterStore {
} else if (appId === KNATIVE) {
this.state.applications.knative.hostname =
serverAppEntry.hostname || this.state.applications.knative.hostname;
+ this.state.applications.knative.externalIp =
+ serverAppEntry.external_ip || this.state.applications.knative.externalIp;
}
});
}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index dff0adba25a..10f02739ec8 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Cookies from 'js-cookie';
import _ from 'underscore';
import bp from './breakpoints';
+import { parseBoolean } from '~/lib/utils/common_utils';
export default class ContextualSidebar {
constructor() {
@@ -78,7 +79,7 @@ export default class ContextualSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') {
- const collapse = Cookies.get('sidebar_collapsed') === 'true';
+ const collapse = parseBoolean(Cookies.get('sidebar_collapsed'));
this.toggleCollapsedSidebar(collapse);
}
}
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index 192032202cd..c0613d80d37 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -106,7 +106,7 @@ export default {
methods: {
...mapActions('diffs', ['loadMoreLines', 'showCommentForm', 'setHighlightedRow']),
handleCommentButton() {
- this.showCommentForm({ lineCode: this.line.line_code });
+ this.showCommentForm({ lineCode: this.line.line_code, fileHash: this.fileHash });
},
handleLoadMoreLines() {
if (this.isRequesting) {
@@ -167,7 +167,7 @@ export default {
>
<template v-else>
<button
- v-if="shouldShowCommentButton"
+ v-show="shouldShowCommentButton"
type="button"
class="add-diff-note js-add-diff-note-button qa-diff-comment"
title="Add a comment to this line"
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index c7cef74fe40..9fd02acbd6e 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -73,6 +73,7 @@ export default {
this.cancelCommentForm({
lineCode: this.line.line_code,
+ fileHash: this.diffFileHash,
});
this.$nextTick(() => {
this.resetAutoSave();
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index 91b87fb042c..aa40b24950a 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -1,5 +1,4 @@
<script>
-import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
@@ -17,29 +16,31 @@ export default {
type: String,
required: true,
},
- lineIndex: {
- type: Number,
- required: true,
- },
},
computed: {
- ...mapState({
- diffLineCommentForms: state => state.diffs.diffLineCommentForms,
- }),
className() {
return this.line.discussions.length ? '' : 'js-temp-notes-holder';
},
+ shouldRender() {
+ if (this.line.hasForm) return true;
+
+ if (!this.line.discussions || !this.line.discussions.length) {
+ return false;
+ }
+
+ return this.line.discussions.every(discussion => discussion.expanded);
+ },
},
};
</script>
<template>
- <tr :class="className" class="notes_holder">
+ <tr v-if="shouldRender" :class="className" class="notes_holder">
<td class="notes_content" colspan="3">
<div class="content">
<diff-discussions v-if="line.discussions.length" :discussions="line.discussions" />
<diff-line-note-form
- v-if="diffLineCommentForms[line.line_code]"
+ v-if="line.hasForm"
:diff-file-hash="diffFileHash"
:line="line"
:note-target-line="line"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index fafc1649ce7..6a0ce760e6d 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -1,5 +1,5 @@
<script>
-import { mapGetters, mapState } from 'vuex';
+import { mapGetters } from 'vuex';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
@@ -19,23 +19,18 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', ['commitId', 'shouldRenderInlineCommentRow']),
- ...mapState({
- diffLineCommentForms: state => state.diffs.diffLineCommentForms,
- }),
+ ...mapGetters('diffs', ['commitId']),
diffLinesLength() {
return this.diffLines.length;
},
- userColorScheme() {
- return window.gon.user_color_scheme;
- },
},
+ userColorScheme: window.gon.user_color_scheme,
};
</script>
<template>
<table
- :class="userColorScheme"
+ :class="$options.userColorScheme"
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view"
>
@@ -49,11 +44,9 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
/>
<inline-diff-comment-row
- v-if="shouldRenderInlineCommentRow(line)"
- :key="index"
+ :key="`icr-${index}`"
:diff-file-hash="diffFile.file_hash"
:line="line"
- :line-index="index"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index c6b50983277..b98463d3dd3 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -1,5 +1,4 @@
<script>
-import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
@@ -23,22 +22,13 @@ export default {
},
},
computed: {
- ...mapState({
- diffLineCommentForms: state => state.diffs.diffLineCommentForms,
- }),
- leftLineCode() {
- return this.line.left && this.line.left.line_code;
- },
- rightLineCode() {
- return this.line.right && this.line.right.line_code;
- },
hasExpandedDiscussionOnLeft() {
- return this.line.left && this.line.left.discussions
+ return this.line.left && this.line.left.discussions.length
? this.line.left.discussions.every(discussion => discussion.expanded)
: false;
},
hasExpandedDiscussionOnRight() {
- return this.line.right && this.line.right.discussions
+ return this.line.right && this.line.right.discussions.length
? this.line.right.discussions.every(discussion => discussion.expanded)
: false;
},
@@ -57,9 +47,10 @@ export default {
);
},
showRightSideCommentForm() {
- return (
- this.line.right && this.line.right.type && this.diffLineCommentForms[this.rightLineCode]
- );
+ return this.line.right && this.line.right.type && this.line.right.hasForm;
+ },
+ showLeftSideCommentForm() {
+ return this.line.left && this.line.left.hasForm;
},
className() {
return (this.left && this.line.left.discussions.length > 0) ||
@@ -67,12 +58,30 @@ export default {
? ''
: 'js-temp-notes-holder';
},
+ shouldRender() {
+ const { line } = this;
+ const hasDiscussion =
+ (line.left && line.left.discussions && line.left.discussions.length) ||
+ (line.right && line.right.discussions && line.right.discussions.length);
+
+ if (
+ hasDiscussion &&
+ (this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight)
+ ) {
+ return true;
+ }
+
+ const hasCommentFormOnLeft = line.left && line.left.hasForm;
+ const hasCommentFormOnRight = line.right && line.right.hasForm;
+
+ return hasCommentFormOnLeft || hasCommentFormOnRight;
+ },
},
};
</script>
<template>
- <tr :class="className" class="notes_holder">
+ <tr v-if="shouldRender" :class="className" class="notes_holder">
<td class="notes_content parallel old" colspan="2">
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
<diff-discussions
@@ -81,7 +90,7 @@ export default {
/>
</div>
<diff-line-note-form
- v-if="diffLineCommentForms[leftLineCode]"
+ v-if="showLeftSideCommentForm"
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 771b8a80352..9a6e0e82529 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState, mapGetters } from 'vuex';
+import { mapGetters } from 'vuex';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
@@ -19,23 +19,18 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', ['commitId', 'shouldRenderParallelCommentRow']),
- ...mapState({
- diffLineCommentForms: state => state.diffs.diffLineCommentForms,
- }),
+ ...mapGetters('diffs', ['commitId']),
diffLinesLength() {
return this.diffLines.length;
},
- userColorScheme() {
- return window.gon.user_color_scheme;
- },
},
+ userColorScheme: window.gon.user_color_scheme,
};
</script>
<template>
<div
- :class="userColorScheme"
+ :class="$options.userColorScheme"
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file"
>
@@ -50,7 +45,6 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
/>
<parallel-diff-comment-row
- v-if="shouldRenderParallelCommentRow(line)"
:key="`dcr-${index}`"
:line="line"
:diff-file-hash="diffFile.file_hash"
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index ec4a4aa1d6d..f40a7b25fde 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,7 +1,7 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
-import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
@@ -18,8 +18,7 @@ export default {
},
data() {
const treeListStored = localStorage.getItem(treeListStorageKey);
- const renderTreeList =
- treeListStored !== null ? convertPermissionToBoolean(treeListStored) : true;
+ const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
return {
search: '',
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 9fe0f37035b..8b477c678fd 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -103,12 +103,12 @@ export const setParallelDiffViewType = ({ commit }) => {
historyPushState(url);
};
-export const showCommentForm = ({ commit }, params) => {
- commit(types.ADD_COMMENT_FORM_LINE, params);
+export const showCommentForm = ({ commit }, { lineCode, fileHash }) => {
+ commit(types.TOGGLE_LINE_HAS_FORM, { lineCode, fileHash, hasForm: true });
};
-export const cancelCommentForm = ({ commit }, params) => {
- commit(types.REMOVE_COMMENT_FORM_LINE, params);
+export const cancelCommentForm = ({ commit }, { lineCode, fileHash }) => {
+ commit(types.TOGGLE_LINE_HAS_FORM, { lineCode, fileHash, hasForm: false });
};
export const loadMoreLines = ({ commit }, options) => {
@@ -195,8 +195,8 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
return dispatch('saveNote', postData, { root: true })
.then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
.then(discussion => dispatch('assignDiscussionsToDiff', [discussion]))
+ .then(() => dispatch('updateResolvableDiscussonsCounts', null, { root: true }))
.then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.file_hash))
- .then(() => dispatch('startTaskList', null, { root: true }))
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 6a87b712b48..fdf1efbb10e 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -70,40 +70,6 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
) || [];
-export const shouldRenderParallelCommentRow = state => line => {
- const hasDiscussion =
- (line.left && line.left.discussions && line.left.discussions.length) ||
- (line.right && line.right.discussions && line.right.discussions.length);
-
- const hasExpandedDiscussionOnLeft =
- line.left && line.left.discussions && line.left.discussions.length
- ? line.left.discussions.every(discussion => discussion.expanded)
- : false;
- const hasExpandedDiscussionOnRight =
- line.right && line.right.discussions && line.right.discussions.length
- ? line.right.discussions.every(discussion => discussion.expanded)
- : false;
-
- if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
- return true;
- }
-
- const hasCommentFormOnLeft = line.left && state.diffLineCommentForms[line.left.line_code];
- const hasCommentFormOnRight = line.right && state.diffLineCommentForms[line.right.line_code];
-
- return hasCommentFormOnLeft || hasCommentFormOnRight;
-};
-
-export const shouldRenderInlineCommentRow = state => line => {
- if (state.diffLineCommentForms[line.line_code]) return true;
-
- if (!line.discussions || line.discussions.length === 0) {
- return false;
- }
-
- return line.discussions.every(discussion => discussion.expanded);
-};
-
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.file_hash === fileHash);
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index e59ef8d2815..98e57d52d77 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,6 +1,7 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import bp from '~/breakpoints';
+import { parseBoolean } from '~/lib/utils/common_utils';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
@@ -17,12 +18,11 @@ export default () => ({
diffFiles: [],
mergeRequestDiffs: [],
mergeRequestDiff: null,
- diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
showTreeList:
- storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true',
+ storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : parseBoolean(storedTreeShow),
currentDiffFileId: '',
projectPath: '',
commentForms: [],
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 60e9cf6b844..0338cde3658 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -3,8 +3,7 @@ export const SET_LOADING = 'SET_LOADING';
export const SET_DIFF_DATA = 'SET_DIFF_DATA';
export const SET_DIFF_VIEW_TYPE = 'SET_DIFF_VIEW_TYPE';
export const SET_MERGE_REQUEST_DIFFS = 'SET_MERGE_REQUEST_DIFFS';
-export const ADD_COMMENT_FORM_LINE = 'ADD_COMMENT_FORM_LINE';
-export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE';
+export const TOGGLE_LINE_HAS_FORM = 'TOGGLE_LINE_HAS_FORM';
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index ec9db8507d1..f0895661bf2 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,4 +1,3 @@
-import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { sortTree } from '~/ide/stores/utils';
import {
@@ -49,12 +48,30 @@ export default {
Object.assign(state, { diffViewType });
},
- [types.ADD_COMMENT_FORM_LINE](state, { lineCode }) {
- Vue.set(state.diffLineCommentForms, lineCode, true);
- },
+ [types.TOGGLE_LINE_HAS_FORM](state, { lineCode, fileHash, hasForm }) {
+ const diffFile = state.diffFiles.find(f => f.file_hash === fileHash);
+
+ if (!diffFile) return;
+
+ if (diffFile.highlighted_diff_lines) {
+ diffFile.highlighted_diff_lines.find(l => l.line_code === lineCode).hasForm = hasForm;
+ }
+
+ if (diffFile.parallel_diff_lines) {
+ const line = diffFile.parallel_diff_lines.find(l => {
+ const { left, right } = l;
- [types.REMOVE_COMMENT_FORM_LINE](state, { lineCode }) {
- Vue.delete(state.diffLineCommentForms, lineCode);
+ return (left && left.line_code === lineCode) || (right && right.line_code === lineCode);
+ });
+
+ if (line.left && line.left.line_code === lineCode) {
+ line.left.hasForm = hasForm;
+ }
+
+ if (line.right && line.right.line_code === lineCode) {
+ line.right.hasForm = hasForm;
+ }
+ }
},
[types.ADD_CONTEXT_LINES](state, options) {
@@ -68,6 +85,7 @@ export default {
...line,
line_code: line.line_code || `${fileHash}_${line.old_line}_${line.new_line}`,
discussions: line.discussions || [],
+ hasForm: false,
}));
addContextLines({
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index d9d3c0f2ca2..54b9ee4d2d6 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -209,9 +209,11 @@ export function prepareDiffData(diffData) {
const line = file.parallel_diff_lines[u];
if (line.left) {
line.left = trimFirstCharOfLineContent(line.left);
+ line.left.hasForm = false;
}
if (line.right) {
line.right = trimFirstCharOfLineContent(line.right);
+ line.right.hasForm = false;
}
}
}
@@ -220,7 +222,7 @@ export function prepareDiffData(diffData) {
const linesLength = file.highlighted_diff_lines.length;
for (let u = 0; u < linesLength; u += 1) {
const line = file.highlighted_diff_lines[u];
- Object.assign(line, { ...trimFirstCharOfLineContent(line) });
+ Object.assign(line, { ...trimFirstCharOfLineContent(line), hasForm: false });
}
showingLines += file.parallel_diff_lines.length;
}
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index f044d31c776..3cf6e4ad14d 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import environmentsFolderApp from './environments_folder_view.vue';
-import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
+import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
Vue.use(Translate);
@@ -18,8 +18,8 @@ export default () =>
endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass,
- canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
+ canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment),
+ canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index 5b6833fb15d..d366e7550b7 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import environmentsComponent from './components/environments_app.vue';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
@@ -19,9 +19,9 @@ export default () =>
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
- canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
- canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
+ canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment),
+ canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment),
+ canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index 0f68f05b523..928f1fe409f 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import Translate from '../vue_shared/translate';
import GroupFilterableList from './groups_filterable_list';
import GroupsStore from './store/groups_store';
@@ -38,7 +39,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => {
},
data() {
const { dataset } = dataEl || this.$options.el;
- const hideProjects = dataset.hideProjects === 'true';
+ const hideProjects = parseBoolean(dataset.hideProjects);
const service = new GroupsService(endpoint || dataset.endpoint);
const store = new GroupsStore(hideProjects);
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 175d0b8498b..2fa7a219ea8 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -4,6 +4,7 @@ import Translate from '~/vue_shared/translate';
import { highCountTrim } from '~/lib/utils/text_utility';
import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue';
import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
/**
* Updates todo counter when todos are toggled.
@@ -36,7 +37,7 @@ document.addEventListener('DOMContentLoaded', () => {
const { hasStatus } = this.$options.el.dataset;
return {
- hasStatus: hasStatus === 'true',
+ hasStatus: parseBoolean(hasStatus),
};
},
render(createElement) {
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 7a5a227db30..fbf944499d5 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -4,7 +4,7 @@ import Translate from '~/vue_shared/translate';
import ide from './components/ide.vue';
import store from './stores';
import router from './ide_router';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { parseBoolean } from '../lib/utils/common_utils';
Vue.use(Translate);
@@ -40,7 +40,7 @@ export function initIde(el, options = {}) {
webIDEHelpPagePath: el.dataset.webIdeHelpPagePath,
});
this.setInitialData({
- clientsidePreviewEnabled: convertPermissionToBoolean(el.dataset.clientsidePreviewEnabled),
+ clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
...extraInitialData(el),
});
},
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index f0193d8e8ea..13449592e62 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -41,13 +41,13 @@ export default {
return Api.project(`${namespace}/${project}`);
},
getProjectMergeRequestData(projectId, mergeRequestId, params = {}) {
- return Api.mergeRequest(projectId, mergeRequestId, params);
+ return Api.projectMergeRequest(projectId, mergeRequestId, params);
},
getProjectMergeRequestChanges(projectId, mergeRequestId) {
- return Api.mergeRequestChanges(projectId, mergeRequestId);
+ return Api.projectMergeRequestChanges(projectId, mergeRequestId);
},
getProjectMergeRequestVersions(projectId, mergeRequestId) {
- return Api.mergeRequestVersions(projectId, mergeRequestId);
+ return Api.projectMergeRequestVersions(projectId, mergeRequestId);
},
getBranchData(projectId, currentBranchId) {
return Api.branchSingle(projectId, currentBranchId);
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
index 4565c11a83f..8b5f7558654 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
@@ -23,13 +23,19 @@ export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }
export const receiveMergeRequestsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_MERGE_REQUESTS_SUCCESS, data);
-export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, search = '' }) => {
+export const fetchMergeRequests = (
+ { dispatch, state: { state }, rootState: { currentProjectId } },
+ { type, search = '' },
+) => {
dispatch('requestMergeRequests');
dispatch('resetMergeRequests');
- const scope = type ? scopes[type] : 'all';
+ const scope = type && scopes[type];
+ const request = scope
+ ? Api.mergeRequests({ scope, state, search })
+ : Api.projectMergeRequest(currentProjectId, '', { state, search });
- return Api.mergeRequests({ scope, state, search })
+ return request
.then(({ data }) => dispatch('receiveMergeRequestsSuccess', data))
.catch(() => dispatch('receiveMergeRequestsError', { type, search }));
};
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index f1beb1a8ea5..1ffd5c61282 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -3,7 +3,7 @@ import _ from 'underscore';
import { __, sprintf } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
-import { convertPermissionToBoolean } from './lib/utils/common_utils';
+import { parseBoolean } from './lib/utils/common_utils';
class ImporterStatus {
constructor({ jobsUrl, importUrl, ciCdOnly }) {
@@ -141,7 +141,7 @@ function initImporterStatus() {
return new ImporterStatus({
jobsUrl: data.jobsImportPath,
importUrl: data.importPath,
- ciCdOnly: convertPermissionToBoolean(data.ciCdOnly),
+ ciCdOnly: parseBoolean(data.ciCdOnly),
});
}
}
diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue
new file mode 100644
index 00000000000..eea0701312b
--- /dev/null
+++ b/app/assets/javascripts/issuable_suggestions/components/app.vue
@@ -0,0 +1,96 @@
+<script>
+import _ from 'underscore';
+import { GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import Suggestion from './item.vue';
+import query from '../queries/issues.graphql';
+
+export default {
+ components: {
+ Suggestion,
+ Icon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ search: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ issues: {
+ query,
+ debounce: 250,
+ skip() {
+ return this.isSearchEmpty;
+ },
+ update: data => data.project.issues.edges.map(({ node }) => node),
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ search: this.search,
+ };
+ },
+ },
+ },
+ data() {
+ return {
+ issues: [],
+ loading: 0,
+ };
+ },
+ computed: {
+ isSearchEmpty() {
+ return _.isEmpty(this.search);
+ },
+ showSuggestions() {
+ return !this.isSearchEmpty && this.issues.length && !this.loading;
+ },
+ },
+ watch: {
+ search() {
+ if (this.isSearchEmpty) {
+ this.issues = [];
+ }
+ },
+ },
+ helpText: __(
+ 'These existing issues have a similar title. It might be better to comment there instead of creating another similar issue.',
+ ),
+};
+</script>
+
+<template>
+ <div v-show="showSuggestions" class="form-group row issuable-suggestions">
+ <div v-once class="col-form-label col-sm-2 pt-0">
+ {{ __('Similar issues') }}
+ <icon
+ v-gl-tooltip.bottom
+ :title="$options.helpText"
+ :aria-label="$options.helpText"
+ name="question-o"
+ class="text-secondary suggestion-help-hover"
+ />
+ </div>
+ <div class="col-sm-10">
+ <ul class="list-unstyled m-0">
+ <li
+ v-for="(suggestion, index) in issues"
+ :key="suggestion.id"
+ :class="{
+ 'append-bottom-default': index !== issues.length - 1,
+ }"
+ >
+ <suggestion :suggestion="suggestion" />
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue
new file mode 100644
index 00000000000..9a16b486bf5
--- /dev/null
+++ b/app/assets/javascripts/issuable_suggestions/components/item.vue
@@ -0,0 +1,137 @@
+<script>
+import _ from 'underscore';
+import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import timeago from '~/vue_shared/mixins/timeago';
+
+export default {
+ components: {
+ GlTooltip,
+ GlLink,
+ Icon,
+ UserAvatarImage,
+ TimeagoTooltip,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [timeago],
+ props: {
+ suggestion: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isOpen() {
+ return this.suggestion.state === 'opened';
+ },
+ isClosed() {
+ return this.suggestion.state === 'closed';
+ },
+ counts() {
+ return [
+ {
+ id: _.uniqueId(),
+ icon: 'thumb-up',
+ tooltipTitle: __('Upvotes'),
+ count: this.suggestion.upvotes,
+ },
+ {
+ id: _.uniqueId(),
+ icon: 'comment',
+ tooltipTitle: __('Comments'),
+ count: this.suggestion.userNotesCount,
+ },
+ ].filter(({ count }) => count);
+ },
+ stateIcon() {
+ return this.isClosed ? 'issue-close' : 'issue-open-m';
+ },
+ stateTitle() {
+ return this.isClosed ? __('Closed') : __('Opened');
+ },
+ closedOrCreatedDate() {
+ return this.suggestion.closedAt || this.suggestion.createdAt;
+ },
+ hasUpdated() {
+ return this.suggestion.updatedAt !== this.suggestion.createdAt;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="suggestion-item">
+ <div class="d-flex align-items-center">
+ <icon
+ v-if="suggestion.confidential"
+ v-gl-tooltip.bottom
+ :title="__('Confidential')"
+ name="eye-slash"
+ class="suggestion-help-hover mr-1 suggestion-confidential"
+ />
+ <gl-link :href="suggestion.webUrl" target="_blank" class="suggestion bold str-truncated-100">
+ {{ suggestion.title }}
+ </gl-link>
+ </div>
+ <div class="text-secondary suggestion-footer">
+ <icon
+ ref="state"
+ :name="stateIcon"
+ :class="{
+ 'suggestion-state-open': isOpen,
+ 'suggestion-state-closed': isClosed,
+ }"
+ class="suggestion-help-hover"
+ />
+ <gl-tooltip :target="() => $refs.state" placement="bottom">
+ <span class="d-block">
+ <span class="bold"> {{ stateTitle }} </span> {{ timeFormated(closedOrCreatedDate) }}
+ </span>
+ <span class="text-tertiary">{{ tooltipTitle(closedOrCreatedDate) }}</span>
+ </gl-tooltip>
+ #{{ suggestion.iid }} &bull;
+ <timeago-tooltip
+ :time="suggestion.createdAt"
+ tooltip-placement="bottom"
+ class="suggestion-help-hover"
+ />
+ by
+ <gl-link :href="suggestion.author.webUrl">
+ <user-avatar-image
+ :img-src="suggestion.author.avatarUrl"
+ :size="16"
+ css-classes="mr-0 float-none"
+ tooltip-placement="bottom"
+ class="d-inline-block"
+ >
+ <span class="bold d-block">{{ __('Author') }}</span> {{ suggestion.author.name }}
+ <span class="text-tertiary">@{{ suggestion.author.username }}</span>
+ </user-avatar-image>
+ </gl-link>
+ <template v-if="hasUpdated">
+ &bull; {{ __('updated') }}
+ <timeago-tooltip
+ :time="suggestion.updatedAt"
+ tooltip-placement="bottom"
+ class="suggestion-help-hover"
+ />
+ </template>
+ <span class="suggestion-counts">
+ <span
+ v-for="{ count, icon, tooltipTitle, id } in counts"
+ :key="id"
+ v-gl-tooltip.bottom
+ :title="tooltipTitle"
+ class="suggestion-help-hover prepend-left-8 text-tertiary"
+ >
+ <icon :name="icon" /> {{ count }}
+ </span>
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issuable_suggestions/index.js b/app/assets/javascripts/issuable_suggestions/index.js
new file mode 100644
index 00000000000..2c80cf1797a
--- /dev/null
+++ b/app/assets/javascripts/issuable_suggestions/index.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import defaultClient from '~/lib/graphql';
+import App from './components/app.vue';
+
+Vue.use(VueApollo);
+
+export default function() {
+ const el = document.getElementById('js-suggestions');
+ const issueTitle = document.getElementById('issue_title');
+ const { projectPath } = el.dataset;
+ const apolloProvider = new VueApollo({
+ defaultClient,
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ data() {
+ return {
+ search: issueTitle.value,
+ };
+ },
+ mounted() {
+ issueTitle.addEventListener('input', () => {
+ this.search = issueTitle.value;
+ });
+ },
+ render(h) {
+ return h(App, {
+ props: {
+ projectPath,
+ search: this.search,
+ },
+ });
+ },
+ });
+}
diff --git a/app/assets/javascripts/issuable_suggestions/queries/issues.graphql b/app/assets/javascripts/issuable_suggestions/queries/issues.graphql
new file mode 100644
index 00000000000..2384b381344
--- /dev/null
+++ b/app/assets/javascripts/issuable_suggestions/queries/issues.graphql
@@ -0,0 +1,26 @@
+query issueSuggestion($fullPath: ID!, $search: String) {
+ project(fullPath: $fullPath) {
+ issues(search: $search, sort: updated_desc, first: 5) {
+ edges {
+ node {
+ iid
+ title
+ confidential
+ userNotesCount
+ upvotes
+ webUrl
+ state
+ closedAt
+ createdAt
+ updatedAt
+ author {
+ name
+ username
+ avatarUrl
+ webUrl
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index 2d09cf5760f..f7fbb9503a0 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -128,7 +128,7 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default js-environment-container">
+ <div class="prepend-top-default append-bottom-default js-environment-container">
<div class="environment-information">
<ci-icon :status="iconStatus" />
<p class="inline append-bottom-0" v-html="environment"></p>
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
index 7b077d5e621..ec52d272168 100644
--- a/app/assets/javascripts/jobs/components/stuck_block.vue
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -28,20 +28,22 @@ export default {
<div class="bs-callout bs-callout-warning">
<p v-if="tags.length" class="js-stuck-with-tags append-bottom-0">
{{
- s__(`This job is stuck, because you don't have
+ s__(`This job is stuck because you don't have
any active runners online with any of these tags assigned to them:`)
}}
- <span v-for="(tag, index) in tags" :key="index" class="badge badge-primary"> {{ tag }} </span>
+ <span v-for="(tag, index) in tags" :key="index" class="badge badge-primary append-right-4">
+ {{ tag }}
+ </span>
</p>
<p v-else-if="hasNoRunnersForProject" class="js-stuck-no-runners append-bottom-0">
{{
- s__(`Job|This job is stuck, because the project
+ s__(`Job|This job is stuck because the project
doesn't have any runners online assigned to it.`)
}}
</p>
<p v-else class="js-stuck-no-active-runner append-bottom-0">
{{
- s__(`This job is stuck, because you don't
+ s__(`This job is stuck because you don't
have any active runners that can run this job.`)
}}
</p>
diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js
index 8c0950ad5d5..bfb4d9ce67b 100644
--- a/app/assets/javascripts/landing.js
+++ b/app/assets/javascripts/landing.js
@@ -1,4 +1,5 @@
import Cookies from 'js-cookie';
+import { parseBoolean } from '~/lib/utils/common_utils';
class Landing {
constructor(landingElement, dismissButton, cookieName) {
@@ -30,7 +31,7 @@ class Landing {
}
isDismissed() {
- return Cookies.get(this.cookieName) === 'true';
+ return parseBoolean(Cookies.get(this.cookieName));
}
}
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
new file mode 100644
index 00000000000..20a0f142d9e
--- /dev/null
+++ b/app/assets/javascripts/lib/graphql.js
@@ -0,0 +1,9 @@
+import ApolloClient from 'apollo-boost';
+import csrf from '~/lib/utils/csrf';
+
+export default new ApolloClient({
+ uri: `${gon.relative_url_root}/api/graphql`,
+ headers: {
+ [csrf.headerKey]: csrf.token,
+ },
+});
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 3186ae9c133..040d0bc659e 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -421,12 +421,28 @@ export const historyPushState = newUrl => {
};
/**
+ * Returns true for a String "true" and false otherwise.
+ * This is the opposite of Boolean(...).toString()
+ *
+ * @param {String} value
+ * @returns {Boolean}
+ */
+export const parseBoolean = value => value === 'true';
+
+/**
* Converts permission provided as strings to booleans.
*
* @param {String} string
* @returns {Boolean}
*/
-export const convertPermissionToBoolean = permission => permission === 'true';
+export const convertPermissionToBoolean = permission => {
+ if (process.env.NODE_ENV !== 'production') {
+ // eslint-disable-next-line no-console
+ console.warn('convertPermissionToBoolean is deprecated! Please use parseBoolean instead.');
+ }
+
+ return parseBoolean(permission);
+};
/**
* Back Off exponential algorithm
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index d8255181574..b0dc5697018 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -8,7 +8,12 @@ import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
-import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils';
+import {
+ parseUrlPathname,
+ handleLocationHash,
+ isMetaClick,
+ parseBoolean,
+} from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Diff from './diff';
@@ -212,6 +217,28 @@ export default class MergeRequestTabs {
}
this.eventHub.$emit('MergeRequestTabChange', this.getCurrentAction());
+ } else if (action === this.currentAction) {
+ // ContentTop is used to handle anything at the top of the page before the main content
+ const mainContentContainer = document.querySelector('.content-wrapper');
+ const tabContentContainer = document.querySelector('.tab-content');
+
+ if (mainContentContainer && tabContentContainer) {
+ const mainContentTop = mainContentContainer.getBoundingClientRect().top;
+ const tabContentTop = tabContentContainer.getBoundingClientRect().top;
+
+ // 51px is the height of the navbar buttons, e.g. `Discussion | Commits | Changes`
+ const scrollDestination = tabContentTop - mainContentTop - 51;
+
+ // scrollBehavior is only available in browsers that support scrollToOptions
+ if ('scrollBehavior' in document.documentElement.style) {
+ window.scrollTo({
+ top: scrollDestination,
+ behavior: 'smooth',
+ });
+ } else {
+ window.scrollTo(0, scrollDestination);
+ }
+ }
}
}
@@ -440,7 +467,7 @@ export default class MergeRequestTabs {
// Expand the issuable sidebar unless the user explicitly collapsed it
expandView() {
- if (Cookies.get('collapsed_gutter') === 'true') {
+ if (parseBoolean(Cookies.get('collapsed_gutter'))) {
return;
}
const $gutterIcon = $('.js-sidebar-toggle i:visible');
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index 23e1e734b37..0e141d02ead 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -44,9 +44,9 @@ export default {
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
- buttonText: 'Install Prometheus on clusters',
+ buttonText: 'Install on clusters',
buttonPath: this.clustersPath,
- secondaryButtonText: 'Configure existing Prometheus',
+ secondaryButtonText: 'Configure existing installation',
secondaryButtonPath: this.settingsPath,
},
loading: {
@@ -88,26 +88,32 @@ export default {
</script>
<template>
- <div class="prometheus-state">
- <div class="state-svg svg-content"><img :src="currentState.svgUrl" /></div>
- <h4 class="state-title">{{ currentState.title }}</h4>
- <p class="state-description">
- {{ currentState.description }}
- <a v-if="showButtonDescription" :href="settingsPath"> Prometheus server </a>
- </p>
- <div class="state-button">
- <a v-if="currentState.buttonPath" :href="currentState.buttonPath" class="btn btn-success">
- {{ currentState.buttonText }}
- </a>
+ <div class="row empty-state js-empty-state">
+ <div class="col-12">
+ <div class="state-svg svg-content"><img :src="currentState.svgUrl" /></div>
</div>
- <div class="state-button">
- <a
- v-if="currentState.secondaryButtonPath"
- :href="currentState.secondaryButtonPath"
- class="btn"
- >
- {{ currentState.secondaryButtonText }}
- </a>
+
+ <div class="col-12">
+ <div class="text-content">
+ <h4 class="state-title text-center">{{ currentState.title }}</h4>
+ <p class="state-description">
+ {{ currentState.description }}
+ <a v-if="showButtonDescription" :href="settingsPath"> Prometheus server </a>
+ </p>
+
+ <div class="text-center">
+ <a v-if="currentState.buttonPath" :href="currentState.buttonPath" class="btn btn-success">
+ {{ currentState.buttonText }}
+ </a>
+ <a
+ v-if="currentState.secondaryButtonPath"
+ :href="currentState.secondaryButtonPath"
+ class="btn"
+ >
+ {{ currentState.secondaryButtonText }}
+ </a>
+ </div>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 41270e015d4..9d78b5ea110 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean } from '~/lib/utils/common_utils';
import Dashboard from './components/dashboard.vue';
export default () => {
@@ -13,7 +13,7 @@ export default () => {
return createElement(Dashboard, {
props: {
...el.dataset,
- hasMetrics: convertPermissionToBoolean(el.dataset.hasMetrics),
+ hasMetrics: parseBoolean(el.dataset.hasMetrics),
},
});
},
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index cba6759ebf5..ee1a5274ff7 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -3,10 +3,11 @@
import $ from 'jquery';
import Api from './api';
import { mergeUrlParams } from './lib/utils/url_utility';
+import { parseBoolean } from '~/lib/utils/common_utils';
export default class NamespaceSelect {
constructor(opts) {
- const isFilter = opts.dropdown.dataset.isFilter === 'true';
+ const isFilter = parseBoolean(opts.dropdown.dataset.isFilter);
const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
$(opts.dropdown).glDropdown({
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 8e8bd150647..af821df0fd2 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -4,7 +4,9 @@ import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { GlSkeletonLoading } from '@gitlab/ui';
-import { trimFirstCharOfLineContent, getDiffMode } from '~/diffs/store/utils';
+import { getDiffMode } from '~/diffs/store/utils';
+
+const FIRST_CHAR_REGEX = /^(\+|-| )/;
export default {
components: {
@@ -26,46 +28,16 @@ export default {
},
computed: {
...mapState({
- noteableData: state => state.notes.noteableData,
projectPath: state => state.diffs.projectPath,
}),
diffMode() {
- return getDiffMode(this.diffFile);
+ return getDiffMode(this.discussion.diff_file);
},
hasTruncatedDiffLines() {
return (
this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0
);
},
- isDiscussionsExpanded() {
- return true; // TODO: @fatihacet - Fix this.
- },
- isCollapsed() {
- return this.diffFile.collapsed || false;
- },
- isImageDiff() {
- return !this.diffFile.text;
- },
- diffFileClass() {
- const { text } = this.diffFile;
- return text ? 'text-file' : 'js-image-file';
- },
- diffFile() {
- return this.discussion.diff_file;
- },
- imageDiffHtml() {
- return this.discussion.image_diff_html;
- },
- userColorScheme() {
- return window.gon.user_color_scheme;
- },
- normalizedDiffLines() {
- if (this.discussion.truncated_diff_lines) {
- return this.discussion.truncated_diff_lines.map(line => trimFirstCharOfLineContent(line));
- }
-
- return [];
- },
},
mounted() {
if (!this.hasTruncatedDiffLines) {
@@ -74,9 +46,6 @@ export default {
},
methods: {
...mapActions(['fetchDiscussionDiffLines']),
- rowTag(html) {
- return html.outerHTML ? 'tr' : 'template';
- },
fetchDiff() {
this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
@@ -85,31 +54,45 @@ export default {
this.error = true;
});
},
+ trimChar(line) {
+ return line.replace(FIRST_CHAR_REGEX, '');
+ },
},
+ userColorSchemeClass: window.gon.user_color_scheme,
};
</script>
<template>
- <div ref="fileHolder" :class="diffFileClass" class="diff-file file-holder">
+ <div :class="{ 'text-file': discussion.diff_file.text }" class="diff-file file-holder">
<diff-file-header
:discussion-path="discussion.discussion_path"
- :diff-file="diffFile"
+ :diff-file="discussion.diff_file"
:can-current-user-fork="false"
- :discussions-expanded="isDiscussionsExpanded"
- :expanded="!isCollapsed"
+ :expanded="!discussion.diff_file.collapsed"
/>
- <div v-if="diffFile.text" :class="userColorScheme" class="diff-content code">
+ <div
+ v-if="discussion.diff_file.text"
+ :class="$options.userColorSchemeClass"
+ class="diff-content code"
+ >
<table>
- <tr v-for="line in normalizedDiffLines" :key="line.line_code" class="line_holder">
- <td class="diff-line-num old_line">{{ line.old_line }}</td>
- <td class="diff-line-num new_line">{{ line.new_line }}</td>
- <td :class="line.type" class="line_content" v-html="line.rich_text"></td>
- </tr>
+ <template v-if="hasTruncatedDiffLines">
+ <tr
+ v-for="line in discussion.truncated_diff_lines"
+ v-once
+ :key="line.line_code"
+ class="line_holder"
+ >
+ <td class="diff-line-num old_line">{{ line.old_line }}</td>
+ <td class="diff-line-num new_line">{{ line.new_line }}</td>
+ <td :class="line.type" class="line_content" v-html="trimChar(line.rich_text)"></td>
+ </tr>
+ </template>
<tr v-if="!hasTruncatedDiffLines" class="line_holder line-holder-placeholder">
<td class="old_line diff-line-num"></td>
<td class="new_line diff-line-num"></td>
<td v-if="error" class="js-error-lazy-load-diff diff-loading-error-block">
- Unable to load the diff
+ {{ error }} Unable to load the diff
<button
class="btn-link btn-link-retry btn-no-padding js-toggle-lazy-diff-retry-button"
@click="fetchDiff"
@@ -131,17 +114,17 @@ export default {
<div v-else>
<diff-viewer
:diff-mode="diffMode"
- :new-path="diffFile.new_path"
- :new-sha="diffFile.diff_refs.head_sha"
- :old-path="diffFile.old_path"
- :old-sha="diffFile.diff_refs.base_sha"
- :file-hash="diffFile.file_hash"
+ :new-path="discussion.diff_file.new_path"
+ :new-sha="discussion.diff_file.diff_refs.head_sha"
+ :old-path="discussion.diff_file.old_path"
+ :old-sha="discussion.diff_file.diff_refs.base_sha"
+ :file-hash="discussion.diff_file.file_hash"
:project-path="projectPath"
>
<image-diff-overlay
slot="image-overlay"
:discussions="discussion"
- :file-hash="diffFile.file_hash"
+ :file-hash="discussion.diff_file.file_hash"
:show-comment-icon="true"
:should-toggle-discussion="false"
badge-class="image-comment-badge"
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index ee79ecbf9b3..c7cfc0f0f3b 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,13 +1,12 @@
<script>
import { mapActions, mapGetters } from 'vuex';
+import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-import { pluralize } from '../../lib/utils/text_utility';
import discussionNavigation from '../mixins/discussion_navigation';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
Icon,
@@ -17,9 +16,9 @@ export default {
...mapGetters([
'getUserData',
'getNoteableData',
- 'discussionCount',
+ 'resolvableDiscussionsCount',
'firstUnresolvedDiscussionId',
- 'resolvedDiscussionCount',
+ 'unresolvedDiscussionsCount',
]),
isLoggedIn() {
return this.getUserData.id;
@@ -27,15 +26,15 @@ export default {
hasNextButton() {
return this.isLoggedIn && !this.allResolved;
},
- countText() {
- return pluralize('discussion', this.discussionCount);
- },
allResolved() {
- return this.resolvedDiscussionCount === this.discussionCount;
+ return this.unresolvedDiscussionsCount === 0;
},
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
+ resolvedDiscussionsCount() {
+ return this.resolvableDiscussionsCount - this.unresolvedDiscussionsCount;
+ },
},
methods: {
...mapActions(['expandDiscussion']),
@@ -50,7 +49,7 @@ export default {
</script>
<template>
- <div v-if="discussionCount > 0" class="line-resolve-all-container prepend-top-8">
+ <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container prepend-top-8">
<div>
<div :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all">
<span
@@ -61,15 +60,15 @@ export default {
<icon name="check-circle" />
</span>
<span class="line-resolve-text">
- {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved
+ {{ resolvedDiscussionsCount }}/{{ resolvableDiscussionsCount }}
+ {{ n__('discussion resolved', 'discussions resolved', resolvableDiscussionsCount) }}
</span>
</div>
<div v-if="resolveAllDiscussionsIssuePath && !allResolved" class="btn-group" role="group">
<a
- v-tooltip
+ v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
:title="s__('Resolve all discussions in new issue')"
- data-container="body"
class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
>
<icon name="issue-new" />
@@ -77,9 +76,8 @@ export default {
</div>
<div v-if="isLoggedIn && !allResolved" class="btn-group" role="group">
<button
- v-tooltip
+ v-gl-tooltip
title="Jump to first unresolved discussion"
- data-container="body"
class="btn btn-default discussion-next-btn"
@click="jumpToFirstUnresolvedDiscussion"
>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 9a5817890c9..d99694b06e9 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,8 +1,7 @@
<script>
import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
export default {
name: 'NoteActions',
@@ -11,7 +10,7 @@ export default {
GlLoadingIcon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
authorId: {
@@ -119,10 +118,10 @@ export default {
<template>
<div class="note-actions">
- <span v-if="accessLevel" class="note-role user-access-role"> {{ accessLevel }} </span>
+ <span v-if="accessLevel" class="note-role user-access-role">{{ accessLevel }}</span>
<div v-if="canResolve" class="note-actions-item">
<button
- v-tooltip
+ v-gl-tooltip
:class="{ 'is-disabled': !resolvable, 'is-active': isResolved }"
:title="resolveButtonTitle"
:aria-label="resolveButtonTitle"
@@ -138,12 +137,10 @@ export default {
</div>
<div v-if="canAwardEmoji" class="note-actions-item">
<a
- v-tooltip
+ v-gl-tooltip.bottom
:class="{ 'js-user-authored': isAuthoredByCurrentUser }"
class="note-action-button note-emoji-button js-add-award js-note-emoji"
data-position="right"
- data-placement="bottom"
- data-container="body"
href="#"
title="Add reaction"
>
@@ -158,12 +155,10 @@ export default {
</div>
<div v-if="canEdit" class="note-actions-item">
<button
- v-tooltip
+ v-gl-tooltip.bottom
type="button"
title="Edit comment"
class="note-action-button js-note-edit btn btn-transparent"
- data-container="body"
- data-placement="bottom"
@click="onEdit"
>
<icon name="pencil" css-classes="link-highlight" />
@@ -171,12 +166,10 @@ export default {
</div>
<div v-if="showDeleteAction" class="note-actions-item">
<button
- v-tooltip
+ v-gl-tooltip.bottom
type="button"
title="Delete comment"
class="note-action-button js-note-delete btn btn-transparent"
- data-container="body"
- data-placement="bottom"
@click="onDelete"
>
<icon name="remove" class="link-highlight" />
@@ -184,19 +177,17 @@ export default {
</div>
<div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item">
<button
- v-tooltip
+ v-gl-tooltip.bottom
type="button"
title="More actions"
class="note-action-button more-actions-toggle btn btn-transparent"
data-toggle="dropdown"
- data-container="body"
- data-placement="bottom"
>
<icon css-classes="icon" name="ellipsis_v" />
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
- <a :href="reportAbusePath"> {{ __('Report abuse to GitLab') }} </a>
+ <a :href="reportAbusePath">{{ __('Report abuse to GitLab') }}</a>
</li>
<li v-if="noteUrl">
<button
@@ -213,7 +204,7 @@ export default {
type="button"
@click.prevent="onDelete"
>
- <span class="text-danger"> {{ __('Delete comment') }} </span>
+ <span class="text-danger">{{ __('Delete comment') }}</span>
</button>
</li>
</ul>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 4aba2e65edb..3d60eb02db8 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,16 +1,16 @@
<script>
import { mapActions, mapGetters } from 'vuex';
+import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
awards: {
@@ -167,21 +167,19 @@ export default {
<button
v-for="(awardList, awardName, index) in groupedAwards"
:key="index"
- v-tooltip
+ v-gl-tooltip.bottom="{ boundary: 'viewport' }"
:class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
class="btn award-control"
- data-boundary="viewport"
- data-placement="bottom"
type="button"
@click="handleAward(awardName);"
>
<span v-html="getAwardHTML(awardName)"></span>
- <span class="award-control-text js-counter"> {{ awardList.length }} </span>
+ <span class="award-control-text js-counter">{{ awardList.length }}</span>
</button>
<div v-if="canAwardEmoji" class="award-menu-holder">
<button
- v-tooltip
+ v-gl-tooltip
:class="{ 'js-user-authored': isAuthoredByMe }"
class="award-control btn js-add-award"
title="Add reaction"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 8b7450783c9..e1a58e7cb26 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -73,7 +73,7 @@ export default {
{{ __('Toggle discussion') }}
</button>
</div>
- <a v-if="hasAuthor" :href="author.path">
+ <a v-if="hasAuthor" v-once :href="author.path">
<span class="note-header-author-name">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
<span class="note-headline-light"> @{{ author.username }} </span>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 29740ddf6ae..da78c6e21a1 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,7 +1,9 @@
<script>
+import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
+import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
-import { s__ } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
@@ -20,14 +22,12 @@ import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
name: 'NoteableDiscussion',
components: {
icon,
noteableNote,
- diffWithNote,
userAvatarLink,
noteHeader,
noteSignedOutWidget,
@@ -39,7 +39,7 @@ export default {
systemNote,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
mixins: [autosave, noteable, resolvable, discussionNavigation],
props: {
@@ -74,33 +74,12 @@ export default {
computed: {
...mapGetters([
'getNoteableData',
- 'discussionCount',
- 'resolvedDiscussionCount',
- 'allDiscussions',
- 'unresolvedDiscussionsIdsByDiff',
- 'unresolvedDiscussionsIdsByDate',
- 'unresolvedDiscussions',
- 'unresolvedDiscussionsIdsOrdered',
'nextUnresolvedDiscussionId',
- 'isLastUnresolvedDiscussion',
+ 'unresolvedDiscussionsCount',
+ 'hasUnresolvedDiscussions',
]),
- transformedDiscussion() {
- return {
- ...this.discussion.notes[0],
- truncated_diff_lines: this.discussion.truncated_diff_lines || [],
- truncated_diff_lines_path: this.discussion.truncated_diff_lines_path,
- diff_file: this.discussion.diff_file,
- diff_discussion: this.discussion.diff_discussion,
- active: this.discussion.active,
- discussion_path: this.discussion.discussion_path,
- resolved: this.discussion.resolved,
- resolved_by: this.discussion.resolved_by,
- resolved_by_push: this.discussion.resolved_by_push,
- resolved_at: this.discussion.resolved_at,
- };
- },
author() {
- return this.transformedDiscussion.author;
+ return this.initialDiscussion.author;
},
canReply() {
return this.getNoteableData.current_user.can_create_note;
@@ -136,29 +115,13 @@ export default {
return null;
},
resolvedText() {
- return this.transformedDiscussion.resolved_by_push ? 'Automatically resolved' : 'Resolved';
- },
- hasMultipleUnresolvedDiscussions() {
- return this.unresolvedDiscussions.length > 1;
- },
- showJumpToNextDiscussion() {
- return (
- this.hasMultipleUnresolvedDiscussions &&
- !this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder)
- );
+ return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
shouldRenderDiffs() {
- return (
- this.transformedDiscussion.diff_discussion &&
- this.transformedDiscussion.diff_file &&
- this.renderDiffFile
- );
+ return this.discussion.diff_discussion && this.renderDiffFile;
},
shouldGroupReplies() {
- return !this.shouldRenderDiffs && !this.transformedDiscussion.diff_discussion;
- },
- shouldRenderHeader() {
- return this.shouldRenderDiffs;
+ return !this.shouldRenderDiffs && !this.discussion.diff_discussion;
},
wrapperComponent() {
return this.shouldRenderDiffs ? diffWithNote : 'div';
@@ -170,9 +133,6 @@ export default {
return {};
},
- wrapperClass() {
- return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
- },
componentClassName() {
if (this.shouldRenderDiffs) {
if (!this.lastUpdatedAt && !this.discussion.resolved) {
@@ -183,11 +143,10 @@ export default {
return '';
},
shouldShowDiscussions() {
- const isExpanded = this.discussion.expanded;
- const { resolved } = this.transformedDiscussion;
- const isResolvedNonDiffDiscussion = !this.transformedDiscussion.diff_discussion && resolved;
+ const { expanded, resolved } = this.discussion;
+ const isResolvedNonDiffDiscussion = !this.discussion.diff_discussion && resolved;
- return isExpanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
+ return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
},
isRepliesCollapsed() {
const { discussion, isRepliesToggledByUser } = this;
@@ -198,13 +157,44 @@ export default {
(!discussion.diff_discussion && resolved && hasReplies && !isRepliesToggledByUser) || false
);
},
+ actionText() {
+ const commitId = this.discussion.commit_id ? truncateSha(this.discussion.commit_id) : '';
+ const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
+ const linkEnd = '</a>';
+
+ let text = s__('MergeRequests|started a discussion');
+
+ if (this.discussion.for_commit) {
+ text = s__(
+ 'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}',
+ );
+ } else if (this.discussion.diff_discussion) {
+ if (this.discussion.active) {
+ text = s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}');
+ } else {
+ text = s__(
+ 'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}',
+ );
+ }
+ }
+
+ return sprintf(
+ text,
+ {
+ commitId,
+ linkStart,
+ linkEnd,
+ },
+ false,
+ );
+ },
},
watch: {
isReplying() {
if (this.isReplying) {
this.$nextTick(() => {
// Pass an extra key to separate reply and note edit forms
- this.initAutoSave(this.transformedDiscussion, ['Reply']);
+ this.initAutoSave({ ...this.initialDiscussion, ...this.discussion }, ['Reply']);
});
} else {
this.disposeAutoSave();
@@ -314,12 +304,9 @@ Please check your network connection and try again.`;
<li class="note note-discussion timeline-entry" :class="componentClassName">
<div class="timeline-entry-inner">
<div class="timeline-content">
- <div
- :data-discussion-id="transformedDiscussion.discussion_id"
- class="discussion js-discussion-container"
- >
- <div v-if="shouldRenderHeader" class="discussion-header note-wrapper">
- <div class="timeline-icon">
+ <div :data-discussion-id="discussion.id" class="discussion js-discussion-container">
+ <div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
+ <div v-once class="timeline-icon">
<user-avatar-link
v-if="author"
:link-href="author.path"
@@ -330,35 +317,18 @@ Please check your network connection and try again.`;
</div>
<note-header
:author="author"
- :created-at="transformedDiscussion.created_at"
- :note-id="transformedDiscussion.id"
+ :created-at="initialDiscussion.created_at"
+ :note-id="initialDiscussion.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
>
- <template v-if="transformedDiscussion.diff_discussion">
- started a discussion on
- <a :href="transformedDiscussion.discussion_path">
- <template v-if="transformedDiscussion.active">
- the diff
- </template>
- <template v-else>
- an old version of the diff
- </template>
- </a>
- </template>
- <template v-else-if="discussion.for_commit">
- started a discussion on commit
- <a :href="discussion.discussion_path"> {{ truncateSha(discussion.commit_id) }} </a>
- </template>
- <template v-else>
- started a discussion
- </template>
+ <span v-html="actionText"></span>
</note-header>
<note-edited-text
- v-if="transformedDiscussion.resolved"
- :edited-at="transformedDiscussion.resolved_at"
- :edited-by="transformedDiscussion.resolved_by"
+ v-if="discussion.resolved"
+ :edited-at="discussion.resolved_at"
+ :edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline"
/>
@@ -371,7 +341,11 @@ Please check your network connection and try again.`;
/>
</div>
<div v-if="shouldShowDiscussions" class="discussion-body">
- <component :is="wrapperComponent" v-bind="wrapperComponentProps" :class="wrapperClass">
+ <component
+ :is="wrapperComponent"
+ v-bind="wrapperComponentProps"
+ class="card discussion-wrapper"
+ >
<div class="discussion-notes">
<ul class="notes">
<template v-if="shouldGroupReplies">
@@ -380,7 +354,7 @@ Please check your network connection and try again.`;
:note="componentData(initialDiscussion)"
@handleDeleteNote="deleteNoteHandler"
>
- <slot slot="avatar-badge" name="avatar-badge"> </slot>
+ <slot slot="avatar-badge" name="avatar-badge"></slot>
</component>
<toggle-replies-widget
v-if="hasReplies"
@@ -406,7 +380,7 @@ Please check your network connection and try again.`;
:note="componentData(note)"
@handleDeleteNote="deleteNoteHandler"
>
- <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"> </slot>
+ <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
</component>
</template>
</ul>
@@ -446,22 +420,19 @@ Please check your network connection and try again.`;
>
<div v-if="!discussionResolved" class="btn-group" role="group">
<a
- v-tooltip
+ v-gl-tooltip
:href="discussion.resolve_with_issue_path"
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
- class="new-issue-for-discussion btn
- btn-default discussion-create-issue-btn"
- data-container="body"
+ class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
>
<icon name="issue-new" />
</a>
</div>
- <div v-if="showJumpToNextDiscussion" class="btn-group" role="group">
+ <div v-if="hasUnresolvedDiscussions" class="btn-group" role="group">
<button
- v-tooltip
+ v-gl-tooltip
class="btn btn-default discussion-next-btn"
title="Jump to next unresolved discussion"
- data-container="body"
@click="jumpToNextDiscussion"
>
<icon name="comment-next" />
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index c2e49f8b23f..fc37fe1c7af 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -177,7 +177,7 @@ export default {
class="note timeline-entry note-wrapper"
>
<div class="timeline-entry-inner">
- <div class="timeline-icon">
+ <div v-once class="timeline-icon">
<user-avatar-link
:link-href="author.path"
:img-src="author.avatar_url"
@@ -190,6 +190,7 @@ export default {
<div class="timeline-content">
<div class="note-header">
<note-header
+ v-once
:author="author"
:created-at="note.created_at"
:note-id="note.id"
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 79ece036e69..6e6efb04753 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -22,6 +22,7 @@ export default {
commentForm,
placeholderNote,
placeholderSystemNote,
+ skeletonLoadingContainer,
},
props: {
noteableData: {
@@ -59,7 +60,6 @@ export default {
'isNotesFetched',
'discussions',
'getNotesDataByProp',
- 'discussionCount',
'isLoading',
'commentsDisabled',
]),
@@ -109,39 +109,22 @@ export default {
this.$nextTick(() => highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')));
},
methods: {
- ...mapActions({
- setLoadingState: 'setLoadingState',
- fetchDiscussions: 'fetchDiscussions',
- poll: 'poll',
- actionToggleAward: 'toggleAward',
- scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
- setNotesData: 'setNotesData',
- setNoteableData: 'setNoteableData',
- setUserData: 'setUserData',
- setLastFetchedAt: 'setLastFetchedAt',
- setTargetNoteHash: 'setTargetNoteHash',
- toggleDiscussion: 'toggleDiscussion',
- setNotesFetchedState: 'setNotesFetchedState',
- startTaskList: 'startTaskList',
- }),
- getComponentName(discussion) {
- if (discussion.isSkeletonNote) {
- return skeletonLoadingContainer;
- }
- if (discussion.isPlaceholderNote) {
- if (discussion.placeholderType === constants.SYSTEM_NOTE) {
- return placeholderSystemNote;
- }
- return placeholderNote;
- } else if (discussion.individual_note) {
- return discussion.notes[0].system ? systemNote : noteableNote;
- }
-
- return noteableDiscussion;
- },
- getComponentData(discussion) {
- return discussion.individual_note ? { note: discussion.notes[0] } : { discussion };
- },
+ ...mapActions([
+ 'setLoadingState',
+ 'fetchDiscussions',
+ 'poll',
+ 'toggleAward',
+ 'scrollToNoteIfNeeded',
+ 'setNotesData',
+ 'setNoteableData',
+ 'setUserData',
+ 'setLastFetchedAt',
+ 'setTargetNoteHash',
+ 'toggleDiscussion',
+ 'setNotesFetchedState',
+ 'expandDiscussion',
+ 'startTaskList',
+ ]),
fetchNotes() {
if (this.isFetching) return null;
@@ -181,31 +164,46 @@ export default {
const noteId = hash && hash.replace(/^note_/, '');
if (noteId) {
- this.discussions.forEach(discussion => {
- if (discussion.notes) {
- discussion.notes.forEach(note => {
- if (`${note.id}` === `${noteId}`) {
- // FIXME: this modifies the store state without using a mutation/action
- Object.assign(discussion, { expanded: true });
- }
- });
- }
- });
+ const discussion = this.discussions.find(d => d.notes.some(({ id }) => id === noteId));
+
+ if (discussion) {
+ this.expandDiscussion({ discussionId: discussion.id });
+ }
}
},
},
+ systemNote: constants.SYSTEM_NOTE,
};
</script>
<template>
<div v-show="shouldShow" id="notes">
<ul id="notes-list" class="notes main-notes-list timeline">
- <component
- :is="getComponentName(discussion)"
- v-for="discussion in allDiscussions"
- :key="discussion.id"
- v-bind="getComponentData(discussion)"
- />
+ <template v-for="discussion in allDiscussions">
+ <skeleton-loading-container v-if="discussion.isSkeletonNote" :key="discussion.id" />
+ <template v-else-if="discussion.isPlaceholderNote">
+ <placeholder-system-note
+ v-if="discussion.placeholderType === $options.systemNote"
+ :key="discussion.id"
+ :note="discussion.notes[0]"
+ />
+ <placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" />
+ </template>
+ <template v-else-if="discussion.individual_note">
+ <system-note
+ v-if="discussion.notes[0].system"
+ :key="discussion.id"
+ :note="discussion.notes[0]"
+ />
+ <noteable-note v-else :key="discussion.id" :note="discussion.notes[0]" />
+ </template>
+ <noteable-discussion
+ v-else
+ :key="discussion.id"
+ :discussion="discussion"
+ :render-diff-file="true"
+ />
+ </template>
</ul>
<comment-form
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 5b2f0540020..b4befdd6e4a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -11,7 +11,7 @@ import * as constants from '../constants';
import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
-import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
+import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
import { __ } from '~/locale';
@@ -39,12 +39,13 @@ export const setNotesFetchedState = ({ commit }, state) =>
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-export const fetchDiscussions = ({ commit }, { path, filter }) =>
+export const fetchDiscussions = ({ commit, dispatch }, { path, filter }) =>
service
.fetchDiscussions(path, filter)
.then(res => res.json())
.then(discussions => {
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
+ dispatch('updateResolvableDiscussonsCounts');
});
export const updateDiscussion = ({ commit, state }, discussion) => {
@@ -53,11 +54,18 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
return utils.findNoteObjectById(state.discussions, discussion.id);
};
-export const deleteNote = ({ commit, dispatch }, note) =>
+export const deleteNote = ({ commit, dispatch, state }, note) =>
service.deleteNote(note.path).then(() => {
+ const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
+
commit(types.DELETE_NOTE, note);
dispatch('updateMergeRequestWidget');
+ dispatch('updateResolvableDiscussonsCounts');
+
+ if (isInMRPage()) {
+ dispatch('diffs/removeDiscussionsFromDiff', discussion);
+ }
});
export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
@@ -89,6 +97,7 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
dispatch('updateMergeRequestWidget');
dispatch('startTaskList');
+ dispatch('updateResolvableDiscussonsCounts');
}
return res;
});
@@ -104,6 +113,8 @@ export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved,
commit(mutationType, res);
+ dispatch('updateResolvableDiscussonsCounts');
+
dispatch('updateMergeRequestWidget');
});
@@ -385,5 +396,8 @@ export const startTaskList = ({ dispatch }) =>
}),
);
+export const updateResolvableDiscussonsCounts = ({ commit }) =>
+ commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS);
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 980d79605d7..2ed8aac059a 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -53,30 +53,15 @@ export const getCurrentUserLastNote = state =>
export const getDiscussionLastNote = state => discussion =>
reverseNotes(discussion.notes).find(el => isLastNote(el, state));
-export const discussionCount = state => {
- const filteredDiscussions = state.discussions.filter(n => !n.individual_note && n.resolvable);
-
- return filteredDiscussions.length;
-};
-
-export const unresolvedDiscussions = (state, getters) => {
- const resolvedMap = getters.resolvedDiscussionsById;
-
- return state.discussions.filter(n => !n.individual_note && !resolvedMap[n.id]);
-};
-
-export const allDiscussions = (state, getters) => {
- const resolved = getters.resolvedDiscussionsById;
- const unresolved = getters.unresolvedDiscussions;
-
- return Object.values(resolved).concat(unresolved);
-};
+export const unresolvedDiscussionsCount = state => state.unresolvedDiscussionsCount;
+export const resolvableDiscussionsCount = state => state.resolvableDiscussionsCount;
+export const hasUnresolvedDiscussions = state => state.hasUnresolvedDiscussions;
export const isDiscussionResolved = (state, getters) => discussionId =>
getters.resolvedDiscussionsById[discussionId] !== undefined;
-export const allResolvableDiscussions = (state, getters) =>
- getters.allDiscussions.filter(d => !d.individual_note && d.resolvable);
+export const allResolvableDiscussions = state =>
+ state.discussions.filter(d => !d.individual_note && d.resolvable);
export const resolvedDiscussionsById = state => {
const map = {};
@@ -147,15 +132,12 @@ export const resolvedDiscussionCount = (state, getters) => {
return Object.keys(resolvedMap).length;
};
-export const discussionTabCounter = state => {
- let all = [];
-
- state.discussions.forEach(discussion => {
- all = all.concat(discussion.notes.filter(note => !note.system && !note.placeholder));
- });
-
- return all.length;
-};
+export const discussionTabCounter = state =>
+ state.discussions.reduce(
+ (acc, discussion) =>
+ acc + discussion.notes.filter(note => !note.system && !note.placeholder).length,
+ 0,
+ );
// Returns the list of discussion IDs ordered according to given parameter
// @param {Boolean} diffOrder - is ordered by diff?
@@ -182,8 +164,10 @@ export const isLastUnresolvedDiscussion = (state, getters) => (discussionId, dif
export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
const currentIndex = idsOrdered.indexOf(discussionId);
+ const slicedIds = idsOrdered.slice(currentIndex + 1, currentIndex + 2);
- return idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0];
+ // Get the first ID if there is none after the currentIndex
+ return slicedIds.length ? idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0] : idsOrdered[0];
};
// @param {Boolean} diffOrder - is ordered by diff?
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 8aea269ea7d..b5fe8bdb1d3 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -22,6 +22,9 @@ export default () => ({
current_user: {},
},
commentsDisabled: false,
+ resolvableDiscussionsCount: 0,
+ unresolvedDiscussionsCount: 0,
+ hasUnresolvedDiscussions: false,
},
actions,
getters,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index dfbf3b7b34b..9c68ab67a8c 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -21,6 +21,7 @@ export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
export const EXPAND_DISCUSSION = 'EXPAND_DISCUSSION';
export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
+export const UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS = 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS';
// Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index f6054e0be87..667c8a97cf3 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -24,6 +24,7 @@ export default {
noteData.resolved = false;
noteData.resolve_path = note.resolve_path;
noteData.resolve_with_issue_path = note.resolve_with_issue_path;
+ noteData.diff_discussion = false;
}
state.discussions.push(noteData);
@@ -97,33 +98,36 @@ export default {
},
[types.SET_INITIAL_DISCUSSIONS](state, discussionsData) {
- const discussions = [];
+ const discussions = discussionsData.reduce((acc, d) => {
+ const discussion = { ...d };
+ const diffData = {};
- discussionsData.forEach(discussion => {
if (discussion.diff_file) {
- Object.assign(discussion, {
- file_hash: discussion.diff_file.file_hash,
- truncated_diff_lines: discussion.truncated_diff_lines || [],
- });
+ diffData.file_hash = discussion.diff_file.file_hash;
+ diffData.truncated_diff_lines = discussion.truncated_diff_lines || [];
}
// To support legacy notes, should be very rare case.
if (discussion.individual_note && discussion.notes.length > 1) {
discussion.notes.forEach(n => {
- discussions.push({
+ acc.push({
...discussion,
+ ...diffData,
notes: [n], // override notes array to only have one item to mimick individual_note
});
});
} else {
const oldNote = utils.findNoteObjectById(state.discussions, discussion.id);
- discussions.push({
+ acc.push({
...discussion,
+ ...diffData,
expanded: oldNote ? oldNote.expanded : discussion.expanded,
});
}
- });
+
+ return acc;
+ }, []);
Object.assign(state, { discussions });
},
@@ -195,7 +199,9 @@ export default {
const selectedDiscussion = state.discussions.find(disc => disc.id === note.id);
note.expanded = true; // override expand flag to prevent collapse
if (note.diff_file) {
- Object.assign(note, { file_hash: note.diff_file.file_hash });
+ Object.assign(note, {
+ file_hash: note.diff_file.file_hash,
+ });
}
Object.assign(selectedDiscussion, { ...note });
},
@@ -229,4 +235,16 @@ export default {
[types.DISABLE_COMMENTS](state, value) {
state.commentsDisabled = value;
},
+ [types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS](state) {
+ state.resolvableDiscussionsCount = state.discussions.filter(
+ discussion => !discussion.individual_note && discussion.resolvable,
+ ).length;
+ state.unresolvedDiscussionsCount = state.discussions.filter(
+ discussion =>
+ !discussion.individual_note &&
+ discussion.resolvable &&
+ discussion.notes.some(note => !note.resolved),
+ ).length;
+ state.hasUnresolvedDiscussions = state.unresolvedDiscussionsCount > 1;
+ },
};
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index d9cf62db3f7..674b807edbe 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { truncate } from '../../../lib/utils/text_utility';
+import { parseBoolean } from '~/lib/utils/common_utils';
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
@@ -26,7 +27,7 @@ export default class AbuseReports {
const $messageCellElement = $(this);
const originalMessage = $messageCellElement.data('originalMessage');
if (!originalMessage) return;
- if ($messageCellElement.data('messageTruncated') === 'true') {
+ if (parseBoolean($messageCellElement.data('messageTruncated'))) {
$messageCellElement.data('messageTruncated', 'false');
$messageCellElement.text(originalMessage);
} else {
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 417935e2ad0..10cd8ecfbc9 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -1,9 +1,10 @@
import $ from 'jquery';
import U2FRegister from '~/u2f/register';
+import { parseBoolean } from '~/lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
- const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true';
+ const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable);
if (skippable) {
const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${
twoFactorNode.dataset.two_factor_skip_url
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index f477424811d..6fc982967eb 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -11,6 +11,8 @@ import initDiffNotes from '~/diff_notes/diff_notes_bundle';
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
document.addEventListener('DOMContentLoaded', () => {
+ const hasPerfBar = document.querySelector('.with-performance-bar');
+ const performanceHeight = hasPerfBar ? 35 : 0;
new Diff();
new ZenMode();
new ShortcutsNavigation();
@@ -18,8 +20,7 @@ document.addEventListener('DOMContentLoaded', () => {
container: '.js-commit-pipeline-graph',
}).bindEvents();
initNotes();
- const stickyBarPaddingTop = 16;
- initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
+ initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
fetchCommitMergeRequests();
initDiffNotes();
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 197bfa8a394..02a56685a35 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -7,6 +7,7 @@ import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
+import initSuggestions from '~/issuable_suggestions';
export default () => {
new ShortcutsNavigation();
@@ -15,4 +16,8 @@ export default () => {
new LabelsSelect();
new MilestoneSelect();
new IssuableTemplateSelectors();
+
+ if (gon.features.issueSuggestions && gon.features.graphql) {
+ initSuggestions();
+ }
};
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
index 1edd076604c..22512a6f12a 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -3,6 +3,7 @@ import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../../../../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg';
+import { parseBoolean } from '~/lib/utils/common_utils';
Vue.use(Translate);
@@ -13,7 +14,7 @@ export default {
data() {
return {
docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
- calloutDismissed: Cookies.get(cookieKey) === 'true',
+ calloutDismissed: parseBoolean(Cookies.get(cookieKey)),
};
},
created() {
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index fc337a7609b..fd72d2ddbe0 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
-import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils';
+import { parseBoolean } from '../../../../lib/utils/common_utils';
Vue.use(Translate);
@@ -33,8 +33,8 @@ document.addEventListener(
noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
autoDevopsPath: this.dataset.helpAutoDevopsPath,
newPipelinePath: this.dataset.newPipelinePath,
- canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
- hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
+ canCreatePipeline: parseBoolean(this.dataset.canCreatePipeline),
+ hasGitlabCi: parseBoolean(this.dataset.hasGitlabCi),
ciLintPath: this.dataset.ciLintPath,
resetCachePath: this.dataset.resetCachePath,
},
diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index d3e8dbf4000..9b58d42b47d 100644
--- a/app/assets/javascripts/pages/projects/wikis/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,5 +1,4 @@
import bp from '../../../breakpoints';
-import { slugify } from '../../../lib/utils/text_utility';
import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility';
@@ -26,7 +25,8 @@ export default class Wikis {
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
- const slug = slugify(slugInput.value);
+
+ const slug = slugInput.value;
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
diff --git a/app/assets/javascripts/performance_bar/services/performance_bar_service.js b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
index 3a496fa2ed8..d8c23c82f7f 100644
--- a/app/assets/javascripts/performance_bar/services/performance_bar_service.js
+++ b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import _ from 'underscore';
import axios from '../../lib/utils/axios_utils';
+import { parseBoolean } from '~/lib/utils/common_utils';
let vueResourceInterceptor;
@@ -41,7 +42,8 @@ export default class PerformanceBarService {
// Vue Resource.
const requestUrl = (response.config || response).url;
const apiRequest = requestUrl && requestUrl.match(/^\/api\//);
- const cachedResponse = response.headers && response.headers['x-gitlab-from-cache'] === 'true';
+ const cachedResponse =
+ response.headers && parseBoolean(response.headers['x-gitlab-from-cache']);
const fireCallback = requestUrl !== peekUrl && requestId && !apiRequest && !cachedResponse;
return [fireCallback, requestId, requestUrl];
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index 8704a655b28..deacff5abe7 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import flash from '../flash';
+import { parseBoolean } from '~/lib/utils/common_utils';
export default class Profile {
constructor({ form } = {}) {
@@ -80,7 +81,7 @@ export default class Profile {
setRepoRadio() {
const multiEditRadios = $('input[name="user[multi_file]"]');
- if (this.newRepoActivated || this.newRepoActivated === 'true') {
+ if (parseBoolean(this.newRepoActivated)) {
multiEditRadios.filter('[value=on]').prop('checked', true);
} else {
multiEditRadios.filter('[value=off]').prop('checked', true);
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index d83ffc7e211..bcb44bf7acf 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import Flash from './flash';
import { __ } from './locale';
-import { convertPermissionToBoolean } from './lib/utils/common_utils';
+import { parseBoolean } from './lib/utils/common_utils';
/*
example HAML:
@@ -18,7 +18,7 @@ function updateToggle(toggle, isOn) {
}
function onToggleClicked(toggle, input, clickCallback) {
- const previousIsOn = convertPermissionToBoolean(input.value);
+ const previousIsOn = parseBoolean(input.value);
// Visually change the toggle and start loading
updateToggle(toggle, !previousIsOn);
@@ -51,7 +51,7 @@ export default function setupToggleButtons(container, clickCallback = () => {})
toggles.forEach(toggle => {
const input = toggle.querySelector('.js-project-feature-toggle-input');
- const isOn = convertPermissionToBoolean(input.value);
+ const isOn = parseBoolean(input.value);
// Get the visible toggle in sync with the hidden input
updateToggle(toggle, isOn);
diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js
index 05607f09a7e..d3d745a3c11 100644
--- a/app/assets/javascripts/usage_ping_consent.js
+++ b/app/assets/javascripts/usage_ping_consent.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Flash, { hideFlash } from './flash';
-import { convertPermissionToBoolean } from './lib/utils/common_utils';
+import { parseBoolean } from './lib/utils/common_utils';
export default () => {
$('body').on('click', '.js-usage-consent-action', e => {
@@ -11,8 +11,8 @@ export default () => {
const { url, checkEnabled, pingEnabled } = e.target.dataset;
const data = {
application_setting: {
- version_check_enabled: convertPermissionToBoolean(checkEnabled),
- usage_ping_enabled: convertPermissionToBoolean(pingEnabled),
+ version_check_enabled: parseBoolean(checkEnabled),
+ usage_ping_enabled: parseBoolean(pingEnabled),
},
};
diff --git a/app/assets/stylesheets/bootstrap.scss b/app/assets/stylesheets/bootstrap.scss
index a040c2f8c20..4a09da3d580 100644
--- a/app/assets/stylesheets/bootstrap.scss
+++ b/app/assets/stylesheets/bootstrap.scss
@@ -1,5 +1,5 @@
/*
- * Includes specific styles from the bootstrap4 foler in node_modules
+ * Includes specific styles from the bootstrap4 folder in node_modules
*/
@import "../../../node_modules/bootstrap/scss/functions";
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 1e00aa4ff7e..62024b8c555 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -336,3 +336,12 @@ input[type=color].form-control {
.input-group-btn:last-child {
@extend .input-group-append;
}
+
+/*
+ Bootstrap 4.1.2 introduced a new default vertical alignment which breaks our icons,
+ so we need to reset the vertical alignment to the default value. See:
+ - https://gitlab.com/gitlab-org/gitlab-ce/issues/51362
+ */
+svg {
+ vertical-align: baseline;
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index dca89981d81..ce5d36a340f 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -802,11 +802,7 @@
@include media-breakpoint-down(xs) {
.navbar-gitlab {
- li.header-projects,
- li.header-groups,
- li.header-more,
- li.header-new,
- li.header-user {
+ li.dropdown {
position: static;
}
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 7a4c3914fb0..afa85f0e4ae 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -32,16 +32,16 @@
margin: 0;
}
+ .flash-text,
+ .flash-action {
+ display: inline-block;
+ }
+
.flash-alert {
@extend .alert;
background-color: $red-500;
margin: 0;
- .flash-text,
- .flash-action {
- display: inline-block;
- }
-
.flash-action {
margin-left: 5px;
text-decoration: none;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index d1ce3a582bb..39410ac56af 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -110,6 +110,10 @@
}
}
+ .navbar-collapse > ul.nav > li:not(.d-none) {
+ margin: 0 2px;
+ }
+
&.menu-expanded {
@include media-breakpoint-down(xs) {
.title-container {
@@ -117,7 +121,7 @@
}
.navbar-collapse {
- display: block;
+ display: flex;
}
}
}
@@ -209,7 +213,7 @@
> a {
will-change: color;
- margin: 4px 2px;
+ margin: 4px 0;
padding: 6px 8px;
height: 32px;
@@ -455,14 +459,11 @@
color: $indigo-900;
font-weight: $gl-font-weight-bold;
line-height: 18px;
+ margin: 4px 0 4px 2px;
&:hover {
background-color: $white-light;
}
-
- @include media-breakpoint-down(xs) {
- margin-top: $gl-padding-4;
- }
}
.navbar-nav {
@@ -509,12 +510,7 @@
margin-right: -10px;
.nav > li:not(.d-none) {
- display: table-cell !important;
- width: 25%;
-
- a {
- margin-right: 8px;
- }
+ flex: 1;
}
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b43bb3feef5..bf2868710eb 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -243,6 +243,7 @@ $gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
$gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
+$browserScrollbarSize: 10px;
/*
* Misc
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 715af4aa4ba..5405f20a760 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -10,22 +10,32 @@
position: -webkit-sticky;
position: sticky;
top: 92px;
+ margin-left: -1px;
+ border-left: 1px solid $border-color;
z-index: 102;
+ &.is-commit {
+ top: $header-height + 36px;
+
+ .with-performance-bar & {
+ top: $header-height + 36px + $performance-bar-height;
+ }
+ }
+
&::before {
content: '';
position: absolute;
top: -1px;
- left: -10px;
+ left: -11px;
width: 10px;
height: calc(100% + 1px);
background: $white-light;
- border-right: 1px solid $border-color;
+ pointer-events: none;
}
- }
- .with-performance-bar & {
- top: 127px;
+ .with-performance-bar & {
+ top: 127px;
+ }
}
a:hover {
@@ -701,15 +711,14 @@
}
@include media-breakpoint-up(sm) {
- top: 24px;
+ position: -webkit-sticky;
+ position: sticky;
+ top: $header-height;
background-color: $white-light;
+ z-index: 200;
- &.diff-files-changed-merge-request {
- position: sticky;
- top: 90px;
- z-index: 200;
- margin: $gl-padding 0;
- padding: 0;
+ .with-performance-bar & {
+ top: $header-height + $performance-bar-height;
}
&.is-stuck {
@@ -734,14 +743,6 @@
}
}
-@include media-breakpoint-up(sm) {
- .with-performance-bar {
- .diff-files-changed.diff-files-changed-merge-request {
- top: 76px + $performance-bar-height;
- }
- }
-}
-
.diff-file-changes {
max-width: 560px;
width: 100%;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 347fcad771a..75166ffcada 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -203,21 +203,6 @@
stroke: $gray-darkest;
}
-.prometheus-state {
- max-width: 460px;
- margin: 10px auto;
- text-align: center;
-
- .state-svg {
- max-width: 80vw;
- margin: 0 auto;
- }
-
- .state-button {
- padding: $gl-padding / 2;
- }
-}
-
.prometheus-graphs {
.environments {
.dropdown-menu-toggle {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 78c5ae9ae63..5b5f486ea63 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -938,3 +938,37 @@
}
}
}
+
+.issuable-suggestions svg {
+ vertical-align: sub;
+}
+
+.suggestion-item a {
+ color: initial;
+}
+
+.suggestion-confidential {
+ color: $orange-600;
+}
+
+.suggestion-state-open {
+ color: $green-500;
+}
+
+.suggestion-state-closed {
+ color: $blue-500;
+}
+
+.suggestion-help-hover {
+ cursor: help;
+}
+
+.suggestion-footer {
+ font-size: 12px;
+ line-height: 15px;
+
+ .avatar {
+ margin-top: -3px;
+ border: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 085ff27e6ef..4fda2964fd5 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -393,6 +393,14 @@ $note-form-margin-left: 72px;
border-top: 1px solid $border-color;
border-radius: 0;
+ @media (min-width: map-get($grid-breakpoints, md)) {
+ top: 91px;
+
+ .with-performance-bar & {
+ top: 126px;
+ }
+ }
+
&:hover {
background-color: $gray-light;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 80ec390d18e..6cc21072acd 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -723,7 +723,8 @@
.scrolling-tabs-container {
.scrolling-tabs {
margin-top: $gl-padding-8;
- margin-bottom: $gl-padding-8;
+ margin-bottom: $gl-padding-8 - $browserScrollbarSize;
+ padding-bottom: $browserScrollbarSize;
flex-wrap: wrap;
border-bottom: 0;
}
@@ -731,7 +732,7 @@
.fade-left,
.fade-right {
top: 0;
- height: 100%;
+ height: calc(100% - #{$browserScrollbarSize});
.fa {
top: 50%;
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
index 08d7e3b4fa2..65fe22bd8f4 100644
--- a/app/controllers/admin/impersonations_controller.rb
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -5,23 +5,12 @@ class Admin::ImpersonationsController < Admin::ApplicationController
before_action :authenticate_impersonator!
def destroy
- original_user = current_user
-
- warden.set_user(impersonator, scope: :user)
-
- Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{original_user.username}")
-
- session[:impersonator_id] = nil
-
+ original_user = stop_impersonation
redirect_to admin_user_path(original_user), status: :found
end
private
- def impersonator
- @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
- end
-
def authenticate_impersonator!
render_404 unless impersonator && impersonator.admin? && !impersonator.blocked?
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index b783c0e2a6f..e93be1c1ba2 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -2,6 +2,7 @@
class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create]
+ before_action :check_impersonation_availability, only: :impersonate
def index
@users = User.order_name_asc.filter(params[:filter])
@@ -227,4 +228,8 @@ class Admin::UsersController < Admin::ApplicationController
result[:status] == :success
end
+
+ def check_impersonation_availability
+ access_denied! unless Gitlab.config.gitlab.impersonation_enabled
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 9b40ffb26a2..65c1576d9d2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,11 +12,11 @@ class ApplicationController < ActionController::Base
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
+ include SessionlessAuthentication
# this can be removed after switching to rails 5
# https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
include InvalidUTF8ErrorHandler unless Gitlab.rails5?
- before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
@@ -28,6 +28,7 @@ class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
before_action :set_usage_stats_consent_flag
+ before_action :check_impersonation_availability
around_action :set_locale
@@ -153,13 +154,6 @@ class ApplicationController < ActionController::Base
end
end
- # This filter handles personal access tokens, and atom requests with rss tokens
- def authenticate_sessionless_user!
- user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user
-
- sessionless_sign_in(user) if user
- end
-
def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
@@ -426,25 +420,11 @@ class ApplicationController < ActionController::Base
Gitlab::I18n.with_user_locale(current_user, &block)
end
- def sessionless_sign_in(user)
- if user && can?(user, :log_in)
- # Notice we are passing store false, so the user is not
- # actually stored in the session and a token is needed
- # for every request. If you want the token to work as a
- # sign in token, you can simply remove store: false.
- sign_in(user, store: false, message: :sessionless_sign_in)
- end
- end
-
def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
- def sessionless_user?
- current_user && !session.keys.include?('warden.user.user.key')
- end
-
def peek_request?
request.path.start_with?('/-/peek')
end
@@ -483,4 +463,28 @@ class ApplicationController < ActionController::Base
.new(settings, current_user, application_setting_params)
.execute
end
+
+ def check_impersonation_availability
+ return unless session[:impersonator_id]
+
+ unless Gitlab.config.gitlab.impersonation_enabled
+ stop_impersonation
+ access_denied! _('Impersonation has been disabled')
+ end
+ end
+
+ def stop_impersonation
+ impersonated_user = current_user
+
+ Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}")
+
+ warden.set_user(impersonator, scope: :user)
+ session[:impersonator_id] = nil
+
+ impersonated_user
+ end
+
+ def impersonator
+ @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+ end
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 777b147e2dd..0319948a12f 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -6,6 +6,7 @@ module NotesActions
extend ActiveSupport::Concern
included do
+ prepend_before_action :normalize_create_params, only: [:create]
before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
@@ -247,6 +248,15 @@ module NotesActions
DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
end
+ # Avoids checking permissions in the wrong object - this ensures that the object we checked permissions for
+ # is the object we're actually creating a note in.
+ def normalize_create_params
+ params[:note].try do |note|
+ note[:noteable_id] = params[:target_id]
+ note[:noteable_type] = params[:target_type].classify
+ end
+ end
+
def note_project
strong_memoize(:note_project) do
next nil unless project
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
new file mode 100644
index 00000000000..590eefc6dab
--- /dev/null
+++ b/app/controllers/concerns/sessionless_authentication.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# == SessionlessAuthentication
+#
+# Controller concern to handle PAT and RSS token authentication methods
+#
+module SessionlessAuthentication
+ # This filter handles personal access tokens, and atom requests with rss tokens
+ def authenticate_sessionless_user!(request_format)
+ user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user(request_format)
+
+ sessionless_sign_in(user) if user
+ end
+
+ def sessionless_user?
+ current_user && !session.keys.include?('warden.user.user.key')
+ end
+
+ def sessionless_sign_in(user)
+ if user && can?(user, :log_in)
+ # Notice we are passing store false, so the user is not
+ # actually stored in the session and a token is needed
+ # for every request. If you want the token to work as a
+ # sign in token, you can simply remove store: false.
+ sign_in(user, store: false, message: :sessionless_sign_in)
+ end
+ end
+end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index e9686ed8d06..57e612d89d3 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -4,6 +4,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility
include RendersMemberAccess
+ prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
before_action :default_sorting
skip_cross_project_access_check :index, :starred
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index b82caf30a91..3fa582cf25b 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
before_action :authorize_read_project!, only: :index
+ before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
def index
@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
end
+ def authorize_read_group!
+ group_id = params[:group_id]
+
+ if group_id.present?
+ group = Group.find(group_id)
+ render_404 unless can?(current_user, :read_group, group)
+ end
+ end
+
def find_todos
@todos ||= TodosFinder.new(current_user, todo_params).execute
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 4ce9be44403..be2d9512c01 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -4,6 +4,9 @@ class DashboardController < Dashboard::ApplicationController
include IssuesAction
include MergeRequestsAction
+ prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) }
+ prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
+
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests]
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index a1ec144410b..6ea4758ec32 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -3,6 +3,7 @@
class GraphqlController < ApplicationController
# Unauthenticated users have access to the API for public data
skip_before_action :authenticate_user!
+ prepend_before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
before_action :check_graphql_feature_flag!
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 062c8c4e9e1..c5d8ac2ed77 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -9,6 +9,9 @@ class GroupsController < Groups::ApplicationController
respond_to :html
+ prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
+ prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
+
before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create]
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index b50f140dc80..ab4ca56bb49 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user!
before_action :add_gon_variables
- before_action :load_scopes, only: [:index, :create, :edit]
+ before_action :load_scopes, only: [:index, :create, :edit, :update]
helper_method :can?
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 84a2a461da7..8ba18aacc58 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -6,6 +6,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
include RendersCommits
+ prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :whitelist_query_limiting, except: :commits_root
before_action :require_non_empty_project
before_action :assign_ref_vars, except: :commits_root
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index e55065c5817..a10e159ea1e 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -13,10 +13,8 @@ class Projects::ImportsController < Projects::ApplicationController
end
def create
- @project.import_url = params[:project][:import_url]
-
- if @project.save
- @project.reload.import_schedule
+ if @project.update(safe_import_params)
+ @project.import_state.reload.schedule
end
redirect_to project_import_path(@project)
@@ -24,7 +22,7 @@ class Projects::ImportsController < Projects::ApplicationController
def show
if @project.import_finished?
- if continue_params
+ if continue_params&.key?(:to)
redirect_to continue_params[:to], notice: continue_params[:notice]
else
redirect_to project_path(@project), notice: finished_notice
@@ -67,4 +65,12 @@ class Projects::ImportsController < Projects::ApplicationController
redirect_to project_path(@project)
end
end
+
+ def import_params
+ params.require(:project).permit(:import_url)
+ end
+
+ def safe_import_params
+ import_params
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 308f666394c..c6ab6b4642e 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -9,10 +9,6 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar
include SpammableActions
- def self.authenticate_user_only_actions
- %i[new]
- end
-
def self.issue_except_actions
%i[index calendar new create bulk_update]
end
@@ -21,7 +17,10 @@ class Projects::IssuesController < Projects::ApplicationController
%i[index calendar]
end
- prepend_before_action :authenticate_user!, only: authenticate_user_only_actions
+ prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
+ prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
+ prepend_before_action :authenticate_new_issue!, only: [:new]
+ prepend_before_action :store_uri, only: [:new, :show]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
@@ -38,6 +37,8 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
+ before_action :set_suggested_issues_feature_flags, only: [:new]
+
respond_to :html
def index
@@ -230,16 +231,18 @@ class Projects::IssuesController < Projects::ApplicationController
] + [{ label_ids: [], assignee_ids: [] }]
end
- def authenticate_user!
+ def authenticate_new_issue!
return if current_user
notice = "Please sign in to create the new issue."
+ redirect_to new_user_session_path, notice: notice
+ end
+
+ def store_uri
if request.get? && !request.xhr?
store_location_for :user, request.fullpath
end
-
- redirect_to new_user_session_path, notice: notice
end
def serializer
@@ -263,4 +266,9 @@ class Projects::IssuesController < Projects::ApplicationController
# 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
end
+
+ def set_suggested_issues_feature_flags
+ push_frontend_feature_flag(:graphql)
+ push_frontend_feature_flag(:issue_suggestions)
+ end
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 20998c97730..8e68014a30d 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_milestone!
# Allow admin milestone
- before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels, :promote]
+ before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels]
+
+ # Allow to promote milestone
+ before_action :authorize_promote_milestone!, only: :promote
respond_to :html
@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def promote
promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
- flash[:notice] = flash_notice_for(promoted_milestone, project.group)
+ flash[:notice] = flash_notice_for(promoted_milestone, project_group)
respond_to do |format|
format.html do
@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
+ def project_group
+ strong_memoize(:project_group) do
+ project.group
+ end
+ end
+
def milestones
strong_memoize(:milestones) do
MilestonesFinder.new(search_params).execute
@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_milestone, @project)
end
+ def authorize_promote_milestone!
+ return render_404 unless can?(current_user, :admin_milestone, project_group)
+ end
+
def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
def search_params
- if request.format.json? && @project.group && can?(current_user, :read_group, @project.group)
- groups = @project.group.self_and_ancestors_ids
+ if request.format.json? && project_group && can?(current_user, :read_group, project_group)
+ groups = project_group.self_and_ancestors_ids
end
params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index ad2466a8588..6543711ecfa 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -8,6 +8,7 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
+ before_action :assign_options
before_action :assign_commit
def show
@@ -29,10 +30,13 @@ class Projects::NetworkController < Projects::ApplicationController
render
end
+ def assign_options
+ @options = params.permit(:filter_ref, :extended_sha1)
+ end
+
def assign_commit
- return if params[:extended_sha1].blank?
+ return if @options[:extended_sha1].blank?
- @options[:extended_sha1] = params[:extended_sha1]
@commit = @repo.commit(@options[:extended_sha1])
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index c8442ff3592..2b28670a49b 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -3,6 +3,8 @@
class Projects::TagsController < Projects::ApplicationController
include SortingHelper
+ prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
+
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 7f4a9f5151b..8bf93bfd68d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -7,6 +7,8 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown
include SendFileUpload
+ prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
+
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 5b70c69d7f4..8b040dc080e 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -14,6 +14,7 @@ class UsersController < ApplicationController
calendar_activities: true
skip_before_action :authenticate_user!
+ prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
new file mode 100644
index 00000000000..4ab3c13787a
--- /dev/null
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class IssuesResolver < BaseResolver
+ extend ActiveSupport::Concern
+
+ argument :search, GraphQL::STRING_TYPE,
+ required: false
+ argument :sort, Types::Sort,
+ required: false,
+ default_value: 'created_desc'
+
+ type Types::IssueType, null: true
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ # Will need to be be made group & namespace aware with
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/54520
+ args[:project_id] = project.id
+
+ IssuesFinder.new(context[:current_user], args).execute
+ end
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
new file mode 100644
index 00000000000..a8f2f7914a8
--- /dev/null
+++ b/app/graphql/types/issue_type.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Types
+ class IssueType < BaseObject
+ expose_permissions Types::PermissionTypes::Issue
+
+ graphql_name 'Issue'
+
+ present_using IssuePresenter
+
+ field :iid, GraphQL::ID_TYPE, null: false
+ field :title, GraphQL::STRING_TYPE, null: false
+ field :description, GraphQL::STRING_TYPE, null: true
+ field :state, GraphQL::STRING_TYPE, null: false
+
+ field :author, Types::UserType,
+ null: false,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do
+ authorize :read_user
+ end
+
+ field :assignees, Types::UserType.connection_type, null: true
+
+ field :labels, Types::LabelType.connection_type, null: true
+ field :milestone, Types::MilestoneType,
+ null: true,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do
+ authorize :read_milestone
+ end
+
+ field :due_date, Types::TimeType, null: true
+ field :confidential, GraphQL::BOOLEAN_TYPE, null: false
+ field :discussion_locked, GraphQL::BOOLEAN_TYPE,
+ null: false,
+ resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
+
+ field :upvotes, GraphQL::INT_TYPE, null: false
+ field :downvotes, GraphQL::INT_TYPE, null: false
+ field :user_notes_count, GraphQL::INT_TYPE, null: false
+ field :web_url, GraphQL::STRING_TYPE, null: false
+
+ field :closed_at, Types::TimeType, null: true
+
+ field :created_at, Types::TimeType, null: false
+ field :updated_at, Types::TimeType, null: false
+ end
+end
diff --git a/app/graphql/types/label_type.rb b/app/graphql/types/label_type.rb
new file mode 100644
index 00000000000..ccd466edc1a
--- /dev/null
+++ b/app/graphql/types/label_type.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class LabelType < BaseObject
+ graphql_name 'Label'
+
+ field :description, GraphQL::STRING_TYPE, null: true
+ field :title, GraphQL::STRING_TYPE, null: false
+ field :color, GraphQL::STRING_TYPE, null: false
+ field :text_color, GraphQL::STRING_TYPE, null: false
+ end
+end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
new file mode 100644
index 00000000000..af31b572c9a
--- /dev/null
+++ b/app/graphql/types/milestone_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ class MilestoneType < BaseObject
+ graphql_name 'Milestone'
+
+ field :description, GraphQL::STRING_TYPE, null: true
+ field :title, GraphQL::STRING_TYPE, null: false
+ field :state, GraphQL::STRING_TYPE, null: false
+
+ field :due_date, Types::TimeType, null: true
+ field :start_date, Types::TimeType, null: true
+
+ field :created_at, Types::TimeType, null: false
+ field :updated_at, Types::TimeType, null: false
+ end
+end
diff --git a/app/graphql/types/order.rb b/app/graphql/types/order.rb
new file mode 100644
index 00000000000..c5e1cc406b4
--- /dev/null
+++ b/app/graphql/types/order.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Types
+ class Types::Order < Types::BaseEnum
+ value "id", "Created at date"
+ value "updated_at", "Updated at date"
+ end
+end
diff --git a/app/graphql/types/permission_types/issue.rb b/app/graphql/types/permission_types/issue.rb
new file mode 100644
index 00000000000..199540c7d6d
--- /dev/null
+++ b/app/graphql/types/permission_types/issue.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Issue < BasePermissionType
+ description 'Check permissions for the current user on a issue'
+ graphql_name 'IssuePermissions'
+
+ abilities :read_issue, :admin_issue,
+ :update_issue, :create_note,
+ :reopen_issue
+ end
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 7b879608b34..050706f97be 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -73,6 +73,11 @@ module Types
authorize :read_merge_request
end
+ field :issues,
+ Types::IssueType.connection_type,
+ null: true,
+ resolver: Resolvers::IssuesResolver
+
field :pipelines,
Types::Ci::PipelineType.connection_type,
null: false,
diff --git a/app/graphql/types/sort.rb b/app/graphql/types/sort.rb
new file mode 100644
index 00000000000..1f756fdab69
--- /dev/null
+++ b/app/graphql/types/sort.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ class Types::Sort < Types::BaseEnum
+ value "updated_desc", "Updated at descending order"
+ value "updated_asc", "Updated at ascending order"
+ value "created_desc", "Created at descending order"
+ value "created_asc", "Created at ascending order"
+ end
+end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
new file mode 100644
index 00000000000..a13e65207df
--- /dev/null
+++ b/app/graphql/types/user_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class UserType < BaseObject
+ graphql_name 'User'
+
+ present_using UserPresenter
+
+ field :name, GraphQL::STRING_TYPE, null: false
+ field :username, GraphQL::STRING_TYPE, null: false
+ field :avatar_url, GraphQL::STRING_TYPE, null: false
+ field :web_url, GraphQL::STRING_TYPE, null: false
+ end
+end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 94a030d9d57..9666080092b 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -2,6 +2,7 @@
module MilestonesHelper
include EntityDateHelper
+ include Gitlab::Utils::StrongMemoize
def milestones_filter_path(opts = {})
if @project
@@ -243,4 +244,16 @@ module MilestonesHelper
dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end
end
+
+ def can_admin_project_milestones?
+ strong_memoize(:can_admin_project_milestones) do
+ can?(current_user, :admin_milestone, @project)
+ end
+ end
+
+ def can_admin_group_milestones?
+ strong_memoize(:can_admin_group_milestones) do
+ can?(current_user, :admin_milestone, @project.group)
+ end
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 78a11616d4c..e2879bfdcf1 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -37,13 +37,13 @@ module TreeHelper
# Using Rails `*_path` methods can be slow, especially when generating
# many paths, as with a repository tree that has thousands of items.
def fast_project_blob_path(project, blob_path)
- Addressable::URI.escape(
+ ActionDispatch::Journey::Router::Utils.escape_path(
File.join(relative_url_root, project.path_with_namespace, 'blob', blob_path)
)
end
def fast_project_tree_path(project, tree_path)
- Addressable::URI.escape(
+ ActionDispatch::Journey::Router::Utils.escape_path(
File.join(relative_url_root, project.path_with_namespace, 'tree', tree_path)
)
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 42b533ad772..bde9ca0cbf2 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -70,6 +70,10 @@ module UsersHelper
end
end
+ def impersonation_enabled?
+ Gitlab.config.gitlab.impersonation_enabled
+ end
+
private
def get_profile_tabs
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index d3284e90568..1b3c1f9a8a9 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -26,7 +26,7 @@ module Emails
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
end
- def note_snippet_email(recipient_id, note_id)
+ def note_project_snippet_email(recipient_id, note_id)
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 108874b75a6..da08214963f 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -15,6 +15,8 @@ module Ci
WRITE_LOCK_SLEEP = 0.01.seconds
WRITE_LOCK_TTL = 1.minute
+ FailedToPersistDataError = Class.new(StandardError)
+
# Note: The ordering of this enum is related to the precedence of persist store.
# The bottom item takes the higest precedence, and the top item takes the lowest precedence.
enum data_store: {
@@ -76,7 +78,7 @@ module Ci
raise ArgumentError, 'Offset is out of range' if offset > size || offset < 0
raise ArgumentError, 'Chunk size overflow' if CHUNK_SIZE < (offset + new_data.bytesize)
- in_lock(*lock_params) do # Write opetation is atomic
+ in_lock(*lock_params) do # Write operation is atomic
unsafe_set_data!(data.byteslice(0, offset) + new_data)
end
@@ -100,7 +102,7 @@ module Ci
end
def persist_data!
- in_lock(*lock_params) do # Write opetation is atomic
+ in_lock(*lock_params) do # Write operation is atomic
unsafe_persist_to!(self.class.persistable_store)
end
end
@@ -109,16 +111,19 @@ module Ci
def unsafe_persist_to!(new_store)
return if data_store == new_store.to_s
- raise ArgumentError, 'Can not persist empty data' unless size > 0
- old_store_class = self.class.get_store_class(data_store)
+ current_data = get_data
- get_data.tap do |the_data|
- self.raw_data = nil
- self.data_store = new_store
- unsafe_set_data!(the_data)
+ unless current_data&.bytesize.to_i == CHUNK_SIZE
+ raise FailedToPersistDataError, 'Data is not fullfilled in a bucket'
end
+ old_store_class = self.class.get_store_class(data_store)
+
+ self.raw_data = nil
+ self.data_store = new_store
+ unsafe_set_data!(current_data)
+
old_store_class.delete_data(self)
end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index bd0286ee3f9..8f8790585a3 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -51,6 +51,10 @@ module Clusters
ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
+
+ def ingress_service
+ cluster.kubeclient.get_service('ingress-nginx-ingress-controller', Gitlab::Kubernetes::Helm::NAMESPACE)
+ end
end
end
end
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index c66d5ce54db..c0aaa8dce20 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -6,9 +6,7 @@ module Clusters
VERSION = '0.1.3'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
- # This is required for helm version <= 2.10.x in order to support
- # Setting up CRDs
- ISTIO_CRDS = 'https://storage.googleapis.com/triggermesh-charts/istio-crds.yaml'.freeze
+ FETCH_IP_ADDRESS_DELAY = 30.seconds
self.table_name = 'clusters_applications_knative'
@@ -16,6 +14,16 @@ module Clusters
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
+ include AfterCommitQueue
+
+ state_machine :status do
+ before_transition any => [:installed] do |application|
+ application.run_after_commit do
+ ClusterWaitForIngressIpAddressWorker.perform_in(
+ FETCH_IP_ADDRESS_DELAY, application.name, application.id)
+ end
+ end
+ end
default_value_for :version, VERSION
@@ -36,19 +44,23 @@ module Clusters
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
- repository: REPOSITORY,
- preinstall: install_script
+ repository: REPOSITORY
)
end
- def client
- cluster.platform_kubernetes.kubeclient.knative_client
+ def schedule_status_update
+ return unless installed?
+ return if external_ip
+
+ ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
- private
+ def ingress_service
+ cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system')
+ end
- def install_script
- ["/usr/bin/kubectl apply -f #{ISTIO_CRDS}"]
+ def client
+ cluster.platform_kubernetes.kubeclient.knative_client
end
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 6e2adc76ec6..a8c9e54f00c 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 11
+ CACHE_COMMONMARK_VERSION = 12
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 7078496ff52..4a128dde5cd 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -8,6 +8,7 @@ class EnvironmentStatus
delegate :id, to: :environment
delegate :name, to: :environment
delegate :project, to: :environment
+ delegate :status, to: :deployment, allow_nil: true
delegate :deployed_at, to: :deployment, allow_nil: true
def self.for_merge_request(mr, user)
@@ -43,22 +44,6 @@ class EnvironmentStatus
.merge_request_diff_files.where(deleted_file: false)
end
- ##
- # Since frontend has not supported all statuses yet, BE has to
- # proxy some status to a supported status.
- def status
- return unless deployment
-
- case deployment.status
- when 'created'
- 'running'
- when 'canceled'
- 'failed'
- else
- deployment.status
- end
- end
-
private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 6f1beede6f9..a3029a54604 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -313,7 +313,8 @@ class MergeRequestDiff < ActiveRecord::Base
# merge_request_diff_commits.reload is preferred way to reload associated
# objects but it returns cached result for some reason in this case
- commits = merge_request_diff_commits(true)
+ # we can circumvent that by specifying that we need an uncached reload
+ commits = self.class.uncached { merge_request_diff_commits.reload }
self.commits_count = commits.size
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 592efb714f3..a6ae4f58ac4 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -324,7 +324,7 @@ class Note < ActiveRecord::Base
end
def to_ability_name
- for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
+ for_snippet? ? noteable.class.name.underscore : noteable_type.underscore
end
def can_be_discussion_note?
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index 8ef74539209..7351674201e 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -1,16 +1,12 @@
# frozen_string_literal: true
class PoolRepository < ActiveRecord::Base
- POOL_PREFIX = '@pools'
-
belongs_to :shard
validates :shard, presence: true
- # For now, only pool repositories are tracked in the database. However, we may
- # want to add other repository types in the future
- self.table_name = 'repositories'
+ has_many :member_projects, class_name: 'Project'
- has_many :pool_member_projects, class_name: 'Project', foreign_key: :pool_repository_id
+ after_create :correct_disk_path
def shard_name
shard&.name
@@ -19,4 +15,15 @@ class PoolRepository < ActiveRecord::Base
def shard_name=(name)
self.shard = Shard.by_name(name)
end
+
+ private
+
+ def correct_disk_path
+ update!(disk_path: storage.disk_path)
+ end
+
+ def storage
+ Storage::HashedProject
+ .new(self, prefix: Storage::HashedProject::POOL_PATH_PREFIX)
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 4d1917b9ab2..185fd76cbbc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -30,6 +30,7 @@ class Project < ActiveRecord::Base
include FeatureGate
include OptionallySearch
include FromUnion
+ include IgnorableColumn
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -55,6 +56,8 @@ class Project < ActiveRecord::Base
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
+ ignore_column :import_status, :import_jid, :import_error
+
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
@@ -63,6 +66,12 @@ class Project < ActiveRecord::Base
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
+ delegate :scheduled?, :started?, :in_progress?,
+ :failed?, :finished?,
+ prefix: :import, to: :import_state, allow_nil: true
+
+ delegate :no_import?, to: :import_state, allow_nil: true
+
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :resolve_outdated_diff_discussions, false
@@ -88,9 +97,6 @@ class Project < ActiveRecord::Base
after_create :create_project_feature, unless: :project_feature
- after_create -> { SiteStatistic.track(STATISTICS_ATTRIBUTE) }
- before_destroy -> { SiteStatistic.untrack(STATISTICS_ATTRIBUTE) }
-
after_create :create_ci_cd_settings,
unless: :ci_cd_settings,
if: proc { ProjectCiCdSetting.available? }
@@ -454,8 +460,8 @@ class Project < ActiveRecord::Base
scope :excluding_project, ->(project) { where.not(id: project) }
- scope :joins_import_state, -> { joins("LEFT JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
- scope :import_started, -> { joins_import_state.where("import_state.status = 'started' OR projects.import_status = 'started'") }
+ # We require an alias to the project_mirror_data_table in order to use import_state in our queries
+ scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
scope :for_group, -> (group) { where(group: group) }
class << self
@@ -631,6 +637,14 @@ class Project < ActiveRecord::Base
id && persisted?
end
+ def import_status
+ import_state&.status || 'none'
+ end
+
+ def human_import_status_name
+ import_state&.human_status_name || 'none'
+ end
+
def add_import_job
job_id =
if forked?
@@ -662,7 +676,7 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id)
end
- update(import_error: nil)
+ import_state.update(last_error: nil)
remove_import_data
end
@@ -724,130 +738,6 @@ class Project < ActiveRecord::Base
import_url.present?
end
- def imported?
- import_finished?
- end
-
- def import_in_progress?
- import_started? || import_scheduled?
- end
-
- def import_state_args
- {
- status: self[:import_status],
- jid: self[:import_jid],
- last_error: self[:import_error]
- }
- end
-
- def ensure_import_state(force: false)
- return if !force && (self[:import_status] == 'none' || self[:import_status].nil?)
- return unless import_state.nil?
-
- if persisted?
- create_import_state(import_state_args)
-
- update_column(:import_status, 'none')
- else
- build_import_state(import_state_args)
-
- self[:import_status] = 'none'
- end
- end
-
- def human_import_status_name
- ensure_import_state
-
- import_state.human_status_name
- end
-
- def import_schedule
- ensure_import_state(force: true)
-
- import_state.schedule
- end
-
- def force_import_start
- ensure_import_state(force: true)
-
- import_state.force_start
- end
-
- def import_start
- ensure_import_state(force: true)
-
- import_state.start
- end
-
- def import_fail
- ensure_import_state(force: true)
-
- import_state.fail_op
- end
-
- def import_finish
- ensure_import_state(force: true)
-
- import_state.finish
- end
-
- def import_jid=(new_jid)
- ensure_import_state(force: true)
-
- import_state.jid = new_jid
- end
-
- def import_jid
- ensure_import_state
-
- import_state&.jid
- end
-
- def import_error=(new_error)
- ensure_import_state(force: true)
-
- import_state.last_error = new_error
- end
-
- def import_error
- ensure_import_state
-
- import_state&.last_error
- end
-
- def import_status=(new_status)
- ensure_import_state(force: true)
-
- import_state.status = new_status
- end
-
- def import_status
- ensure_import_state
-
- import_state&.status || 'none'
- end
-
- def no_import?
- import_status == 'none'
- end
-
- def import_started?
- # import? does SQL work so only run it if it looks like there's an import running
- import_status == 'started' && import?
- end
-
- def import_scheduled?
- import_status == 'scheduled'
- end
-
- def import_failed?
- import_status == 'failed'
- end
-
- def import_finished?
- import_status == 'finished'
- end
-
def safe_import_url
Gitlab::UrlSanitizer.new(import_url).masked_url
end
@@ -988,9 +878,9 @@ class Project < ActiveRecord::Base
end
def readme_url
- readme = repository.readme
- if readme
- Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme.path))
+ readme_path = repository.readme_path
+ if readme_path
+ Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme_path))
end
end
@@ -1394,7 +1284,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
- repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}", shell: false)
+ repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
@@ -1646,8 +1536,8 @@ class Project < ActiveRecord::Base
def after_import
repository.after_import
wiki.repository.after_import
- import_finish
- remove_import_jid
+ import_state.finish
+ import_state.remove_jid
update_project_counter_caches
after_create_default_branch
refresh_markdown_cache!
@@ -1687,32 +1577,11 @@ class Project < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
- def remove_import_jid
- return unless import_jid
-
- Gitlab::SidekiqStatus.unset(import_jid)
-
- import_state.update_column(:jid, nil)
- end
-
# Lazy loading of the `pipeline_status` attribute
def pipeline_status
@pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
end
- def mark_import_as_failed(error_message)
- original_errors = errors.dup
- sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
-
- import_fail
-
- import_state.update_column(:last_error, sanitized_message)
- rescue ActiveRecord::ActiveRecordError => e
- Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
- ensure
- @errors = original_errors
- end
-
def add_export_job(current_user:, after_export_strategy: nil, params: {})
job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
@@ -1989,17 +1858,6 @@ class Project < ActiveRecord::Base
Gitlab::ReferenceCounter.new(gl_repository(is_wiki: wiki))
end
- # Refreshes the expiration time of the associated import job ID.
- #
- # This method can be used by asynchronous importers to refresh the status,
- # preventing the StuckImportJobsWorker from marking the import as failed.
- def refresh_import_jid_expiration
- return unless import_jid
-
- Gitlab::SidekiqStatus
- .set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
- end
-
def badges
return project_badges unless group
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 7126bb66d80..488f0cb5971 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -69,4 +69,33 @@ class ProjectImportState < ActiveRecord::Base
ensure
@errors = original_errors
end
+
+ alias_method :no_import?, :none?
+
+ def in_progress?
+ scheduled? || started?
+ end
+
+ def started?
+ # import? does SQL work so only run it if it looks like there's an import running
+ status == 'started' && project.import?
+ end
+
+ def remove_jid
+ return unless jid
+
+ Gitlab::SidekiqStatus.unset(jid)
+
+ update_column(:jid, nil)
+ end
+
+ # Refreshes the expiration time of the associated import job ID.
+ #
+ # This method can be used by asynchronous importers to refresh the status,
+ # preventing the StuckImportJobsWorker from marking the import as failed.
+ def refresh_jid_expiration
+ return unless jid
+
+ Gitlab::SidekiqStatus.set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ end
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 211e5c3fcbf..60cb2d380d5 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
end
def prometheus_client
- RestClient::Resource.new(api_url) if api_url && manual_configuration? && active?
+ RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
end
def prometheus_available?
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index a3415a4a14c..b7b4d0f1be9 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -15,8 +15,6 @@ class RemoteMirror < ActiveRecord::Base
insecure_mode: true,
algorithm: 'aes-256-cbc'
- default_value_for :only_protected_branches, true
-
belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a77fa8f2ce7..35dd120856d 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -35,7 +35,7 @@ class Repository
#
# For example, for entry `:commit_count` there's a method called `commit_count` which
# stores its data in the `commit_count` cache key.
- CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
+ CACHED_METHODS = %i(size commit_count rendered_readme readme_path contribution_guide
changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref has_visible_content?
@@ -48,7 +48,7 @@ class Repository
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
# the corresponding methods to call for refreshing caches.
METHOD_CACHES_FOR_FILE_TYPES = {
- readme: :rendered_readme,
+ readme: %i(rendered_readme readme_path),
changelog: :changelog,
license: %i(license_blob license_key license),
contributing: :contribution_guide,
@@ -259,7 +259,7 @@ class Repository
next if kept_around?(sha)
# This will still fail if the file is corrupted (e.g. 0 bytes)
- raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
+ raw_repository.write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
@@ -591,6 +591,11 @@ class Repository
head_tree&.readme
end
+ def readme_path
+ readme&.path
+ end
+ cache_method :readme_path
+
def rendered_readme
return unless readme
diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb
deleted file mode 100644
index 3a7912ed53a..00000000000
--- a/app/models/site_statistic.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-class SiteStatistic < ActiveRecord::Base
- # prevents the creation of multiple rows
- default_value_for :id, 1
-
- COUNTER_ATTRIBUTES = %w(repositories_count).freeze
- REQUIRED_SCHEMA_VERSION = 20180629153018
-
- # Tracks specific attribute
- #
- # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES
- def self.track(raw_attribute)
- with_statistics_available(raw_attribute) do |attribute|
- SiteStatistic.update_all(["#{attribute} = #{attribute}+1"])
- end
- end
-
- # Untracks specific attribute
- #
- # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES
- def self.untrack(raw_attribute)
- with_statistics_available(raw_attribute) do |attribute|
- SiteStatistic.update_all(["#{attribute} = #{attribute}-1 WHERE #{attribute} > 0"])
- end
- end
-
- # Wrapper for track/untrack operations with basic validations and enforced requirements
- #
- # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES
- # @yield [String] attribute quoted to be used inside SQL / Arel query
- def self.with_statistics_available(raw_attribute)
- unless raw_attribute.in?(COUNTER_ATTRIBUTES)
- raise ArgumentError, "Invalid attribute: '#{raw_attribute}' to '#{caller_locations(1, 1)[0].label}' method. " \
- "Valid attributes are: #{COUNTER_ATTRIBUTES.join(', ')}"
- end
-
- return unless available?
-
- self.fetch # make sure record exists
-
- attribute = self.connection.quote_column_name(raw_attribute)
-
- # will be running on its own transaction context
- yield(attribute)
- end
-
- # Returns a site statistic record with tracked information
- #
- # @return [SiteStatistic] record with tracked information
- def self.fetch
- transaction(requires_new: true) do
- SiteStatistic.first_or_create!
- end
- rescue ActiveRecord::RecordNotUnique
- retry
- end
-
- # Return whether required schema change is available
- #
- # This is needed in order to degrade gracefully when testing schema migrations
- #
- # @return [Boolean] whether schema is available
- def self.available?
- @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION
- end
-
- # Resets cached column information
- #
- # This is called during schema migration specs, in order to reset internal cache state
- def self.reset_column_information
- @available_flag = nil
-
- super
- end
-end
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 90710f73fd3..911fb7e9ce9 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -5,17 +5,19 @@ module Storage
attr_accessor :project
delegate :gitlab_shell, :repository_storage, to: :project
- ROOT_PATH_PREFIX = '@hashed'.freeze
+ REPOSITORY_PATH_PREFIX = '@hashed'
+ POOL_PATH_PREFIX = '@pools'
- def initialize(project)
+ def initialize(project, prefix: REPOSITORY_PATH_PREFIX)
@project = project
+ @prefix = prefix
end
# Base directory
#
# @return [String] directory where repository is stored
def base_dir
- "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}" if disk_hash
+ "#{@prefix}/#{disk_hash[0..1]}/#{disk_hash[2..3]}" if disk_hash
end
# Disk path is used to build repository and project's wiki path on disk
diff --git a/app/models/user.rb b/app/models/user.rb
index 01eba7e0426..dbd754dd25a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -363,7 +363,7 @@ class User < ActiveRecord::Base
from_users = from_users.confirmed if confirmed
from_emails = joins(:emails).where(emails: { email: emails })
- from_emails = from_emails.confirmed if confirmed
+ from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed
items = [from_users, from_emails]
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 7769c3d71c0..b1d6d461928 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -85,6 +85,12 @@ class WikiPage
alias_method :to_param, :slug
+ def human_title
+ return 'Home' if title == 'home'
+
+ title
+ end
+
# The formatted title of this page.
def title
if @attributes[:title]
diff --git a/app/policies/commit_policy.rb b/app/policies/commit_policy.rb
index 67e9bc12804..4d4f0ba9267 100644
--- a/app/policies/commit_policy.rb
+++ b/app/policies/commit_policy.rb
@@ -2,4 +2,6 @@
class CommitPolicy < BasePolicy
delegate { @subject.project }
+
+ rule { can?(:download_code) }.enable :read_commit
end
diff --git a/app/policies/milestone_policy.rb b/app/policies/milestone_policy.rb
new file mode 100644
index 00000000000..ac4f5b08504
--- /dev/null
+++ b/app/policies/milestone_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class MilestonePolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index bbc2b48b856..f22843b6463 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -9,8 +9,17 @@ class NotePolicy < BasePolicy
condition(:editable, scope: :subject) { @subject.editable? }
+ condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") }
+
rule { ~editable }.prevent :admin_note
+ # If user can't read the issue/MR/etc then they should not be allowed to do anything to their own notes
+ rule { ~can_read_noteable }.policy do
+ prevent :read_note
+ prevent :admin_note
+ prevent :resolve_note
+ end
+
rule { is_author }.policy do
enable :read_note
enable :admin_note
diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb
new file mode 100644
index 00000000000..c12a202efbc
--- /dev/null
+++ b/app/presenters/issue_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class IssuePresenter < Gitlab::View::Presenter::Delegated
+ presents :issue
+
+ def web_url
+ Gitlab::UrlBuilder.build(issue)
+ end
+end
diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb
new file mode 100644
index 00000000000..14ef53e9ec8
--- /dev/null
+++ b/app/presenters/user_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class UserPresenter < Gitlab::View::Presenter::Delegated
+ presents :user
+
+ def web_url
+ Gitlab::Routing.url_helpers.user_url(user)
+ end
+end
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index 2a337918d21..40aa9250885 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -6,6 +6,7 @@ class AccessTokenValidationService
EXPIRED = :expired
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
+ IMPERSONATION_DISABLED = :impersonation_disabled
attr_reader :token, :request
@@ -24,6 +25,11 @@ class AccessTokenValidationService
elsif !self.include_any_scope?(scopes)
return INSUFFICIENT_SCOPE
+ elsif token.respond_to?(:impersonation) &&
+ token.impersonation &&
+ !Gitlab.config.gitlab.impersonation_enabled
+ return IMPERSONATION_DISABLED
+
else
return VALID
end
diff --git a/app/services/ci/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
new file mode 100644
index 00000000000..a1dd00721b5
--- /dev/null
+++ b/app/services/ci/archive_trace_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Ci
+ class ArchiveTraceService
+ def execute(job)
+ job.trace.archive!
+ rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
+ # It's already archived, thus we can safely ignore this exception.
+ rescue => e
+ # Tracks this error with application logs, Sentry, and Prometheus.
+ # If `archive!` keeps failing for over a week, that could incur data loss.
+ # (See more https://docs.gitlab.com/ee/administration/job_traces.html#new-live-trace-architecture)
+ # In order to avoid interrupting the system, we do not raise an exception here.
+ archive_error(e, job)
+ end
+
+ private
+
+ def failed_archive_counter
+ @failed_archive_counter ||=
+ Gitlab::Metrics.counter(:job_trace_archive_failed_total,
+ "Counter of failed attempts of trace archiving")
+ end
+
+ def archive_error(error, job)
+ failed_archive_counter.increment
+ Rails.logger.error "Failed to archive trace. id: #{job.id} message: #{error.message}"
+
+ Gitlab::Sentry
+ .track_exception(error,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
+ extra: { job_id: job.id })
+ end
+ end
+end
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index 270a8eb24f4..e86ca8cf1d0 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -11,6 +11,25 @@ module Clusters
protected
+ def log_error(error)
+ meta = {
+ exception: error.class.name,
+ error_code: error.respond_to?(:error_code) ? error.error_code : nil,
+ service: self.class.name,
+ app_id: app.id,
+ project_ids: app.cluster.project_ids,
+ group_ids: app.cluster.group_ids,
+ message: error.message
+ }
+
+ logger.error(meta)
+ Gitlab::Sentry.track_acceptable_exception(error, extra: meta)
+ end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
+
def cluster
app.cluster
end
diff --git a/app/services/clusters/applications/check_ingress_ip_address_service.rb b/app/services/clusters/applications/check_ingress_ip_address_service.rb
index f32e73e8b1c..0ec06e776a7 100644
--- a/app/services/clusters/applications/check_ingress_ip_address_service.rb
+++ b/app/services/clusters/applications/check_ingress_ip_address_service.rb
@@ -30,7 +30,7 @@ module Clusters
def service
strong_memoize(:ingress_service) do
- kubeclient.get_service('ingress-nginx-ingress-controller', Gitlab::Kubernetes::Helm::NAMESPACE)
+ app.ingress_service
end
end
end
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index ca0f7b30053..21ec26ea233 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -15,8 +15,7 @@ module Clusters
check_timeout
end
rescue Kubeclient::HttpError => e
- Rails.logger.error("Kubernetes error: #{e.error_code} #{e.message}")
- Gitlab::Sentry.track_acceptable_exception(e, extra: { scope: 'kubernetes', app_id: app.id })
+ log_error(e)
app.make_errored!("Kubernetes error: #{e.error_code}") unless app.errored?
end
@@ -29,17 +28,13 @@ module Clusters
end
def on_failed
- app.make_errored!('Installation failed')
- ensure
- remove_installation_pod
+ app.make_errored!("Installation failed. Check pod logs for #{install_command.pod_name} for more details.")
end
def check_timeout
if timeouted?
begin
- app.make_errored!('Installation timed out')
- ensure
- remove_installation_pod
+ app.make_errored!("Installation timed out. Check pod logs for #{install_command.pod_name} for more details.")
end
else
ClusterWaitForAppInstallationWorker.perform_in(
@@ -53,9 +48,6 @@ module Clusters
def remove_installation_pod
helm_api.delete_pod!(install_command.pod_name)
- rescue => e
- Rails.logger.error("Kubernetes error: #{e.class.name} #{e.message}")
- # no-op
end
def installation_phase
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index f4385748c43..5a65dc4ef59 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -13,12 +13,10 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError => e
- Rails.logger.error("Kubernetes error: #{e.error_code} #{e.message}")
- Gitlab::Sentry.track_acceptable_exception(e, extra: { scope: 'kubernetes', app_id: app.id })
+ log_error(e)
app.make_errored!("Kubernetes error: #{e.error_code}")
rescue StandardError => e
- Rails.logger.error "Can't start installation process: #{e.class.name} #{e.message}"
- Gitlab::Sentry.track_acceptable_exception(e, extra: { scope: 'kubernetes', app_id: app.id })
+ log_error(e)
app.make_errored!("Can't start installation process.")
end
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index c9d3ee31d82..927634c2159 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -8,6 +8,7 @@ module Files
transformer = Lfs::FileTransformer.new(project, @branch_name)
actions = actions_after_lfs_transformation(transformer, params[:actions])
+ actions = transform_move_actions(actions)
commit_actions!(actions)
end
@@ -26,6 +27,16 @@ module Files
end
end
+ # When moving a file, `content: nil` means "use the contents of the previous
+ # file", while `content: ''` means "move the file and set it to empty"
+ def transform_move_actions(actions)
+ actions.map do |action|
+ action[:infer_content] = true if action[:content].nil?
+
+ action
+ end
+ end
+
def commit_actions!(actions)
repository.multi_action(
current_user,
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 20bfe5af7a1..481de34b977 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -148,7 +148,7 @@ module Projects
Rails.logger.error(log_message)
if @project
- @project.mark_import_as_failed(message) if @project.persisted? && @project.import?
+ @project.import_state.mark_as_failed(message) if @project.persisted? && @project.import?
end
@project
@@ -181,7 +181,7 @@ module Projects
def import_schedule
if @project.errors.empty?
- @project.import_schedule if @project.import? && !@project.bare_repository_import?
+ @project.import_state.schedule if @project.import? && !@project.bare_repository_import?
else
fail(error: @project.errors.full_messages.join(', '))
end
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index bfbc16d37a0..a733f420d11 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -8,7 +8,7 @@
%span.cred (Admin)
.float-right
- - if @user != current_user && @user.can?(:log_in)
+ - if impersonation_enabled? && @user != current_user && @user.can?(:log_in)
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do
%i.fa.fa-pencil-square-o
diff --git a/app/views/devise/mailer/email_changed.html.haml b/app/views/devise/mailer/email_changed.html.haml
new file mode 100644
index 00000000000..5398430fdfd
--- /dev/null
+++ b/app/views/devise/mailer/email_changed.html.haml
@@ -0,0 +1,12 @@
+= email_default_heading("Hello, #{@resource.name}!")
+
+- if @resource.try(:unconfirmed_email?)
+ %p
+ We're contacting you to notify you that your email is being changed to #{@resource.reload.unconfirmed_email}.
+- else
+ %p
+ We're contacting you to notify you that your email has been changed to #{@resource.email}.
+
+%p
+ If you did not initiate this change, please contact your administrator
+ immediately.
diff --git a/app/views/devise/mailer/email_changed.text.erb b/app/views/devise/mailer/email_changed.text.erb
new file mode 100644
index 00000000000..18137389e7b
--- /dev/null
+++ b/app/views/devise/mailer/email_changed.text.erb
@@ -0,0 +1,10 @@
+Hello, <%= @resource.name %>!
+
+<% if @resource.try(:unconfirmed_email?) %>
+We're contacting you to notify you that your email is being changed to <%= @resource.reload.unconfirmed_email %>.
+<% else %>
+We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
+<% end %>
+
+If you did not initiate this change, please contact your administrator
+immediately.
diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml
index 836981fc6fd..586b0f6ebfa 100644
--- a/app/views/groups/labels/edit.html.haml
+++ b/app/views/groups/labels/edit.html.haml
@@ -1,4 +1,6 @@
-- page_title 'Edit', @label.name, 'Labels'
+- add_to_breadcrumbs _("Labels"), group_labels_path(@group)
+- breadcrumb_title _("Edit")
+- page_title "Edit", @label.name, _("Labels")
%h3.page-title
Edit Label
diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml
index 538c353cf2d..bb0b8d2b94d 100644
--- a/app/views/groups/labels/new.html.haml
+++ b/app/views/groups/labels/new.html.haml
@@ -1,5 +1,6 @@
-- breadcrumb_title "Labels"
-- page_title 'New Label'
+- add_to_breadcrumbs _("Labels"), group_labels_path(@group)
+- breadcrumb_title _("New")
+- page_title _("New Label")
%h3.page-title
New Label
diff --git a/app/views/groups/milestones/edit.html.haml b/app/views/groups/milestones/edit.html.haml
index 5f6d7d209d0..c703d5f7f93 100644
--- a/app/views/groups/milestones/edit.html.haml
+++ b/app/views/groups/milestones/edit.html.haml
@@ -1,7 +1,10 @@
-- page_title "Milestones"
+- breadcrumb_title _("Edit")
+- page_title _("Milestones")
+
- render "header_title"
%h3.page-title
Edit Milestone
+%hr
= render "form"
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index d758e314d41..248cb3b0ba5 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -1,7 +1,12 @@
-- breadcrumb_title "Milestones"
-- page_title "Milestones"
+- @no_container = true
+- add_to_breadcrumbs _("Milestones"), group_milestones_path(@group)
+- breadcrumb_title _("New")
+- page_title _("Milestones"), @milestone.name, _("Milestones")
-%h3.page-title
- New Milestone
+%div{ class: container_class }
+ %h3.page-title
+ New Milestone
-= render "form"
+ %hr
+
+ = render "form"
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 3b1b5e55302..2336e1e83f9 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -37,11 +37,12 @@
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
+ - case project.import_status
+ - when 'finished'
%span
%i.fa.fa-check
= _('done')
- - elsif project.import_status == 'started'
+ - when 'started'
%i.fa.fa-spinner.fa-spin
= _('started')
- else
diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml
index 56d4f2ba881..ef69197e453 100644
--- a/app/views/import/bitbucket_server/status.html.haml
+++ b/app/views/import/bitbucket_server/status.html.haml
@@ -38,9 +38,10 @@
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
+ - case project.import_status
+ - when 'finished'
= icon('check', text: 'Done')
- - elsif project.import_status == 'started'
+ - when 'started'
= icon('spin', text: 'started')
- else
= project.human_import_status_name
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 830d141ebea..eca67582d6f 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -34,11 +34,12 @@
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
+ - case project.import_status
+ - when 'finished'
%span
%i.fa.fa-check
= _("done")
- - elsif project.import_status == 'started'
+ - when 'started'
%i.fa.fa-spinner.fa-spin
= _("started")
- else
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index b7bfbae5edf..a5fa12fe7df 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -30,11 +30,12 @@
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
+ - case project.import_status
+ - when 'finished'
%span
%i.fa.fa-check
= _('done')
- - elsif project.import_status == 'started'
+ - when 'started'
%i.fa.fa-spinner.fa-spin
= _('started')
- else
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 347e2820f94..f322b7a956a 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -39,11 +39,12 @@
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
+ - case project.import_status
+ - when 'finished'
%span
%i.fa.fa-check
= _("done")
- - elsif project.import_status == 'started'
+ - when 'started'
%i.fa.fa-spinner.fa-spin
= _("started")
- else
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 474ef25cef7..b7d69539eb7 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -71,7 +71,7 @@
= link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: _('Stop impersonation'), aria: { label: _('Stop impersonation') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- if header_link?(:sign_in)
- %li.nav-item.m-auto
+ %li.nav-item
%div
- sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
= link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index e134f416c70..5cb8aebadb3 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -7,15 +7,14 @@
- if @group&.persisted?
- create_group_project = can?(current_user, :create_projects, @group)
- create_group_subgroup = can?(current_user, :create_subgroup, @group)
+
- if create_group_project || create_group_subgroup
%li.dropdown-bold-header
= _('This group')
- if create_group_project
- %li.header-new-group-project
- = link_to _('New project'), new_project_path(namespace_id: @group.id)
+ %li= link_to _('New project'), new_project_path(namespace_id: @group.id)
- if create_group_subgroup
- %li
- = link_to _('New subgroup'), new_group_path(parent_id: @group.id)
+ %li= link_to _('New subgroup'), new_group_path(parent_id: @group.id)
%li.divider
%li.dropdown-bold-header GitLab
@@ -23,25 +22,20 @@
- create_project_issue = show_new_issue_link?(@project)
- merge_project = merge_request_source_project_for_project(@project)
- create_project_snippet = can?(current_user, :create_project_snippet, @project)
+
- if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header
= _('This project')
- if create_project_issue
- %li
- = link_to _('New issue'), new_project_issue_path(@project)
+ %li= link_to _('New issue'), new_project_issue_path(@project)
- if merge_project
- %li
- = link_to _('New merge request'), project_new_merge_request_path(merge_project)
+ %li= link_to _('New merge request'), project_new_merge_request_path(merge_project)
- if create_project_snippet
- %li.header-new-project-snippet
- = link_to _('New snippet'), new_project_snippet_path(@project)
+ %li= link_to _('New snippet'), new_project_snippet_path(@project)
%li.divider
%li.dropdown-bold-header GitLab
- if current_user.can_create_project?
- %li
- = link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
+ %li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group?
- %li
- = link_to _('New group'), new_group_path
- %li
- = link_to _('New snippet'), new_snippet_path
+ %li= link_to _('New group'), new_group_path
+ %li= link_to _('New snippet'), new_snippet_path
diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_project_snippet_email.html.haml
index 5e69f01a486..5e69f01a486 100644
--- a/app/views/notify/note_snippet_email.html.haml
+++ b/app/views/notify/note_project_snippet_email.html.haml
diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_project_snippet_email.text.erb
index 413d9e6e9ac..413d9e6e9ac 100644
--- a/app/views/notify/note_snippet_email.text.erb
+++ b/app/views/notify/note_project_snippet_email.text.erb
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 8181ee9eea1..af8887b0c39 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -13,7 +13,7 @@
.form-group.row
= label_tag :branch_name, nil, class: 'col-form-label col-sm-2'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name'
+ = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name monospace'
.form-text.text-muted.text-danger.js-branch-name-error
.form-group.row
= label_tag :ref, 'Create from', class: 'col-form-label col-sm-2'
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 541ae905246..79e32949db9 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -13,7 +13,7 @@
= render "ci_menu"
- else
.block-connector
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, is_commit: true
.limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index c6789e32dbe..1a74b120c26 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -8,62 +8,50 @@
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- link = commit_path(project, commit, merge_request: merge_request)
-- cache_key = [project.full_path,
- ref,
- commit.id,
- Gitlab::CurrentSettings.current_application_settings,
- @path.presence,
- current_controller?(:commits),
- merge_request&.iid,
- view_details,
- commit.status(ref),
- I18n.locale].compact
-
-= cache(cache_key, expires_in: 1.day) do
- %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
-
- .avatar-cell.d-none.d-sm-block
- = author_avatar(commit, size: 36, has_tooltip: false)
-
- .commit-detail.flex-list
- .commit-content.qa-commit-content
- - if view_details && merge_request
- = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- - else
- = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
- %span.commit-row-message.d-block.d-sm-none
- &middot;
- = commit.short_id
- - if commit.status(ref)
- .d-block.d-sm-none
- = render_commit_status(commit, ref: ref)
- - if commit.description?
- %button.text-expander.js-toggle-button
- = sprite_icon('ellipsis_h', size: 12)
+%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
+
+ .avatar-cell.d-none.d-sm-block
+ = author_avatar(commit, size: 36, has_tooltip: false)
+
+ .commit-detail.flex-list
+ .commit-content.qa-commit-content
+ - if view_details && merge_request
+ = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
+ - else
+ = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
+ %span.commit-row-message.d-block.d-sm-none
+ &middot;
+ = commit.short_id
+ - if commit.status(ref)
+ .d-block.d-sm-none
+ = render_commit_status(commit, ref: ref)
+ - if commit.description?
+ %button.text-expander.js-toggle-button
+ = sprite_icon('ellipsis_h', size: 12)
- .committer
- - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- - commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- - commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
- #{ commit_text.html_safe }
+ .committer
+ - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
+ - commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
+ - commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
+ #{ commit_text.html_safe }
- - if commit.description?
- %pre.commit-row-description.js-toggle-content.append-bottom-8
- = preserve(markdown_field(commit, :description))
+ - if commit.description?
+ %pre.commit-row-description.js-toggle-content.append-bottom-8
+ = preserve(markdown_field(commit, :description))
- .commit-actions.flex-row.d-none.d-sm-flex
- - if request.xhr?
- = render partial: 'projects/commit/signature', object: commit.signature
- - else
- = render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
+ .commit-actions.flex-row.d-none.d-sm-flex
+ - if request.xhr?
+ = render partial: 'projects/commit/signature', object: commit.signature
+ - else
+ = render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- - if commit.status(ref)
- = render_commit_status(commit, ref: ref)
+ - if commit.status(ref)
+ = render_commit_status(commit, ref: ref)
- .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
+ .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
- .commit-sha-group
- .label.label-monospace
- = commit.short_id
- = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
- = link_to_browse_code(project, commit)
+ .commit-sha-group
+ .label.label-monospace
+ = commit.short_id
+ = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 9de3c2db6e7..cc2d0d3b2d8 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,9 +2,9 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
-- merge_request = local_assigns.fetch(:merge_request, false)
+- is_commit = local_assigns.fetch(:is_commit, false)
-.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) }
+.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.files-changed-inner
.inline-parallel-buttons.d-none.d-sm-none.d-md-block
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
@@ -25,4 +25,4 @@
= render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment }
+ = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, is_commit: is_commit }
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 1f90acaabcc..5565ae1d98b 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,10 +1,11 @@
- environment = local_assigns.fetch(:environment, nil)
+- is_commit = local_assigns.fetch(:is_commit, false)
- file_hash = hexdigest(diff_file.file_path)
- image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
- image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
- .js-file-title.file-title-flex-parent
+ .js-file-title.file-title-flex-parent{ class: is_commit ? "is-commit" : "" }
.file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 996c7b1b960..82f035f24da 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -1,6 +1,6 @@
- page_title "Find File", @ref
-.file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) }
+.file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, format: :json))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) }
.nav-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'find_file', path: @path
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 1c50cfbde85..bd0ab2c19f2 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -10,7 +10,7 @@
.card-body
%pre
:preserve
- #{h(@project.import_error)}
+ #{h(@project.import_state.last_error)}
= form_for @project, url: project_import_path(@project), method: :post do |f|
= render "shared/import_form", f: f
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index b8ee4305142..b9d45e83032 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Labels", project_labels_path(@project)
+- breadcrumb_title "Edit"
- page_title "Edit", @label.name, "Labels"
%div{ class: container_class }
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 02f59f30a39..c6739231e36 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,5 +1,6 @@
- @no_container = true
-- breadcrumb_title "Labels"
+- add_to_breadcrumbs "Labels", project_labels_path(@project)
+- breadcrumb_title "New"
- page_title "New Label"
%div{ class: container_class }
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 515499956a2..4ebb029e48b 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -21,7 +21,7 @@
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')}
window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
- window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests', anchor: 'troubleshooting')}';
+ window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/index.md', anchor: 'troubleshooting')}';
#js-vue-mr-widget.mr-widget
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index af3f25c6a30..4006a468792 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,6 +1,9 @@
- @no_container = true
+- breadcrumb_title "Edit"
+- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- page_title "Edit", @milestone.title, "Milestones"
+
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index c301f517013..01cc951e8c2 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,5 +1,6 @@
- @no_container = true
-- breadcrumb_title "Milestones"
+- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
+- breadcrumb_title "New"
- page_title "New Milestone"
%div{ class: container_class }
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 8dc042d87d1..3effdf934fb 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -15,7 +15,7 @@
.changing-auth-method= icon('spinner spin lg')
.well-password-auth.collapse.js-well-password-auth
= f.label :password, _("Password"), class: "label-bold"
- = f.password_field :password, value: mirror.password, class: 'form-control'
+ = f.password_field :password, value: mirror.password, class: 'form-control', autocomplete: 'new-password'
- unless is_push
.well-ssh-auth.collapse.js-well-ssh-auth
%p.js-ssh-public-key-present{ class: ('collapse' unless ssh_public_key_present) }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index eede8704564..10e3b01096a 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -4,10 +4,10 @@
- header_title "Projects", dashboard_projects_path
- active_tab = local_assigns.fetch(:active_tab, 'blank')
-.project-edit-container
+.project-edit-container.prepend-top-default
.project-edit-errors
= render 'projects/errors'
- .row.prepend-top-default
+ .row
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= _('New project')
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
index 2423ac6abce..769d869bd53 100644
--- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -1,3 +1,3 @@
%li{ class: active_when(params[:id] == wiki_page.slug) }
= link_to project_wiki_path(@project, wiki_page) do
- = wiki_page.title.capitalize
+ = wiki_page.human_title
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 80aa1500d53..26671a7b7d2 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,5 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
-- page_title _("Edit"), @page.title.capitalize, _("Wiki")
+- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page)
+- breadcrumb_title @page.persisted? ? _("Edit") : _("New")
+- page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki")
= wiki_page_errors(@error)
@@ -10,9 +12,9 @@
.nav-text
%h2.wiki-page-title
- if @page.persisted?
- = link_to @page.title.capitalize, project_wiki_path(@project, @page)
+ = link_to @page.human_title, project_wiki_path(@project, @page)
- else
- = @page.title.capitalize
+ = @page.human_title
%span.light
&middot;
- if @page.persisted?
@@ -28,7 +30,7 @@
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project)
- #delete-wiki-modal-wrapper{ data: { delete_wiki_url: project_wiki_path(@project, @page), page_title: @page.title.capitalize } }
+ #delete-wiki-modal-wrapper{ data: { delete_wiki_url: project_wiki_path(@project, @page), page_title: @page.human_title } }
= render 'form', uploads_path: wiki_attachment_upload_url
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 969a1677d9a..c5fbeeafa54 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,4 +1,4 @@
-- page_title _("History"), @page.title.capitalize, _("Wiki")
+- page_title _("History"), @page.human_title, _("Wiki")
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
@@ -6,7 +6,7 @@
.nav-text
%h2.wiki-page-title
- = link_to @page.title.capitalize, project_wiki_path(@project, @page)
+ = link_to @page.human_title, project_wiki_path(@project, @page)
%span.light
&middot;
= _("History")
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index fbf248c2058..cc38ec12fd8 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,7 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
-- breadcrumb_title @page.title.capitalize
+- breadcrumb_title @page.human_title
- wiki_breadcrumb_dropdown_links(@page.slug)
-- page_title @page.title.capitalize, _("Wiki")
+- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project)
.wiki-page-header.has-sidebar-toggle
@@ -9,7 +9,7 @@
= icon('angle-double-left')
.nav-text
- %h2.wiki-page-title= @page.title.capitalize
+ %h2.wiki-page-title= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index b33c758b464..1618655182c 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -17,6 +17,8 @@
= render 'shared/issuable/form/template_selector', issuable: issuable
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
+- if Feature.enabled?(:issue_suggestions) && Feature.enabled?(:graphql)
+ #js-suggestions{ data: { project_path: @project.full_path } }
= render 'shared/form_elements/description', model: issuable, form: form, project: project
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 3dd2842be4f..ed7fefba56d 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -35,8 +35,8 @@
.col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- - if @project.group
+ - if can_admin_project_milestones? and milestone.active?
+ - if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
disabled: true,
type: 'button',
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 0ce13ee7a53..ef8664e6f47 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -3,31 +3,31 @@
.d-none.d-sm-block
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
- Edit
+ = _("Edit")
- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
- Delete
- = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-success", title: "New snippet" do
- New snippet
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
+ = _("Delete")
+ = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: _("New snippet") do
+ = _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user)
- = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
.d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
+ = _("Options")
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
%li
- = link_to new_snippet_path, title: "New snippet" do
- New snippet
+ = link_to new_snippet_path, title: _("New snippet") do
+ = _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
+ = _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
- Edit
+ = _("Edit")
- if @snippet.submittable_as_spam_by?(current_user)
%li
- = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
+ = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index dfea8b40bd8..69d41f8fe5e 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -5,6 +5,6 @@
= render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
- if @snippets.empty?
%li
- .nothing-here-block Nothing here.
+ .nothing-here-block= _("Nothing here.")
= paginate @snippets, theme: 'gitlab'
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
index dc4b0fd9ba0..c312226dd6c 100644
--- a/app/views/snippets/_snippets_scope_menu.html.haml
+++ b/app/views/snippets/_snippets_scope_menu.html.haml
@@ -4,7 +4,7 @@
.nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs
%li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do
- All
+ = _("All")
%span.badge.badge-pill
- if include_private
= subject.snippets.count
@@ -14,18 +14,18 @@
- if include_private
%li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do
- Private
+ = _("Private")
%span.badge.badge-pill
= subject.snippets.are_private.count
%li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do
- Internal
+ = _("Internal")
%span.badge.badge-pill
= subject.snippets.are_internal.count
%li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do
- Public
+ = _("Public")
%span.badge.badge-pill
= subject.snippets.are_public.count
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 18ebeb78f87..ebc6c0a2605 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,5 +1,6 @@
-- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
+- page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
+
%h3.page-title
- Edit Snippet
+ = _("Edit Snippet")
%hr
= render 'shared/snippets/form', url: snippet_path(@snippet)
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 9b4a7dbe68d..4f418e2381f 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -1,13 +1,13 @@
-- page_title "By #{@user.name}", "Snippets"
+- page_title _("By %{user_name}") % { user_name: @user.name }, _("Snippets")
%ol.breadcrumb
%li.breadcrumb-item
= link_to snippets_path do
- Snippets
+ = _("Snippets")
%li.breadcrumb-item
= @user.name
.float-right.d-none.d-sm-block
= link_to user_path(@user) do
- #{@user.name} profile page
+ = _("%{user_name} profile page") % { user_name: @user.name }
= render 'snippets'
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 6bc748d346e..114c777bdc2 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
- @hide_breadcrumbs = true
-- page_title "New Snippet"
+- page_title _("New Snippet")
.page-title-holder
%h1.page-title= _('New Snippet')
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 220ba2b49e6..01b95145937 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -1,7 +1,7 @@
- if current_user
- if note.emoji_awardable?
.note-actions-item
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
+ = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
@@ -9,7 +9,7 @@
- if note_editable
.note-actions-item
- = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ = button_tag title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
%span.link-highlight
= custom_icon('icon_pencil')
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 578327883e5..36b4e00e8d5 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,8 +1,8 @@
- @hide_top_links = true
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
-- add_to_breadcrumbs "Snippets", dashboard_snippets_path
+- add_to_breadcrumbs _("Snippets"), dashboard_snippets_path
- breadcrumb_title @snippet.to_reference
-- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
+- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header'
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index 938cb579e9f..01acbf8eadd 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -7,7 +7,7 @@
%li
%span.light
%i.fa.fa-clock-o
- = event.created_at.strftime('%-I:%M%P')
+ = event.created_at.to_time.in_time_zone.strftime('%-I:%M%P')
- if event.visible_to_user?(current_user)
- if event.push?
#{event.action_name} #{event.ref_type}
diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb
index c1283e9b2fc..4a9becf0ca7 100644
--- a/app/workers/archive_trace_worker.rb
+++ b/app/workers/archive_trace_worker.rb
@@ -7,7 +7,7 @@ class ArchiveTraceWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
Ci::Build.without_archived_trace.find_by(id: job_id).try do |job|
- job.trace.archive!
+ Ci::ArchiveTraceService.new.execute(job)
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb
index 7443aad1380..f65ff239866 100644
--- a/app/workers/ci/archive_traces_cron_worker.rb
+++ b/app/workers/ci/archive_traces_cron_worker.rb
@@ -11,21 +11,9 @@ module Ci
# This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
# More details in https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build|
- begin
- build.trace.archive!
- rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
- rescue => e
- failed_archive_counter.increment
- Rails.logger.error "Failed to archive stale live trace. id: #{build.id} message: #{e.message}"
- end
+ Ci::ArchiveTraceService.new.execute(build)
end
end
# rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def failed_archive_counter
- @failed_archive_counter ||= Gitlab::Metrics.counter(:job_trace_archive_failed_total, "Counter of failed attempts of traces archiving")
- end
end
end
diff --git a/app/workers/concerns/gitlab/github_import/stage_methods.rb b/app/workers/concerns/gitlab/github_import/stage_methods.rb
index 59e6bc2c97d..e2dee315cde 100644
--- a/app/workers/concerns/gitlab/github_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/stage_methods.rb
@@ -24,7 +24,7 @@ module Gitlab
def find_project(id)
# If the project has been marked as failed we want to bail out
# automatically.
- Project.import_started.find_by(id: id)
+ Project.joins_import_state.where(import_state: { status: :started }).find_by(id: id)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/concerns/project_import_options.rb b/app/workers/concerns/project_import_options.rb
index 22bdf441d6b..2baf768bfd1 100644
--- a/app/workers/concerns/project_import_options.rb
+++ b/app/workers/concerns/project_import_options.rb
@@ -18,7 +18,7 @@ module ProjectImportOptions
"import"
end
- project.mark_import_as_failed("Every #{action} attempt has failed: #{job['error_message']}. Please try again.")
+ project.import_state.mark_as_failed(_("Every %{action} attempt has failed: %{job_error_message}. Please try again.") % { action: action, job_error_message: job['error_message'] })
Sidekiq.logger.warn "Failed #{job['class']} with #{job['args']}: #{job['error_message']}"
end
end
diff --git a/app/workers/concerns/project_start_import.rb b/app/workers/concerns/project_start_import.rb
index 46a133db2a1..4462bc51a24 100644
--- a/app/workers/concerns/project_start_import.rb
+++ b/app/workers/concerns/project_start_import.rb
@@ -2,11 +2,11 @@
# Used in EE by mirroring
module ProjectStartImport
- def start(project)
- if project.import_started? && project.import_jid == self.jid
+ def start(import_state)
+ if import_state.started? && import_state.jid == self.jid
return true
end
- project.import_start
+ import_state.start
end
end
diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb
index 2b49860025a..0b3437a8a33 100644
--- a/app/workers/gitlab/github_import/advance_stage_worker.rb
+++ b/app/workers/gitlab/github_import/advance_stage_worker.rb
@@ -31,7 +31,7 @@ module Gitlab
# next_stage - The name of the next stage to start when all jobs have been
# completed.
def perform(project_id, waiters, next_stage)
- return unless (project = find_project(project_id))
+ return unless import_state = find_import_state(project_id)
new_waiters = wait_for_jobs(waiters)
@@ -41,7 +41,7 @@ module Gitlab
# the pressure on Redis. We _only_ do this once all jobs are done so
# we don't get stuck forever if one or more jobs failed to notify the
# JobWaiter.
- project.refresh_import_jid_expiration
+ import_state.refresh_jid_expiration
STAGES.fetch(next_stage.to_sym).perform_async(project_id)
else
@@ -64,11 +64,8 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def find_project(id)
- # TODO: Only select the JID
- # This is due to the fact that the JID could be present in either the project record or
- # its associated import_state record
- Project.import_started.find_by(id: id)
+ def find_import_state(project_id)
+ ProjectImportState.select(:jid).with_status(:started).find_by(project_id: project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
index 65473026b4c..76723e4a61f 100644
--- a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
+++ b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
@@ -16,12 +16,13 @@ module Gitlab
# project_id - The ID of the project that is being imported.
# check_job_id - The ID of the job for which to check the status.
def perform(project_id, check_job_id)
- return unless (project = find_project(project_id))
+ import_state = find_import_state(project_id)
+ return unless import_state
if SidekiqStatus.running?(check_job_id)
# As long as the repository is being cloned we want to keep refreshing
# the import JID status.
- project.refresh_import_jid_expiration
+ import_state.refresh_jid_expiration
self.class.perform_in_the_future(project_id, check_job_id)
end
@@ -31,11 +32,10 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def find_project(id)
- # TODO: Only select the JID
- # This is due to the fact that the JID could be present in either the project record or
- # its associated import_state record
- Project.import_started.find_by(id: id)
+ def find_import_state(project_id)
+ ProjectImportState.select(:jid)
+ .with_status(:started)
+ .find_by(project_id: project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/gitlab/github_import/stage/import_base_data_worker.rb b/app/workers/gitlab/github_import/stage/import_base_data_worker.rb
index 5726fbb573d..ccfed2ae187 100644
--- a/app/workers/gitlab/github_import/stage/import_base_data_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_base_data_worker.rb
@@ -23,7 +23,7 @@ module Gitlab
klass.new(project, client).execute
end
- project.refresh_import_jid_expiration
+ project.import_state.refresh_jid_expiration
ImportPullRequestsWorker.perform_async(project.id)
end
diff --git a/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb b/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
index 1c5a7139802..37a7a7f4ba0 100644
--- a/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
@@ -15,7 +15,7 @@ module Gitlab
.new(project, client)
.execute
- project.refresh_import_jid_expiration
+ project.import_state.refresh_jid_expiration
AdvanceStageWorker.perform_async(
project.id,
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 68ec66e8499..7eae07d3f6b 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -12,7 +12,7 @@ class RepositoryForkWorker
source_project = target_project.forked_from_project
unless source_project
- return target_project.mark_import_as_failed('Source project cannot be found.')
+ return target_project.import_state.mark_as_failed(_('Source project cannot be found.'))
end
fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
@@ -33,7 +33,7 @@ class RepositoryForkWorker
end
def start_fork(project)
- return true if start(project)
+ return true if start(project.import_state)
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.")
false
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 82189a3c9f5..59691f48a39 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -34,14 +34,14 @@ class RepositoryImportWorker
attr_reader :project
def start_import
- return true if start(project)
+ return true if start(project.import_state)
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
false
end
def fail_import(message)
- project.mark_import_as_failed(message)
+ project.import_state.mark_as_failed(message)
end
def template_import?
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index 667a4121131..c8a186ba4ce 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -63,6 +63,6 @@ class StuckImportJobsWorker
# rubocop: enable CodeReuse/ActiveRecord
def error_message
- "Import timed out. Import took longer than #{IMPORT_JOBS_EXPIRATION} seconds"
+ _("Import timed out. Import took longer than %{import_jobs_expiration} seconds") % { import_jobs_expiration: IMPORT_JOBS_EXPIRATION }
end
end
diff --git a/changelogs/archive.md b/changelogs/archive.md
index b57440f7dc6..dd7c21dabd6 100644
--- a/changelogs/archive.md
+++ b/changelogs/archive.md
@@ -2267,7 +2267,7 @@
- Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Add Slack notifications when Wiki is edited (Sebastian Klier)
- - Diffs load at the correct point when linking from from number
+ - Diffs load at the correct point when linking from number
- Selected diff rows highlight
- Fix emoji categories in the emoji picker
- API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
diff --git a/changelogs/unreleased/33705-merge-request-rebase-api.yml b/changelogs/unreleased/33705-merge-request-rebase-api.yml
new file mode 100644
index 00000000000..322fe31ce87
--- /dev/null
+++ b/changelogs/unreleased/33705-merge-request-rebase-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add a rebase API endpoint for merge requests
+merge_request: 23296
+author:
+type: added
diff --git a/changelogs/unreleased/38495-calendar-activities-in-timezone.yml b/changelogs/unreleased/38495-calendar-activities-in-timezone.yml
new file mode 100644
index 00000000000..778d637609c
--- /dev/null
+++ b/changelogs/unreleased/38495-calendar-activities-in-timezone.yml
@@ -0,0 +1,5 @@
+---
+title: Show user contributions in correct timezone within user profile
+merge_request: 23419
+author:
+type: changed
diff --git a/changelogs/unreleased/40385-prohibit_impersonation.yml b/changelogs/unreleased/40385-prohibit_impersonation.yml
new file mode 100644
index 00000000000..dd061b17939
--- /dev/null
+++ b/changelogs/unreleased/40385-prohibit_impersonation.yml
@@ -0,0 +1,5 @@
+---
+title: Add config to prohibit impersonation
+merge_request: 23338
+author:
+type: added
diff --git a/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml b/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml
new file mode 100644
index 00000000000..3893f14e15c
--- /dev/null
+++ b/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml
@@ -0,0 +1,6 @@
+---
+title: Removes all the irrelevant code and columns that were migrated from the Project
+ table over to the ProjectImportState table
+merge_request: 21497
+author:
+type: performance
diff --git a/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml b/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml
new file mode 100644
index 00000000000..1c6c8747197
--- /dev/null
+++ b/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Scope default MR search in WebIDE dropdown to current project
+merge_request: 23400
+author:
+type: changed
diff --git a/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml b/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml
new file mode 100644
index 00000000000..86f91fcb427
--- /dev/null
+++ b/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Improves performance of Project#readme_url by caching the README path
+merge_request: 23357
+author:
+type: performance
diff --git a/changelogs/unreleased/51083-fix-move-api.yml b/changelogs/unreleased/51083-fix-move-api.yml
new file mode 100644
index 00000000000..8838f6f267e
--- /dev/null
+++ b/changelogs/unreleased/51083-fix-move-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'Commits API: Preserve file content in move operations if unspecified'
+merge_request: 23387
+author:
+type: fixed
diff --git a/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml b/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml
new file mode 100644
index 00000000000..7a900cbb86e
--- /dev/null
+++ b/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml
@@ -0,0 +1,5 @@
+---
+title: Don't remove failed install pods after installing GitLab managed applications
+merge_request: 23350
+author:
+type: changed
diff --git a/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml b/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml
new file mode 100644
index 00000000000..3dc95441eec
--- /dev/null
+++ b/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Allow user to scroll to top of tab on MR page
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml b/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml
new file mode 100644
index 00000000000..8132dde8636
--- /dev/null
+++ b/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Make new branch form fields' fonts consistent
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml b/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml
new file mode 100644
index 00000000000..8377fdc6133
--- /dev/null
+++ b/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent user from navigating away from file edit without commit
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/53763-fix-encrypt-columns-data-loss.yml b/changelogs/unreleased/53763-fix-encrypt-columns-data-loss.yml
new file mode 100644
index 00000000000..44362a8622e
--- /dev/null
+++ b/changelogs/unreleased/53763-fix-encrypt-columns-data-loss.yml
@@ -0,0 +1,5 @@
+---
+title: Correctly handle data-loss scenarios when encrypting columns
+merge_request: 23306
+author:
+type: fixed
diff --git a/changelogs/unreleased/53778-remove-site-statistics.yml b/changelogs/unreleased/53778-remove-site-statistics.yml
new file mode 100644
index 00000000000..fe006e43671
--- /dev/null
+++ b/changelogs/unreleased/53778-remove-site-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Removed Site Statistics optimization as it was causing problems
+merge_request: 23314
+author:
+type: removed
diff --git a/changelogs/unreleased/53874-navbar-lowres.yml b/changelogs/unreleased/53874-navbar-lowres.yml
new file mode 100644
index 00000000000..3b31b8f93fe
--- /dev/null
+++ b/changelogs/unreleased/53874-navbar-lowres.yml
@@ -0,0 +1,5 @@
+---
+title: "Fix overlapping navbar separator and overflowing navbar dropdown on small displays"
+merge_request: 23126
+author: Thomas Pathier
+type: fix
diff --git a/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml b/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml
new file mode 100644
index 00000000000..f0bbf69736d
--- /dev/null
+++ b/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml
@@ -0,0 +1,5 @@
+---
+title: Remove index for notes on updated_at
+merge_request: 23356
+author:
+type: performance
diff --git a/changelogs/unreleased/54571-runner-tags.yml b/changelogs/unreleased/54571-runner-tags.yml
new file mode 100644
index 00000000000..1bb19d22e9c
--- /dev/null
+++ b/changelogs/unreleased/54571-runner-tags.yml
@@ -0,0 +1,5 @@
+---
+title: Adds margins between tags when a job is stuck
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/_acet-fix-flash-styling.yml b/changelogs/unreleased/_acet-fix-flash-styling.yml
new file mode 100644
index 00000000000..57354c04899
--- /dev/null
+++ b/changelogs/unreleased/_acet-fix-flash-styling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix flash notice styling for fluid layout
+merge_request: 23382
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-use-shell-writeref.yml b/changelogs/unreleased/bvl-use-shell-writeref.yml
new file mode 100644
index 00000000000..682d428e8c5
--- /dev/null
+++ b/changelogs/unreleased/bvl-use-shell-writeref.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid creating invalid refs using rugged, shelling out for writing refs
+merge_request: 23286
+author:
+type: fixed
diff --git a/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml b/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml
new file mode 100644
index 00000000000..eb5d2e3244c
--- /dev/null
+++ b/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml
@@ -0,0 +1,5 @@
+---
+title: Respect confirmed flag on secondary emails
+merge_request: 23181
+author:
+type: fixed
diff --git a/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml b/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml
new file mode 100644
index 00000000000..31c131045b9
--- /dev/null
+++ b/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml
@@ -0,0 +1,5 @@
+---
+title: Validate chunk size when persist
+merge_request: 23341
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-deadlock-chunked-io.yml b/changelogs/unreleased/fix-deadlock-chunked-io.yml
new file mode 100644
index 00000000000..def7a59e86e
--- /dev/null
+++ b/changelogs/unreleased/fix-deadlock-chunked-io.yml
@@ -0,0 +1,5 @@
+---
+title: Fix deadlock on ChunkedIO
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/force-reload-arguments-1.yml b/changelogs/unreleased/force-reload-arguments-1.yml
new file mode 100644
index 00000000000..29f34b8bdbe
--- /dev/null
+++ b/changelogs/unreleased/force-reload-arguments-1.yml
@@ -0,0 +1,5 @@
+---
+title: Passing an argument to force an association to reload is now deprecated
+merge_request: 23334
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-snippets.yml b/changelogs/unreleased/gt-externalize-app-views-snippets.yml
new file mode 100644
index 00000000000..633aa9f2534
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/snippets`
+merge_request: 23351
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/gt-update-env-metrics-empty-state.yml b/changelogs/unreleased/gt-update-env-metrics-empty-state.yml
new file mode 100644
index 00000000000..a05dc07e65c
--- /dev/null
+++ b/changelogs/unreleased/gt-update-env-metrics-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Update environments metrics empty state
+merge_request: 23074
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/include-new-link-in-breadcrumb.yml b/changelogs/unreleased/include-new-link-in-breadcrumb.yml
new file mode 100644
index 00000000000..68c808d66d7
--- /dev/null
+++ b/changelogs/unreleased/include-new-link-in-breadcrumb.yml
@@ -0,0 +1,5 @@
+---
+title: Include new link in breadcrumb for issues, merge requests, milestones, and labels
+merge_request: 18515
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/lock-trace-writes.yml b/changelogs/unreleased/lock-trace-writes.yml
new file mode 100644
index 00000000000..9c5239081b9
--- /dev/null
+++ b/changelogs/unreleased/lock-trace-writes.yml
@@ -0,0 +1,5 @@
+---
+title: Lock writes to trace stream
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/non-webkit-scrollbar-fixing.yml b/changelogs/unreleased/non-webkit-scrollbar-fixing.yml
new file mode 100644
index 00000000000..526a9f25486
--- /dev/null
+++ b/changelogs/unreleased/non-webkit-scrollbar-fixing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix horizontal scrollbar overlapping on horizontal scrolling-tabs
+merge_request: 23167
+author: Harry Kiselev
+type: other
diff --git a/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml b/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml
new file mode 100644
index 00000000000..2348bfab7d9
--- /dev/null
+++ b/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml
@@ -0,0 +1,5 @@
+---
+title: Return real deployment status to frontend
+merge_request: 23270
+author:
+type: fixed
diff --git a/changelogs/unreleased/security-182-update-workhorse.yml b/changelogs/unreleased/security-182-update-workhorse.yml
new file mode 100644
index 00000000000..76850901b68
--- /dev/null
+++ b/changelogs/unreleased/security-182-update-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Redact sensitive information on gitlab-workhorse log
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-2736-prometheus-ssrf.yml b/changelogs/unreleased/security-2736-prometheus-ssrf.yml
new file mode 100644
index 00000000000..9d0dda8a75f
--- /dev/null
+++ b/changelogs/unreleased/security-2736-prometheus-ssrf.yml
@@ -0,0 +1,5 @@
+---
+title: Do not follow redirects in Prometheus service when making http requests to the configured api url
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml b/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml
new file mode 100644
index 00000000000..0361fb0c041
--- /dev/null
+++ b/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml
@@ -0,0 +1,5 @@
+---
+title: Don't expose confidential information in commit message list
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-email-change-notification.yml b/changelogs/unreleased/security-email-change-notification.yml
new file mode 100644
index 00000000000..45075ff20bb
--- /dev/null
+++ b/changelogs/unreleased/security-email-change-notification.yml
@@ -0,0 +1,5 @@
+---
+title: Provide email notification when a user changes their email address
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-pat-web-access.yml b/changelogs/unreleased/security-fix-pat-web-access.yml
new file mode 100644
index 00000000000..62ffb908fe5
--- /dev/null
+++ b/changelogs/unreleased/security-fix-pat-web-access.yml
@@ -0,0 +1,5 @@
+---
+title: Restrict Personal Access Tokens to API scope on web requests
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-uri-xss-applications.yml b/changelogs/unreleased/security-fix-uri-xss-applications.yml
new file mode 100644
index 00000000000..0eaa1b1c4a3
--- /dev/null
+++ b/changelogs/unreleased/security-fix-uri-xss-applications.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve reflected XSS in Ouath authorize window
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml b/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml
new file mode 100644
index 00000000000..32c85a2a7da
--- /dev/null
+++ b/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml
@@ -0,0 +1,5 @@
+---
+title: Fix SSRF in project integrations
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fj-crlf-injection.yml b/changelogs/unreleased/security-fj-crlf-injection.yml
new file mode 100644
index 00000000000..861167b8a6e
--- /dev/null
+++ b/changelogs/unreleased/security-fj-crlf-injection.yml
@@ -0,0 +1,5 @@
+---
+title: Fix CRLF vulnerability in Project hooks
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-guest-comments.yml b/changelogs/unreleased/security-guest-comments.yml
new file mode 100644
index 00000000000..2c99512433b
--- /dev/null
+++ b/changelogs/unreleased/security-guest-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed ability to comment on locked/confidential issues.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-guest-comments_2.yml b/changelogs/unreleased/security-guest-comments_2.yml
new file mode 100644
index 00000000000..be6f2d6a490
--- /dev/null
+++ b/changelogs/unreleased/security-guest-comments_2.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed ability of guest users to edit/delete comments on locked or confidential issues.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-issue_51301.yml b/changelogs/unreleased/security-issue_51301.yml
new file mode 100644
index 00000000000..cf8ebb54b1c
--- /dev/null
+++ b/changelogs/unreleased/security-issue_51301.yml
@@ -0,0 +1,5 @@
+---
+title: Fix milestone promotion authorization check
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-mermaid-xss.yml b/changelogs/unreleased/security-mermaid-xss.yml
new file mode 100644
index 00000000000..bcf93ef37ff
--- /dev/null
+++ b/changelogs/unreleased/security-mermaid-xss.yml
@@ -0,0 +1,5 @@
+---
+title: Configure mermaid to not render HTML content in diagrams
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-pages-toctou-race.yml b/changelogs/unreleased/security-pages-toctou-race.yml
new file mode 100644
index 00000000000..1c055f6087f
--- /dev/null
+++ b/changelogs/unreleased/security-pages-toctou-race.yml
@@ -0,0 +1,6 @@
+---
+title: Fix a possible symlink time of check to time of use race condition in GitLab
+ Pages
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-private-group.yml b/changelogs/unreleased/security-private-group.yml
new file mode 100644
index 00000000000..dbb7794dfed
--- /dev/null
+++ b/changelogs/unreleased/security-private-group.yml
@@ -0,0 +1,6 @@
+---
+title: Removed ability to see private group names when the group id is entered in
+ the url.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-stored-xss-for-environments.yml b/changelogs/unreleased/security-stored-xss-for-environments.yml
new file mode 100644
index 00000000000..5d78ca00942
--- /dev/null
+++ b/changelogs/unreleased/security-stored-xss-for-environments.yml
@@ -0,0 +1,5 @@
+---
+title: Fix stored XSS for Environments
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml b/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml
new file mode 100644
index 00000000000..3bd8123a346
--- /dev/null
+++ b/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml
@@ -0,0 +1,5 @@
+---
+title: Fix possible XSS attack in Markdown urls with spaces
+merge_request: 2599
+author:
+type: security
diff --git a/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml b/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml
new file mode 100644
index 00000000000..e42906e88f2
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Disable password autocomplete in mirror form fill
+merge_request: 23402
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-hash-filename-handling.yml b/changelogs/unreleased/sh-fix-hash-filename-handling.yml
new file mode 100644
index 00000000000..cb32051a4ab
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-hash-filename-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix handling of filenames with hash characters in tree view
+merge_request: 23368
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-38317.yml b/changelogs/unreleased/sh-fix-issue-38317.yml
new file mode 100644
index 00000000000..13fcb5b8f96
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-38317.yml
@@ -0,0 +1,5 @@
+---
+title: Remove needless auto-capitalization on Wiki page titles
+merge_request: 23288
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-handle-string-null-bytes.yml b/changelogs/unreleased/sh-handle-string-null-bytes.yml
new file mode 100644
index 00000000000..edc045274e3
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-string-null-bytes.yml
@@ -0,0 +1,5 @@
+---
+title: Gracefully handle references with null bytes
+merge_request: 23365
+author:
+type: fixed
diff --git a/changelogs/unreleased/tc-backfill-full-path-config.yml b/changelogs/unreleased/tc-backfill-full-path-config.yml
new file mode 100644
index 00000000000..4f06284d0e3
--- /dev/null
+++ b/changelogs/unreleased/tc-backfill-full-path-config.yml
@@ -0,0 +1,5 @@
+---
+title: Migration to write fullpath in all repository configs
+merge_request: 22322
+author:
+type: other
diff --git a/changelogs/unreleased/triggermesh-phase2-external-ip.yml b/changelogs/unreleased/triggermesh-phase2-external-ip.yml
new file mode 100644
index 00000000000..582c8f6df2e
--- /dev/null
+++ b/changelogs/unreleased/triggermesh-phase2-external-ip.yml
@@ -0,0 +1,5 @@
+---
+title: Add an external IP address to the knative cluster application page
+merge_request:
+author: Chris Baumbauer
+type: fixed
diff --git a/changelogs/unreleased/triggermesh-phase2-knative-description.yml b/changelogs/unreleased/triggermesh-phase2-knative-description.yml
new file mode 100644
index 00000000000..c6cee1984d5
--- /dev/null
+++ b/changelogs/unreleased/triggermesh-phase2-knative-description.yml
@@ -0,0 +1,5 @@
+---
+title: Modify the wording for the knative cluster application to match upstream
+merge_request: 23289
+author: Chris Baumbauer
+type: fixed
diff --git a/changelogs/unreleased/unicorn-monkey-patch.yml b/changelogs/unreleased/unicorn-monkey-patch.yml
new file mode 100644
index 00000000000..6b0e00ca291
--- /dev/null
+++ b/changelogs/unreleased/unicorn-monkey-patch.yml
@@ -0,0 +1,5 @@
+---
+title: Add monkey patch to unicorn to fix eof? problem
+merge_request: 23385
+author:
+type: fixed
diff --git a/changelogs/unreleased/upgrade_kubeclient_400.yml b/changelogs/unreleased/upgrade_kubeclient_400.yml
new file mode 100644
index 00000000000..edb38710e6a
--- /dev/null
+++ b/changelogs/unreleased/upgrade_kubeclient_400.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade kubeclient to 4.0.0
+merge_request: 23261
+author: Praveen Arimbrathodiyil @pravi
+type: other
diff --git a/changelogs/unreleased/winh-merge-request-commit-discussion.yml b/changelogs/unreleased/winh-merge-request-commit-discussion.yml
new file mode 100644
index 00000000000..b0c6264369b
--- /dev/null
+++ b/changelogs/unreleased/winh-merge-request-commit-discussion.yml
@@ -0,0 +1,5 @@
+---
+title: Display commit ID for commit diff discussion on merge request
+merge_request: 23370
+author:
+type: fixed
diff --git a/config.ru b/config.ru
index 405d01863ac..a5d055334dd 100644
--- a/config.ru
+++ b/config.ru
@@ -13,6 +13,10 @@ if defined?(Unicorn)
# Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, min, max
end
+
+ # Monkey patch for fixing Rack 2.0.6 bug:
+ # https://gitlab.com/gitlab-org/gitlab-ee/issues/8539
+ Unicorn::StreamInput.send(:public, :eof?) # rubocop:disable GitlabSecurity/PublicSend
end
require ::File.expand_path('../config/environment', __FILE__)
diff --git a/config/application.rb b/config/application.rb
index 5804d8fd27b..63a5b483fc2 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -103,6 +103,9 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content)
+ #
+ # NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
+ # introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
config.filter_parameters += [/token$/, /password/, /secret/, /key$/]
config.filter_parameters += %i(
certificate
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 09e21b2c6f2..58b7c248aaf 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -114,6 +114,9 @@ production: &base
# The default is 'shared/cache/archive/' relative to the root of the Rails app.
# repository_downloads_path: shared/cache/archive/
+ ## Impersonation settings
+ impersonation_enabled: true
+
## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails.
# For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index bd02b85c7ce..82e3b490378 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -153,6 +153,7 @@ Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values
Settings.gitlab['trusted_proxies'] ||= []
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
+Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
#
diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb
index e007666b852..7ad458929db 100644
--- a/config/initializers/attr_encrypted_no_db_connection.rb
+++ b/config/initializers/attr_encrypted_no_db_connection.rb
@@ -1,7 +1,18 @@
module AttrEncrypted
module Adapters
module ActiveRecord
- module DBConnectionQuerier
+ module GitlabMonkeyPatches
+ # Prevent attr_encrypted from defining virtual accessors for encryption
+ # data when the code and schema are out of sync. See this issue for more
+ # details: https://github.com/attr-encrypted/attr_encrypted/issues/332
+ def attribute_instance_methods_as_symbols_available?
+ false
+ end
+
+ # Prevent attr_encrypted from checking out a database connection
+ # indefinitely. The result of this method is only used when the former
+ # is true, but it is called unconditionally, so there is still value to
+ # ensuring the connection is released
def attribute_instance_methods_as_symbols
# Use with_connection so the connection doesn't stay pinned to the thread.
connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false
@@ -15,7 +26,16 @@ module AttrEncrypted
end
end
end
- prepend DBConnectionQuerier
end
end
end
+
+# As of v3.1.0, the attr_encrypted gem defines the AttrEncrypted and
+# AttrEncrypted::Adapters::ActiveRecord modules, and uses "extend" to mix them
+# into the ActiveRecord::Base class. This intervention overrides utility methods
+# defined by attr_encrypted to fix two bugs, as detailed above.
+#
+# The methods are used here: https://github.com/attr-encrypted/attr_encrypted/blob/3.1.0/lib/attr_encrypted.rb#L145-158
+ActiveSupport.on_load(:active_record) do
+ extend AttrEncrypted::Adapters::ActiveRecord::GitlabMonkeyPatches
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 179e00cdbd0..67eabb0b4fc 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -103,6 +103,9 @@ Devise.setup do |config|
# Send a notification email when the user's password is changed
config.send_password_change_notification = true
+ # Send a notification email when the user's email is changed
+ config.send_email_changed_notification = true
+
# ==> Configuration for :validatable
# Range for password length. Default is 6..128.
config.password_length = 8..128
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index f321b4ea763..6be5c00daaa 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -48,6 +48,13 @@ Doorkeeper.configure do
#
force_ssl_in_redirect_uri false
+ # Specify what redirect URI's you want to block during Application creation.
+ # Any redirect URI is whitelisted by default.
+ #
+ # You can use this option in order to forbid URI's with 'javascript' scheme
+ # for example.
+ forbid_redirect_uri { |uri| %w[data vbscript javascript].include?(uri.scheme.to_s.downcase) }
+
# Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application
diff --git a/config/initializers/kubeclient.rb b/config/initializers/kubeclient.rb
index 2d9f439fdc0..f8fe1156aaa 100644
--- a/config/initializers/kubeclient.rb
+++ b/config/initializers/kubeclient.rb
@@ -1,19 +1,4 @@
class Kubeclient::Client
- # We need to monkey patch this method until
- # https://github.com/abonas/kubeclient/pull/323 is merged
- def proxy_url(kind, name, port, namespace = '')
- discover unless @discovered
- entity_name_plural =
- if %w[services pods nodes].include?(kind.to_s)
- kind.to_s
- else
- @entities[kind.to_s].resource_name
- end
-
- ns_prefix = build_namespace_prefix(namespace)
- rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url
- end
-
# Monkey patch to set `max_redirects: 0`, so that kubeclient
# does not follow redirects and expose internal services.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/53158
diff --git a/config/initializers/rack_attack_global.rb b/config/initializers/rack_attack_global.rb
index 45963831c41..86cb930eca9 100644
--- a/config/initializers/rack_attack_global.rb
+++ b/config/initializers/rack_attack_global.rb
@@ -33,22 +33,22 @@ class Rack::Attack
throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_api_enabled &&
req.api_request? &&
- req.authenticated_user_id
+ req.authenticated_user_id([:api])
end
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_web_enabled &&
req.web_request? &&
- req.authenticated_user_id
+ req.authenticated_user_id([:api, :rss, :ics])
end
class Request
def unauthenticated?
- !authenticated_user_id
+ !authenticated_user_id([:api, :rss, :ics])
end
- def authenticated_user_id
- Gitlab::Auth::RequestAuthenticator.new(self).user&.id
+ def authenticated_user_id(request_formats)
+ Gitlab::Auth::RequestAuthenticator.new(self).user(request_formats)&.id
end
def api_request?
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 9ecae9790fd..b9044e13f50 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -84,7 +84,7 @@ module.exports = {
},
resolve: {
- extensions: ['.js'],
+ extensions: ['.js', '.gql', '.graphql'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
@@ -101,6 +101,11 @@ module.exports = {
strictExportPresence: true,
rules: [
{
+ type: 'javascript/auto',
+ test: /\.mjs$/,
+ use: [],
+ },
+ {
test: /\.js$/,
exclude: path => /node_modules|vendor[\\/]assets/.test(path) && !/\.vue\.js/.test(path),
loader: 'babel-loader',
@@ -122,6 +127,11 @@ module.exports = {
},
},
{
+ test: /\.(graphql|gql)$/,
+ exclude: /node_modules/,
+ loader: 'graphql-tag/loader',
+ },
+ {
test: /\.svg$/,
loader: 'raw-loader',
},
diff --git a/db/migrate/20181108091549_cleanup_environments_external_url.rb b/db/migrate/20181108091549_cleanup_environments_external_url.rb
new file mode 100644
index 00000000000..8d6c20a4b15
--- /dev/null
+++ b/db/migrate/20181108091549_cleanup_environments_external_url.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ update_column_in_batches(:environments, :external_url, nil) do |table, query|
+ query.where(table[:external_url].matches('javascript://%'))
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20181116050532_knative_external_ip.rb b/db/migrate/20181116050532_knative_external_ip.rb
new file mode 100644
index 00000000000..f1f903fb692
--- /dev/null
+++ b/db/migrate/20181116050532_knative_external_ip.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class KnativeExternalIp < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :clusters_applications_knative, :external_ip, :string
+ end
+end
diff --git a/db/migrate/20181120082911_rename_repositories_pool_repositories.rb b/db/migrate/20181120082911_rename_repositories_pool_repositories.rb
new file mode 100644
index 00000000000..165771c4775
--- /dev/null
+++ b/db/migrate/20181120082911_rename_repositories_pool_repositories.rb
@@ -0,0 +1,11 @@
+class RenameRepositoriesPoolRepositories < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # This change doesn't require downtime as the table is not in use, so we're
+ # free to change an empty table
+ DOWNTIME = false
+
+ def change
+ rename_table :repositories, :pool_repositories
+ end
+end
diff --git a/db/migrate/20181123042307_drop_site_statistics.rb b/db/migrate/20181123042307_drop_site_statistics.rb
new file mode 100644
index 00000000000..8986374ef65
--- /dev/null
+++ b/db/migrate/20181123042307_drop_site_statistics.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class DropSiteStatistics < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ drop_table :site_statistics
+ end
+
+ def down
+ create_table :site_statistics do |t|
+ t.integer :repositories_count, default: 0, null: false
+ end
+
+ execute('INSERT INTO site_statistics (id) VALUES(1)')
+ end
+end
diff --git a/db/migrate/20181123135036_drop_not_null_constraint_pool_repository_disk_path.rb b/db/migrate/20181123135036_drop_not_null_constraint_pool_repository_disk_path.rb
new file mode 100644
index 00000000000..bcd969e91c5
--- /dev/null
+++ b/db/migrate/20181123135036_drop_not_null_constraint_pool_repository_disk_path.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class DropNotNullConstraintPoolRepositoryDiskPath < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ change_column_null :pool_repositories, :disk_path, true
+ end
+end
diff --git a/db/migrate/20181126153547_remove_notes_index_on_updated_at.rb b/db/migrate/20181126153547_remove_notes_index_on_updated_at.rb
new file mode 100644
index 00000000000..d7ca46b50e4
--- /dev/null
+++ b/db/migrate/20181126153547_remove_notes_index_on_updated_at.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveNotesIndexOnUpdatedAt < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index(*index_arguments)
+ end
+
+ def down
+ add_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :notes,
+ [:updated_at],
+ {
+ name: 'index_notes_on_updated_at'
+ }
+ ]
+ end
+end
diff --git a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb
new file mode 100644
index 00000000000..e9ab45ae9a1
--- /dev/null
+++ b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 1_000
+ DELAY_INTERVAL = 5.minutes
+ UP_MIGRATION = 'BackfillProjectFullpathInRepoConfig::Up'
+ DOWN_MIGRATION = 'BackfillProjectFullpathInRepoConfig::Down'
+
+ disable_ddl_transaction!
+
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+
+ include EachBatch
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(Project, UP_MIGRATION, DELAY_INTERVAL)
+ end
+
+ def down
+ queue_background_migration_jobs_by_range_at_intervals(Project, DOWN_MIGRATION, DELAY_INTERVAL)
+ end
+end
diff --git a/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb
new file mode 100644
index 00000000000..ff5510e8eb7
--- /dev/null
+++ b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class MigrateForbiddenRedirectUris < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ FORBIDDEN_SCHEMES = %w[data:// vbscript:// javascript://]
+ NEW_URI = 'http://forbidden-scheme-has-been-overwritten'
+
+ disable_ddl_transaction!
+
+ def up
+ update_forbidden_uris(:oauth_applications)
+ update_forbidden_uris(:oauth_access_grants)
+ end
+
+ def down
+ # noop
+ end
+
+ private
+
+ def update_forbidden_uris(table_name)
+ update_column_in_batches(table_name, :redirect_uri, NEW_URI) do |table, query|
+ where_clause = FORBIDDEN_SCHEMES.map do |scheme|
+ table[:redirect_uri].matches("#{scheme}%")
+ end.inject(&:or)
+
+ query.where(where_clause)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index acabd7b442b..4f9588fd86b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181126150622) do
+ActiveRecord::Schema.define(version: 20181126153547) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -698,6 +698,7 @@ ActiveRecord::Schema.define(version: 20181126150622) do
t.string "version", null: false
t.string "hostname"
t.text "status_reason"
+ t.string "external_ip"
t.index ["cluster_id"], name: "index_clusters_applications_knative_on_cluster_id", unique: true, using: :btree
end
@@ -1392,7 +1393,6 @@ ActiveRecord::Schema.define(version: 20181126150622) do
t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
t.index ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
- t.index ["updated_at"], name: "index_notes_on_updated_at", using: :btree
end
create_table "notification_settings", force: :cascade do |t|
@@ -1501,6 +1501,13 @@ ActiveRecord::Schema.define(version: 20181126150622) do
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
end
+ create_table "pool_repositories", id: :bigserial, force: :cascade do |t|
+ t.integer "shard_id", null: false
+ t.string "disk_path"
+ t.index ["disk_path"], name: "index_pool_repositories_on_disk_path", unique: true, using: :btree
+ t.index ["shard_id"], name: "index_pool_repositories_on_shard_id", using: :btree
+ end
+
create_table "programming_languages", force: :cascade do |t|
t.string "name", null: false
t.string "color", null: false
@@ -1798,13 +1805,6 @@ ActiveRecord::Schema.define(version: 20181126150622) do
t.index ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
end
- create_table "repositories", id: :bigserial, force: :cascade do |t|
- t.integer "shard_id", null: false
- t.string "disk_path", null: false
- t.index ["disk_path"], name: "index_repositories_on_disk_path", unique: true, using: :btree
- t.index ["shard_id"], name: "index_repositories_on_shard_id", using: :btree
- end
-
create_table "repository_languages", id: false, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "programming_language_id", null: false
@@ -1885,10 +1885,6 @@ ActiveRecord::Schema.define(version: 20181126150622) do
t.index ["name"], name: "index_shards_on_name", unique: true, using: :btree
end
- create_table "site_statistics", force: :cascade do |t|
- t.integer "repositories_count", default: 0, null: false
- end
-
create_table "snippets", force: :cascade do |t|
t.string "title"
t.text "content"
@@ -2377,6 +2373,7 @@ ActiveRecord::Schema.define(version: 20181126150622) do
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
+ add_foreign_key "pool_repositories", "shards", on_delete: :restrict
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
@@ -2389,7 +2386,7 @@ ActiveRecord::Schema.define(version: 20181126150622) do
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
- add_foreign_key "projects", "repositories", column: "pool_repository_id", name: "fk_6e5c14658a", on_delete: :nullify
+ add_foreign_key "projects", "pool_repositories", name: "fk_6e5c14658a", on_delete: :nullify
add_foreign_key "prometheus_metrics", "projects", on_delete: :cascade
add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
@@ -2401,7 +2398,6 @@ ActiveRecord::Schema.define(version: 20181126150622) do
add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "remote_mirrors", "projects", on_delete: :cascade
- add_foreign_key "repositories", "shards", on_delete: :restrict
add_foreign_key "repository_languages", "projects", on_delete: :cascade
add_foreign_key "resource_label_events", "issues", on_delete: :cascade
add_foreign_key "resource_label_events", "labels", on_delete: :nullify
diff --git a/doc/README.md b/doc/README.md
index c1971b2b905..b15c3a63d92 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,12 +3,18 @@ comments: false
description: 'Learn how to use and administer GitLab, the most scalable Git-based fully integrated platform for software development.'
---
+<div class="display-none">
+ <em>Visit <a href="https://docs.gitlab.com/ce/">docs.gitlab.com</a> for optimized
+ navigation, discoverability, and readability.</em>
+</div>
+<!-- the div above will not display on the docs site but will display on /help -->
+
# GitLab Documentation
Welcome to [GitLab](https://about.gitlab.com/) Documentation.
Here you can access the complete documentation for GitLab, the single application for the
-[entire DevOps lifecycle](#complete-devops-with-gitlab).
+[entire DevOps lifecycle](#the-entire-devops-lifecycle).
## Overview
@@ -58,7 +64,7 @@ The following sections provide links to documentation for each DevOps stage:
| [Release](#release) | Application release and delivery features. |
| [Configure](#configure) | Application and infrastructure configuration tools. |
| [Monitor](#monitor) | Application monitoring and metrics features. |
-| [Secure](#secure) | Security capability feature. |
+| [Secure](#secure) | Security capability features. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -72,11 +78,11 @@ GitLab provides statistics and insight into ways you can maximize the value of G
The following documentation relates to the DevOps **Manage** stage:
-| Manage Topics | Description |
-|:----------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Authentication and Authorization](administration/auth/README.md) **[CORE ONLY]** | Supported authentication and authorization providers. |
-| [GitLab Cycle Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. |
-| [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. |
+| Manage Topics | Description |
+|:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Authentication and<br/>Authorization](administration/auth/README.md) **[CORE ONLY]** | Supported authentication and authorization providers. |
+| [GitLab Cycle Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. |
+| [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -93,17 +99,17 @@ management tools.
The following documentation relates to the DevOps **Plan** stage:
-| Plan Topics | Description |
-|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
-| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
-| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
-| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md), [issue and merge request templates](user/project/description_templates.md), and [moving issues](user/project/issues/moving_issues.md) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. |
-| [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. |
-| [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. |
-| [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. |
-| [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. |
-| [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
+| Plan Topics | Description |
+|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
+| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
+| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
+| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/moving_issues.md) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. |
+| [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. |
+| [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. |
+| [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. |
+| [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. |
+| [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -124,16 +130,16 @@ The following documentation relates to the DevOps **Create** stage:
#### Projects and Groups
-| Create Topics - Projects and Groups | Description |
-|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|
-| [Create](gitlab-basics/create-project.md) and [fork](gitlab-basics/fork-project.md) projects, and [import and export projects between instances](user/project/settings/import_export.md) | Create, duplicate, and move projects. |
-| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. |
-| [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. |
-| [Projects](user/project/index.md), including [project access](public_access/public_access.md) and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
-| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
-| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
-| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
-| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
+| Create Topics - Projects and Groups | Description |
+|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|
+| [Create](gitlab-basics/create-project.md) and [fork](gitlab-basics/fork-project.md) projects, and<br/>[import and export<br/>projects between instances](user/project/settings/import_export.md) | Create, duplicate, and move projects. |
+| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. |
+| [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. |
+| [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
+| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
+| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
+| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
+| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -143,18 +149,18 @@ The following documentation relates to the DevOps **Create** stage:
#### Repositories
-| Create Topics - Repositories | Description |
-|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
-| [Branches](user/project/repository/branches/index.md) and the [default branch](user/project/repository/branches/index.md#default-branch) | How to use branches in GitLab. |
-| [Commits](user/project/repository/index.md#commits) and [signing commits](user/project/repository/gpg_signed_commits/index.md) | Work with commits, and use GPG to sign your commits. |
-| [Create branches](user/project/repository/web_editor.md#create-a-new-branch), [create](user/project/repository/web_editor.md#create-a-file) and [upload](user/project/repository/web_editor.md#upload-a-file) files, and [create directories](user/project/repository/web_editor.md#create-a-directory) | Create branches, create and upload files, and create directories within GitLab. |
-| [Delete merged branches](user/project/repository/branches/index.md#delete-merged-branches) | Bulk delete branches after their changes are merged. |
-| [File templates](user/project/repository/web_editor.md#template-dropdowns) | File templates for common files. |
-| [Files](user/project/repository/index.md#files) | Files management. |
-| [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) | GitLab's support for `.ipynb` files. |
-| [Protected branches](user/project/protected_branches.md) | Use protected branches. |
-| [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. |
-| [Start a merge request](user/project/repository/web_editor.md#tips) | Start merge request when committing via GitLab's user interface. |
+| Create Topics - Repositories | Description |
+|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
+| [Branches](user/project/repository/branches/index.md) and the [default branch](user/project/repository/branches/index.md#default-branch) | How to use branches in GitLab. |
+| [Commits](user/project/repository/index.md#commits) and [signing commits](user/project/repository/gpg_signed_commits/index.md) | Work with commits, and use GPG to sign your commits. |
+| [Create branches](user/project/repository/web_editor.md#create-a-new-branch), [create](user/project/repository/web_editor.md#create-a-file)<br/>and [upload](user/project/repository/web_editor.md#upload-a-file) files, and [create directories](user/project/repository/web_editor.md#create-a-directory) | Create branches, create and upload files, and create directories within GitLab. |
+| [Delete merged branches](user/project/repository/branches/index.md#delete-merged-branches) | Bulk delete branches after their changes are merged. |
+| [File templates](user/project/repository/web_editor.md#template-dropdowns) | File templates for common files. |
+| [Files](user/project/repository/index.md#files) | Files management. |
+| [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) | GitLab's support for `.ipynb` files. |
+| [Protected branches](user/project/protected_branches.md) | Use protected branches. |
+| [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. |
+| [Start a merge request](user/project/repository/web_editor.md#tips) | Start merge request when committing via GitLab's user interface. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -268,15 +274,15 @@ configuration. Then customize everything from buildpacks to CI/CD.
The following documentation relates to the DevOps **Configure** stage:
-| Configure Topics | Description |
-|:-------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
-| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
-| [Easy creation of Kubernetes clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
-| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
-| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
-| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
-| [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. |
-| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
+| Configure Topics | Description |
+|:-----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
+| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
+| [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
+| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
+| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
+| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
+| [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. |
+| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index f16ae835ced..2ca860bd763 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -118,7 +118,7 @@ need some extra configuration.
gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
```
-1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+1. Run `touch /etc/gitlab/skip-auto-reconfigure` to prevent database migrations
from running on upgrade. Only the primary GitLab application server should
handle migrations.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index a9ba40c870c..833c1f367dd 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -336,7 +336,7 @@ The prerequisites for a HA Redis setup are the following:
1. To prevent database migrations from running on upgrade, run:
```
- sudo touch /etc/gitlab/skip-auto-migrations
+ sudo touch /etc/gitlab/skip-auto-reconfigure
```
Only the primary GitLab application server should handle migrations.
@@ -458,7 +458,7 @@ multiple machines with the Sentinel daemon.
1. To prevent database migrations from running on upgrade, run:
```
- sudo touch /etc/gitlab/skip-auto-migrations
+ sudo touch /etc/gitlab/skip-auto-reconfigure
```
Only the primary GitLab application server should handle migrations.
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 7e5a3eb9ccd..698f4caab3a 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -126,6 +126,25 @@ It contains information about [integrations](../user/project/integrations/projec
{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.net"}
```
+## `kubernetes.log`
+
+Introduced in GitLab 11.6. This file lives in
+`/var/log/gitlab/gitlab-rails/kubernetes.log` for Omnibus GitLab
+packages or in `/home/git/gitlab/log/kubernetes.log` for
+installations from source.
+
+It logs information related to the Kubernetes Integration including errors
+during installing cluster applications on your GitLab managed Kubernetes
+clusters.
+
+Each line contains a JSON line that can be ingested by Elasticsearch, Splunk,
+etc. For example:
+
+```json
+{"severity":"ERROR","time":"2018-11-23T15:14:54.652Z","exception":"Kubeclient::HttpError","error_code":401,"service":"Clusters::Applications::CheckInstallationProgressService","app_id":14,"project_ids":[1],"group_ids":[],"message":"Unauthorized"}
+{"severity":"ERROR","time":"2018-11-23T15:42:11.647Z","exception":"Kubeclient::HttpError","error_code":null,"service":"Clusters::Applications::InstallService","app_id":2,"project_ids":[19],"group_ids":[],"message":"SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)"}
+```
+
## `githost.log`
This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for
diff --git a/doc/api/README.md b/doc/api/README.md
index 0e8d1aaf86e..848a6e6b72b 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -225,6 +225,43 @@ For more information, refer to the
Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the
`private_token` parameter or the `Private-Token` header.
+#### Disable impersonation
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/40385) in GitLab
+11.6.
+
+By default, impersonation is enabled. To disable impersonation, GitLab must be
+reconfigured:
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['impersonation_enabled'] = false
+ ```
+
+1. Save the file and [reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
+ GitLab for the changes to take effect.
+
+To re-enable impersonation, remove this configuration and reconfigure GitLab.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ gitlab:
+ impersonation_enabled: false
+ ```
+
+1. Save the file and [restart](../administration/restart_gitlab.md#installations-from-source)
+ GitLab for the changes to take effect.
+
+To re-enable impersonation, remove this configuration and restart GitLab.
+
### Sudo
NOTE: **Note:**
@@ -540,7 +577,7 @@ When you try to access an API URL that does not exist you will receive 404 Not F
```
HTTP/1.1 404 Not Found
Content-Type: application/json
-{
+{ f
"error": "404 Not Found"
}
```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 7d9b52ec24f..6c16216429d 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -87,7 +87,7 @@ POST /projects/:id/repository/commits
| `action` | string | yes | The action to perform, `create`, `delete`, `move`, `update`, `chmod`|
| `file_path` | string | yes | Full path to the file. Ex. `lib/class.rb` |
| `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb`. Only considered for `move` action. |
-| `content` | string | no | File content, required for all except `delete` and `chmod`. Optional for `move` |
+| `content` | string | no | File content, required for all except `delete`, `chmod`, and `move`. Move actions that do not specify `content` will preserve the existing file content, and any other value of `content` will overwrite the file content. |
| `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. |
| `execute_filemode` | boolean | no | When `true/false` enables/disables the execute flag on the file. Only considered for `chmod` action. |
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index a1e1ff1419d..3538a577c8e 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -123,7 +123,7 @@ curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
### Create new issue discussion
Creates a new discussion to a single project issue. This is similar to creating
-a note but but another comments (replies) can be added to it later.
+a note but other comments (replies) can be added to it later.
```
POST /projects/:id/issues/:issue_iid/discussions
@@ -329,7 +329,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitla
### Create new snippet discussion
Creates a new discussion to a single project snippet. This is similar to creating
-a note but but another comments (replies) can be added to it later.
+a note but other comments (replies) can be added to it later.
```
POST /projects/:id/snippets/:snippet_id/discussions
@@ -588,7 +588,7 @@ curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
### Create new merge request discussion
Creates a new discussion to a single project merge request. This is similar to creating
-a note but but another comments (replies) can be added to it later.
+a note but other comments (replies) can be added to it later.
```
POST /projects/:id/merge_requests/:merge_request_iid/discussions
@@ -881,7 +881,7 @@ curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
### Create new commit discussion
Creates a new discussion to a single project commit. This is similar to creating
-a note but but another comments (replies) can be added to it later.
+a note but other comments (replies) can be added to it later.
```
POST /projects/:id/commits/:commit_id/discussions
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index da70c74c4ce..fc03cf6cc39 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -408,6 +408,7 @@ Parameters:
- `merge_request_iid` (required) - The internal ID of the merge request
- `render_html` (optional) - If `true` response includes rendered HTML for title and description
- `include_diverged_commits_count` (optional) - If `true` response includes the commits behind the target branch
+- `include_rebase_in_progress` (optional) - If `true` response includes whether a rebase operation is in progress
```json
{
@@ -461,6 +462,7 @@ Parameters:
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
+ "merge_error": null,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
@@ -505,7 +507,8 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "rebase_in_progress": false
}
```
@@ -773,6 +776,7 @@ POST /projects/:id/merge_requests
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
+ "merge_error": null,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
@@ -900,6 +904,7 @@ Must include at least one non-required attribute from above.
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
+ "merge_error": null,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
@@ -1043,6 +1048,7 @@ Parameters:
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
+ "merge_error": null,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
@@ -1158,6 +1164,7 @@ Parameters:
},
"merge_when_pipeline_succeeds": false,
"merge_status": "can_be_merged",
+ "merge_error": null,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
@@ -1206,6 +1213,62 @@ Parameters:
}
```
+## Rebase a merge request
+
+Automatically rebase the `source_branch` of the merge request against its
+`target_branch`.
+
+If you don't have permissions to push to the merge request's source branch -
+you'll get a `403 Forbidden` response.
+
+```
+PUT /projects/:id/merge_requests/:merge_request_iid/rebase
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase
+```
+
+This is an asynchronous request. The API will return an empty `202 Accepted`
+response if the request is enqueued successfully.
+
+You can poll the [Get single MR](#get-single-mr) endpoint with the
+`include_rebase_in_progress` parameter to check the status of the
+asynchronous request.
+
+If the rebase operation is ongoing, the response will include the following:
+
+```json
+{
+ "rebase_in_progress": true
+ "merge_error": null
+}
+```
+
+Once the rebase operation has completed successfully, the response will include
+the following:
+
+```json
+{
+ "rebase_in_progress": false,
+ "merge_error": null,
+}
+```
+
+If the rebase operation fails, the response will include the following:
+
+```json
+{
+ "rebase_in_progress": false,
+ "merge_error": "Rebase failed. Please rebase locally",
+}
+```
+
## Comments on merge requests
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 31649ee2792..959271d8abc 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -67,6 +67,9 @@ services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
Any image found at [Docker Hub][hub] or your private Container Registry can be
used as a service.
+Services inherit the same DNS servers, search domains, and additional hosts as
+the CI container itself.
+
You can see some widely used services examples in the relevant documentation of
[CI services examples](../services/README.md).
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index fdf09d332a5..a8c119edaa0 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -9,7 +9,7 @@ GitLab will give you the option to choose one of these templates.
If your favorite programming language or framework are missing we would love your
help by sending a merge request with a new `.gitlab-ci.yml` to this project.
-There's also a collection of repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork an adjust them to your own needs.
+There's also a collection of repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork and adjust them to your own needs.
## Languages, frameworks, OSs
@@ -45,11 +45,11 @@ There's also a collection of repositories with [example projects](https://gitlab
## Test Reports
-[Collect test reports in Verify stage](../junit_test_reports.md).
+[Collect test reports in Verify stage](../junit_test_reports.md)
## Code Quality analysis
-**(Starter)** [Analyze your project's Code Quality](code_quality.md).
+**(Starter)** [Analyze your project's Code Quality](code_quality.md)
## Static Application Security Testing (SAST)
@@ -65,15 +65,15 @@ There's also a collection of repositories with [example projects](https://gitlab
## Dynamic Application Security Testing (DAST)
-Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md).
+Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md)
## Browser Performance Testing with Sitespeed.io
-Analyze your [browser performance with Sitespeed.io](browser_performance.md).
+Analyze your [browser performance with Sitespeed.io](browser_performance.md)
## GitLab CI/CD for Review Apps
-- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html).
+- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html)
- [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
## GitLab CI/CD for GitLab Pages
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 3ea81be1569..40ceef3d554 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -64,7 +64,7 @@ applications:
## Configure GitLab CI/CD to deploy your application
-Now we need to add the the GitLab CI/CD configuration file
+Now we need to add the GitLab CI/CD configuration file
([`.gitlab-ci.yml`](../../yaml/README.md)) to our
project's root. This is how GitLab figures out what commands need to be run whenever
code is pushed to our repository. We will add the following `.gitlab-ci.yml`
diff --git a/doc/ci/img/pipelines-goal.png b/doc/ci/img/pipelines-goal.png
new file mode 100644
index 00000000000..a96368e562b
--- /dev/null
+++ b/doc/ci/img/pipelines-goal.png
Binary files differ
diff --git a/doc/ci/img/pipelines-goal.svg b/doc/ci/img/pipelines-goal.svg
deleted file mode 100644
index a925e2282a4..00000000000
--- a/doc/ci/img/pipelines-goal.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" standalone="yes"?>
-
-<svg version="1.1" viewBox="0.0 0.0 1091.020997375328 262.04461942257217" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="p.0"><path d="m0 0l1091.021 0l0 262.04462l-1091.021 0l0 -262.04462z" clip-rule="nonzero"></path></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l1091.021 0l0 262.04462l-1091.021 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m226.93439 3.7664042l860.7559 0l0 249.5748l-860.7559 0z" fill-rule="nonzero"></path><path stroke="#666666" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m226.93439 3.7664042l860.7559 0l0 249.5748l-860.7559 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m67.72179 27.199474l147.2126 0l0 39.464565l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m126.91313 49.353848l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.578125 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.5 -0.828125 3.296875 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.625 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.078125 0.640625 2.3125 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm3.5354462 4.765625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.978302 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 4.65625q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.735092 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm14.449646 5.875l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm15.634552 0l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5z" fill-rule="nonzero"></path><path fill="#efefef" d="m765.3307 106.94125l147.21265 0l0 59.74803l-147.21265 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m765.3307 106.94125l147.21265 0l0 59.74803l-147.21265 0z" fill-rule="nonzero"></path><path fill="#000000" d="m800.99805 132.73526l0 -13.593742l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.7499924q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.0781174 0.359375 -2.6249924q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390617zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343674q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.43749237l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.7031174l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.656242l0 -13.640617l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.5156174 -0.546875 2.7343674q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.656242q0 1.9062424 0.765625 2.8124924q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.9218674q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.8749924l0 -13.593742l1.671875 0l0 13.593742l-1.671875 0zm3.5510254 -4.9218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.718742l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.874992l1.796875 0l2.046875 5.7187424q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.7499924l1.671875 0l-3.75 10.031242q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.4218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#000000" d="m808.1591 150.36026l1.6875 -0.140625q0.125 1.015625 0.5625 1.671875q0.4375 0.65625 1.359375 1.0625q0.9375 0.40625 2.09375 0.40625q1.03125 0 1.8125 -0.3125q0.796875 -0.3125 1.1875 -0.84375q0.390625 -0.53125 0.390625 -1.15625q0 -0.640625 -0.375 -1.109375q-0.375 -0.484375 -1.234375 -0.8125q-0.546875 -0.21875 -2.421875 -0.65625q-1.875 -0.453125 -2.625 -0.859375q-0.96875 -0.515625 -1.453125 -1.265625q-0.46875 -0.75 -0.46875 -1.6875q0 -1.03125 0.578125 -1.921875q0.59375 -0.90625 1.703125 -1.359375q1.125 -0.46875 2.5 -0.46875q1.515625 0 2.671875 0.484375q1.15625 0.484375 1.765625 1.4375q0.625 0.9375 0.671875 2.140625l-1.71875 0.125q-0.140625 -1.28125 -0.953125 -1.9375q-0.796875 -0.671875 -2.359375 -0.671875q-1.625 0 -2.375 0.609375q-0.75 0.59375 -0.75 1.4375q0 0.734375 0.53125 1.203125q0.515625 0.46875 2.703125 0.96875q2.203125 0.5 3.015625 0.875q1.1875 0.546875 1.75 1.390625q0.578125 0.828125 0.578125 1.921875q0 1.09375 -0.625 2.0625q-0.625 0.953125 -1.796875 1.484375q-1.15625 0.53125 -2.609375 0.53125q-1.84375 0 -3.09375 -0.53125q-1.25 -0.546875 -1.96875 -1.625q-0.703125 -1.078125 -0.734375 -2.453125zm16.490417 2.875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm7.9645386 0.28125q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819824 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.313232 -6.578125l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.078796 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875z" fill-rule="nonzero"></path><path fill="#efefef" d="m925.54333 177.39108l147.21252 0l0 59.74803l-147.21252 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m925.54333 177.39108l147.21252 0l0 59.74803l-147.21252 0z" fill-rule="nonzero"></path><path fill="#000000" d="m961.2107 203.18509l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207275 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510864 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203796 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.24585 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90197754 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#000000" d="m956.0228 225.18509l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4122925 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.6033325 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672607 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719421 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360107 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.531311 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.281311 -1.328125 -1.281311 -3.8125zm1.718811 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.28186 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m57.989502 117.082985l156.94489 0l0 39.46456l-156.94489 0z" fill-rule="nonzero"></path><path fill="#000000" d="m71.518036 144.00298l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm22.134552 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.931427 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm17.949646 4.375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90205383 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm14.621521 4.921875l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm21.822052 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.4069672 2.0l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m5.0104985 187.5328l209.95276 0l0 39.46457l-209.95276 0z" fill-rule="nonzero"></path><path fill="#000000" d="m34.188858 214.4528l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm22.134552 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.931427 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm17.949646 4.375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90205383 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm14.684021 4.921875l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.412323 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.603302 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719467 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360092 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426788 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041962 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281967 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.813217 0l0 -1.90625l1.90625 0l0 1.90625q0 1.046875 -0.375 1.6875q-0.375 0.65625 -1.171875 1.0l-0.46875 -0.71875q0.53125 -0.21875 0.78125 -0.671875q0.25 -0.453125 0.28125 -1.296875l-0.953125 0z" fill-rule="nonzero"></path><path fill="#000000" d="m51.19565 236.4528l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.161606 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819672 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm14.887146 5.109375l0 -8.546875l-1.484375 0l0 -1.3125l1.484375 0l0 -1.046875q0 -0.984375 0.171875 -1.46875q0.234375 -0.65625 0.84375 -1.046875q0.609375 -0.40625 1.703125 -0.40625q0.703125 0 1.5625 0.15625l-0.25 1.46875q-0.515625 -0.09375 -0.984375 -0.09375q-0.765625 0 -1.078125 0.328125q-0.3125 0.3125 -0.3125 1.203125l0 0.90625l1.921875 0l0 1.3125l-1.921875 0l0 8.546875l-1.65625 0zm4.152054 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.266342 4.921875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm11.661606 0l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176071 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.926071 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 4.65625q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.4069672 2.0l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm16.75 -0.234375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.547592 5.875l0 -1.90625l1.90625 0l0 1.90625q0 1.046875 -0.375 1.6875q-0.375 0.65625 -1.171875 1.0l-0.46875 -0.71875q0.53125 -0.21875 0.78125 -0.671875q0.25 -0.453125 0.28125 -1.296875l-0.953125 0zm9.304108 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.266342 4.921875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m88.11708 258.45282l0 -13.593765l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.0156403q0.234375 -0.75001526 0.734375 -2.1875153l3.25 -9.453125l2.421875 0l0 13.593765l-1.734375 0l0 -11.39064l-3.953125 11.39064l-1.625 0l-3.9375 -11.57814l0 11.57814l-1.734375 0zm21.822052 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.0312653q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.3281403 0.09375 2.9531403q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187653q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.67189026 0.5 1.1250153q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.1562653q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm4.078842 4.9375153l0 -9.85939l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625153l-1.671875 0l0 -6.0000153q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3750153l-1.671875 0zm16.828842 0l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.50001526 -0.09375 -1.5625153l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65626526 0.671875 1.0312653q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.0468903q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.85939l-1.5 0zm10.360092 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.0312653q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.3281403 0.09375 2.9531403q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187653q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.67189026 0.5 1.1250153q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.1562653q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm4.047592 4.9375153l0 -13.593765l1.671875 0l0 13.593765l-1.671875 0zm13.015625 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.9843903l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71876526 0.078125 0.92189026q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5114288 1.5l0 -9.85939l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562653l-1.671875 0zm6.243927 -11.687515l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.687515l0 -9.85939l1.671875 0l0 9.85939l-1.671875 0zm3.8323212 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.4687653 -1.109375 -3.5156403q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.51564q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218903q0 1.953125 0.765625 2.8437653q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.89064026 0.78125 -2.7812653q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.000717 5.9218903l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.4687653 -1.109375 -3.5156403q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.51564q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218903q0 1.953125 0.765625 2.8437653q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.89064026 0.78125 -2.7812653q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5000153 -1.515625 2.3437653q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343903q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.4843903q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.5468903zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.8750153l0 -9.85939l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562653l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m214.93439 46.93176l13.599762 0l0 0.062992096l13.612839 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.93439 46.93176l13.599747 0l0 0.062992096l7.612854 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m236.14699 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#efefef" d="m242.13387 17.057743l147.2126 0l0 59.748028l-147.2126 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m242.13387 17.057743l147.2126 0l0 59.748028l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m296.38846 53.851757l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382324 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097931 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566711 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#efefef" d="m408.5328 17.057743l147.21262 0l0 59.748028l-147.21262 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m408.5328 17.057743l147.21262 0l0 59.748028l-147.21262 0z" fill-rule="nonzero"></path><path fill="#000000" d="m455.20813 40.258007l1.796875 0l0 7.84375q0 2.0625 -0.46875 3.265625q-0.453125 1.203125 -1.671875 1.96875q-1.203125 0.75 -3.171875 0.75q-1.90625 0 -3.125 -0.65625q-1.21875 -0.65625 -1.734375 -1.90625q-0.515625 -1.25 -0.515625 -3.421875l0 -7.84375l1.796875 0l0 7.84375q0 1.765625 0.328125 2.609375q0.328125 0.84375 1.125 1.296875q0.8125 0.453125 1.96875 0.453125q1.984375 0 2.828125 -0.890625q0.84375 -0.90625 0.84375 -3.46875l0 -7.84375zm4.332306 13.59375l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.391357 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm7.785431 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm10.382233 1.5l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m594.1181 37.30971l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m594.1181 37.30971l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m625.4092 63.103725l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.24999619q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.2812462q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3124962 -0.46875 3.2656212q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.3906212zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m654.5047 85.10372l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m584.5302 27.199474l147.21259 0l0 59.748035l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m584.5302 27.199474l147.21259 0l0 59.748035l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m615.8212 52.99349l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6677246 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031921 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.2771 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813171 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6658325 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7350464 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5427246 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041504 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m644.9168 74.99349l0 -12.000004l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.000004l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438171 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375038l1.65625 -1.0l0 3.4375038l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m574.93176 17.057743l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m574.93176 17.057743l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m606.22284 42.851757l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m635.31836 64.85175l0 -11.999996l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 11.999996l-1.796875 0zm14.474121 -3.1718712l1.71875 0.21875q-0.40625 1.5 -1.515625 2.3437462q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.3281212q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.7343712 -1.40625 1.1406212q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.7968712 -1.28125 -2.3593712zm13.65625 1.4375l0.234375 1.4843712q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.7499962q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m389.34647 46.93176l9.593231 0l0 0.062992096l9.58786 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m389.34647 46.93176l9.593201 0l0 0.062992096l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m402.52756 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m555.7454 46.93176l9.593201 0l0 0.062992096l9.587891 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m555.7454 46.93176l9.593201 0l0 0.062992096l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m568.9265 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m214.96326 207.26509l355.29132 0l0 0.06298828l355.29138 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.96326 207.26509l355.29132 0l0 0.06298828l349.29138 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m919.54596 208.97981l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m214.93439 136.81526l14.661118 0l0 0.06300354l14.661713 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.93439 136.81526l14.661133 0l0 0.06300354l8.661697 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m238.25722 138.53l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#efefef" d="m244.25691 106.94125l147.2126 0l0 59.74803l-147.2126 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m244.25691 106.94125l147.2126 0l0 59.74803l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m298.5115 143.73526l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382324 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097931 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566711 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#efefef" d="m410.65585 106.94125l147.21262 0l0 59.74803l-147.21262 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m410.65585 106.94125l147.21262 0l0 59.74803l-147.21262 0z" fill-rule="nonzero"></path><path fill="#000000" d="m457.33118 130.14151l1.796875 0l0 7.84375q0 2.0625 -0.46875 3.265625q-0.453125 1.203125 -1.671875 1.96875q-1.203125 0.75 -3.171875 0.75q-1.90625 0 -3.125 -0.65625q-1.21875 -0.65625 -1.734375 -1.90625q-0.515625 -1.25 -0.515625 -3.421875l0 -7.84375l1.796875 0l0 7.84375q0 1.765625 0.328125 2.609375q0.328125 0.84375 1.125 1.296875q0.8125 0.453125 1.96875 0.453125q1.984375 0 2.828125 -0.890625q0.84375 -0.90625 0.84375 -3.46875l0 -7.84375zm4.332306 13.59375l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.391357 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm7.785431 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm10.382233 1.5l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m596.24115 127.19322l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m596.24115 127.19322l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m627.5322 152.98723l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m656.62775 174.98723l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m586.65326 117.082985l147.21259 0l0 59.748024l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m586.65326 117.082985l147.21259 0l0 59.748024l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m617.9443 142.877l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6677246 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031921 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.2771 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813171 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6658325 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7350464 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5427246 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041504 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m647.03986 164.877l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438171 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m577.0548 106.94125l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m577.0548 106.94125l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m608.3459 132.73526l0 -13.593742l1.8125 0l0 13.593742l-1.8125 0zm4.6676636 0l0 -9.859367l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0624924l-1.671875 0l0 -5.9999924q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3749924l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343674q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.43749237l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.7031174l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6874924l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.5156174q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515617q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218674q0 1.9531174 0.765625 2.8437424q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.7812424q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.1093674l0 -9.859367l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562424l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.9531174q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.2343674q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187424q-0.90625 0.35936737 -2.734375 0.6249924q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.6093674zm7.7351074 3.4374924l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.187492l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.687492l0 -9.859367l1.671875 0l0 9.859367l-1.671875 0zm3.5042114 -4.9218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.9218674l0 -9.859367l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0624924l-1.671875 0l0 -5.9999924q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3749924l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m637.4414 154.73526l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m391.4695 136.81526l9.593231 0l0 0.06300354l9.58786 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m391.46954 136.81526l9.593201 0l0 0.06300354l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m404.65063 138.53l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m557.86847 136.81526l9.593201 0l0 0.06300354l9.587891 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m557.86847 136.81526l9.593201 0l0 0.06300354l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m571.04956 138.53l4.538147 -1.6517334l-4.538147 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m724.2674 136.81526l20.531738 0l0 0.06300354l20.539124 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m724.2674 136.81526l20.531738 0l0 0.06300354l14.539124 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m759.33826 138.53l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path></g></svg>
-
diff --git a/doc/ci/img/types-of-pipelines.png b/doc/ci/img/types-of-pipelines.png
new file mode 100644
index 00000000000..bd809de5e68
--- /dev/null
+++ b/doc/ci/img/types-of-pipelines.png
Binary files differ
diff --git a/doc/ci/img/types-of-pipelines.svg b/doc/ci/img/types-of-pipelines.svg
deleted file mode 100644
index b63b5f56ba6..00000000000
--- a/doc/ci/img/types-of-pipelines.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" standalone="yes"?>
-
-<svg version="1.1" viewBox="0.0 0.0 740.6272965879265 293.7795275590551" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="p.0"><path d="m0 0l740.6273 0l0 293.77954l-740.6273 0l0 -293.77954z" clip-rule="nonzero"></path></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l740.6273 0l0 293.77954l-740.6273 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m176.05511 4.632546l282.4567 0l0 129.10236l-282.4567 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m176.05511 4.632546l282.4567 0l0 129.10236l-282.4567 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m283.34512 115.88928l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.578125 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.5 -0.828125 3.296875 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.625 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.078125 0.640625 2.3125 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm4.066681 4.765625l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm10.069733 0l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.443573 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610077 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078857 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.191681 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125702 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m482.78796 22.986877l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m482.78796 22.986877l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path fill="#434343" d="m499.90414 48.78089l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207306 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510864 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203796 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#434343" d="m507.0652 66.40589l1.6875 -0.140625q0.125 1.015625 0.5625 1.671875q0.4375 0.65625 1.359375 1.0625q0.9375 0.40625 2.09375 0.40625q1.03125 0 1.8125 -0.3125q0.796875 -0.3125 1.1875 -0.84375q0.390625 -0.53125 0.390625 -1.15625q0 -0.640625 -0.375 -1.109375q-0.375 -0.484375 -1.234375 -0.8125q-0.546875 -0.21875 -2.421875 -0.65625q-1.875 -0.453125 -2.625 -0.859375q-0.96875 -0.515625 -1.453125 -1.265625q-0.46875 -0.75 -0.46875 -1.6875q0 -1.03125 0.578125 -1.921875q0.59375 -0.90625 1.703125 -1.359375q1.125 -0.46875 2.5 -0.46875q1.515625 0 2.671875 0.484375q1.15625 0.484375 1.765625 1.4375q0.625 0.9375 0.671875 2.140625l-1.71875 0.125q-0.140625 -1.28125 -0.953125 -1.9375q-0.796875 -0.671875 -2.359375 -0.671875q-1.625 0 -2.375 0.609375q-0.75 0.59375 -0.75 1.4375q0 0.734375 0.53125 1.203125q0.515625 0.46875 2.703125 0.96875q2.203125 0.5 3.015625 0.875q1.1875 0.546875 1.75 1.390625q0.578125 0.828125 0.578125 1.921875q0 1.09375 -0.625 2.0625q-0.625 0.953125 -1.796875 1.484375q-1.15625 0.53125 -2.609375 0.53125q-1.84375 0 -3.09375 -0.53125q-1.25 -0.546875 -1.96875 -1.625q-0.703125 -1.078125 -0.734375 -2.453125zm16.490417 2.875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm7.9645996 0.28125q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819214 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.313232 -6.578125l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.078796 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m606.1778 23.019379l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m606.1778 23.019379l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path fill="#434343" d="m623.29395 48.813393l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510254 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#434343" d="m618.10614 70.81339l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4122925 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.6033325 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672546 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719482 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360107 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m192.52992 22.986877l110.07875 0l0 59.74803l-110.07875 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m192.52992 22.986877l110.07875 0l0 59.74803l-110.07875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m228.21759 59.78089l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382172 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097946 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566681 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m331.2466 43.238846l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m331.2466 43.238846l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path fill="#434343" d="m343.97073 69.03286l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.667694 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031952 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277069 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813202 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.665802 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.735077 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426941 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.504181 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m373.06628 91.03286l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m324.0777 33.12861l110.078766 0l0 59.74803l-110.078766 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m324.0777 33.12861l110.078766 0l0 59.74803l-110.078766 0z" fill-rule="nonzero"></path><path fill="#434343" d="m336.80185 58.922623l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.667694 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031952 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277069 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813202 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.8593445 0.3125 -1.8437195 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.0312195 -0.25 2.9843445 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.9062195 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.2499695 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.7343445 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.9687195 0 1.7187195 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281952 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m365.8974 80.92262l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474091 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m316.90097 22.986877l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m316.90097 22.986877l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path fill="#434343" d="m354.05658 59.78089l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.8551941 -1.4375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m302.60867 52.860893l7.165344 0l0 0.062992096l7.165344 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m302.60864 52.860893l7.1653748 0l0 0.062992096l1.1653442 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m310.93936 54.57562l4.5381165 -1.6517334l-4.5381165 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m426.9797 52.860893l27.904388 0l0 0.062992096l27.906647 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m426.9797 52.860893l27.904388 0l0 0.062992096l21.906616 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m476.7907 54.57562l4.5381165 -1.6517334l-4.5381165 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m592.8982 52.860893l6.6398315 0l0 0.062992096l6.6514893 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m592.8982 52.860893l6.6398315 0l0 0.062992096l0.6515503 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m600.1896 54.57562l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 22.986877l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 22.986877l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 55.015266l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 52.860893l28.15747 0l0 0.062992096l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 52.860893l28.15747 0l0 0.062992096l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.57562l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 120.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 120.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m50.508137 146.78088l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176067 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.2656212 -1.328125 -1.2656212 -3.734375q0 -2.484375 1.2656212 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.613571 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.4062576 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.6562576 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.500717 5.875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 164.01526l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 150.86089l28.15747 0l0 -98.01574l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 150.86089l28.15747 0l0 -98.01574l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.496876l4.538101 -1.6517296l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 190.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 190.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m50.508137 216.78088l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176067 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.2656212 -1.328125 -1.2656212 -3.734375q0 -2.484375 1.2656212 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.613571 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.4062576 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.6562576 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.500717 5.875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 234.01526l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 220.86089l28.15747 0l0 -168.0l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 220.86089l28.15747 0l0 -168.0l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.512627l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m5.1522307 4.632546l165.85826 0l0 283.52756l-165.85826 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m5.1522307 4.632546l165.85826 0l0 283.52756l-165.85826 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m24.73604 275.0801l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q0.9999981 0.171875 1.6718731 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984373 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.468748 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.1249981 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.412321 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.603302 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281967 -6.734375l0 -1.9375l1.65625 0l0 1.9375l-1.65625 0zm-2.125 15.484375l0.3125 -1.421875q0.5 0.125 0.796875 0.125q0.515625 0 0.765625 -0.34375q0.25 -0.328125 0.25 -1.6875l0 -10.359375l1.65625 0l0 10.390625q0 1.828125 -0.46875 2.546875q-0.59375 0.921875 -2.0 0.921875q-0.671875 0 -1.3125 -0.171875zm13.019821 -7.0l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 2.265625l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm6.9291077 1.5l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.443573 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.129196 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610092 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.191696 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.129196 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125732 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m464.70865 4.632546l270.23624 0l0 129.10236l-270.23624 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m464.70865 4.632546l270.23624 0l0 129.10236l-270.23624 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m536.47687 120.65491l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510254 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm14.808289 -3.796875l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4436035 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610046 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078857 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.1917114 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125671 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path></g></svg>
-
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 1ddc1bf4d7e..2c799e83a5f 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -1,4 +1,4 @@
-# Getting started with interactive web terminals
+# Getting started with interactive web terminals **[CORE ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
@@ -6,10 +6,9 @@ Interactive web terminals give the user access to a terminal in GitLab for
running one-off commands for their CI pipeline.
NOTE: **Note:**
-This is not available for the shared Runners on GitLab.com.
-To make use of this feature, you need to provide your
-[own Runner](https://docs.gitlab.com/runner/install/) and properly
-[configure it](#configuration).
+GitLab.com does not support interactive web terminal at the moment. Please
+follow [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611) for
+progress.
## Configuration
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index 3fd54647abb..91a0ae327bf 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -61,7 +61,7 @@ For a list of supported languages on JUnit tests, check the
[Wikipedia article](https://en.wikipedia.org/wiki/JUnit#Ports).
To enable the JUnit reports in merge requests, you need to add
-[`artifacts:reports:junit`](yaml/README.md#artifacts-reports-junit)
+[`artifacts:reports:junit`](yaml/README.md#artifactsreportsjunit)
in `.gitlab-ci.yml`, and specify the path(s) of the generated test reports.
In the following examples, the job in the `test` stage runs and GitLab
@@ -69,6 +69,10 @@ collects the JUnit test report from each job. After each job is executed, the
XML reports are stored in GitLab as artifacts and their results are shown in the
merge request widget.
+NOTE: **Note:**
+If you also want the ability to browse JUnit output files, include the
+[`artifacts:paths`](yaml/README.md#artifactspaths) keyword.
+
### Ruby example
Use the following job in `.gitlab-ci.yml`:
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index c628895ee1a..f8cf9de9aff 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -27,7 +27,7 @@ GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeli
There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
-![Types of Pipelines](img/types-of-pipelines.svg)
+![Types of Pipelines](img/types-of-pipelines.png)
1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`.
1. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production.
@@ -43,7 +43,7 @@ Pipelines accommodate several development workflows:
Example continuous delivery flow:
-![CD Flow](img/pipelines-goal.svg)
+![CD Flow](img/pipelines-goal.png)
## Jobs
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index 554c321fd0c..36f71427ae7 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -43,7 +43,7 @@ sudo apt-get install redis-server
Verify that you can connect to the server with the `gitlab-runner` user:
```bash
-# Try connecting the the Redis server
+# Try connecting the Redis server
sudo -u gitlab-runner -H redis-cli
# Quit the session
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 02f198dbd67..e46b2bbc79c 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -400,7 +400,7 @@ except master.
> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4
CAUTION: **Warning:**
-This an _alpha_ feature, and it it subject to change at any time without
+This an _alpha_ feature, and it is subject to change at any time without
prior notice!
Since GitLab 10.0 it is possible to define a more elaborate only/except job
@@ -1297,13 +1297,17 @@ GitLab 11.2. Requires GitLab Runner 11.2 and above.
The `reports` keyword is used for collecting test reports from jobs and
exposing them in GitLab's UI (merge requests, pipeline views). Read how to use
-this with [JUnit reports](#artifacts-reports-junit).
+this with [JUnit reports](#artifactsreportsjunit).
NOTE: **Note:**
The test reports are collected regardless of the job results (success or failure).
You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration
date for their artifacts.
+NOTE: **Note:**
+If you also want the ability to browse the report output files, include the
+[`artifacts:paths`](#artifactspaths) keyword.
+
#### `artifacts:reports:junit`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in
@@ -1312,8 +1316,9 @@ GitLab 11.2. Requires GitLab Runner 11.2 and above.
The `junit` report collects [JUnit XML files](https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html)
as artifacts. Although JUnit was originally developed in Java, there are many
[third party ports](https://en.wikipedia.org/wiki/JUnit#Ports) for other
-languages like Javascript, Python, Ruby, etc.
+languages like JavaScript, Python, Ruby, etc.
+See [JUnit test reports](../junit_test_reports.md) for more details and examples.
Below is an example of collecting a JUnit XML file from Ruby's RSpec test tool:
```yaml
@@ -1330,8 +1335,6 @@ rspec:
The collected JUnit reports will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests.
-For more examples, see [JUnit test reports](../junit_test_reports.md).
-
NOTE: **Note:**
In case the JUnit tool you use exports to multiple XML files, you can specify
multiple test report paths within a single job and they will be automatically
@@ -1532,7 +1535,7 @@ test:
```
By default, a job will be retried on all failure cases. To have a better control
-on which failures to retry, `retry` can be a hash with with the following keys:
+on which failures to retry, `retry` can be a hash with the following keys:
- `max`: The maximum number of retries.
- `when`: The failure cases to retry.
@@ -1818,13 +1821,6 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers,
thus allowing to fine tune them.
-To turn off global defined variables in a specific job, define an empty hash:
-
-```yaml
-job_name:
- variables: {}
-```
-
Except for the user defined variables, there are also the ones [set up by the
Runner itself](../variables/README.md#predefined-variables-environment-variables).
One example would be `CI_COMMIT_REF_NAME` which has the value of
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 01d99c46f89..931a7a8e6d5 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -42,7 +42,7 @@ run: unicorn: (pid 30960) 14204s; run: log: (pid 13809) 2432047s
GitLab can be considered to have two layers from a process perspective:
- **Monitoring**: Anything from this layer is not required to deliver GitLab the application, but will allow administrators more insight into their infrastructure and what the service as a whole is doing.
-- **Core**: Any process that is vital for the delivery of GitLab as as platform. If any of these processes halt there will be a GitLab outage. For the Core layer, you can further divide into:
+- **Core**: Any process that is vital for the delivery of GitLab as a platform. If any of these processes halt there will be a GitLab outage. For the Core layer, you can further divide into:
- **Processors**: These processes are responsible for actually performing operations and presenting the service.
- **Data**: These services store/expose structured data for the GitLab service.
@@ -86,7 +86,7 @@ GitLab is comprised of a large number of services that all log. We started bundl
- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/nginx.html)
- Layer: Core Service (Processor)
-Nginx as as an ingress port for all HTTP requests and routes them to the approriate sub-systems within GitLab. We are bundling an unmodified version of the popular open source webserver.
+Nginx as an ingress port for all HTTP requests and routes them to the approriate sub-systems within GitLab. We are bundling an unmodified version of the popular open source webserver.
### node-exporter
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 52710e54e86..8a186df7f06 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -5,7 +5,7 @@ having your code reviewed.
All merge requests for GitLab CE and EE, whether written by a GitLab team member
or a volunteer contributor, must go through a code review process to ensure the
-code is effective, understandable, and maintainable.
+code is effective, understandable, maintainable, and secure.
## Getting your merge request reviewed, approved, and merged
@@ -20,12 +20,24 @@ importance of involving reviewer(s) in the section on the responsibility of the
If you need some guidance (e.g. it's your first merge request), feel free to ask
one of the [Merge request coaches][team].
+If you need assistance with security scans or comments, feel free to include the
+Security Team (`@gitlab-com/gl-security`) in the review.
+
Depending on the areas your merge request touches, it must be **approved** by one
or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer):
For approvals, we use the approval functionality found in the merge request
widget. Reviewers can add their approval by [approving additionally](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#adding-or-removing-an-approval).
+Getting your merge request **merged** also requires a maintainer. If it requires
+more than one approval, the last maintainer to review and approve it will also merge it.
+
+### Approval guidelines
+
+As described in the section on the responsibility of the maintainer below, you
+are recommended to get your merge request approved and merged by maintainer(s)
+from teams other than your own.
+
1. If your merge request includes backend changes [^1], it must be
**approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
1. If your merge request includes frontend changes [^1], it must be
@@ -39,12 +51,14 @@ widget. Reviewers can add their approval by [approving additionally](https://doc
1. If your merge request includes a new dependency or a filesystem change, it must be
**approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details.
-Getting your merge request **merged** also requires a maintainer. If it requires
-more than one approval, the last maintainer to review and approve it will also merge it.
+#### Security requirements
-As described in the section on the responsibility of the maintainer below, you
-are recommended to get your merge request approved and merged by maintainer(s)
-from other teams than your own.
+ 1. If your merge request is processing, storing, or transferring any kind of [RED or ORANGE data][https://docs.google.com/document/d/15eNKGA3zyZazsJMldqTBFbYMnVUSQSpU14lo22JMZQY/edit] (this is a confidential document), it must be
+ **approved by a [Security Engineer][team]**.
+ 1. If your merge request involves implementing, utilizing, or is otherwise related to any type of authentication, authorization, or session handling mechanism, it must be
+ **approved by a [Security Engineer][team]**.
+ 1. If your merge request has a goal which requires a cryptographic function such as: confidentiality, integrity, authentication, or non-repudiation, it must be
+ **approved by a [Security Engineer][team]**.
### The responsibility of the merge request author
@@ -54,9 +68,10 @@ merge request author.
Before assigning a merge request to a maintainer for approval and merge, they
should be confident that it actually solves the problem it was meant to solve,
that it does so in the most appropriate way, that it satisfies all requirements,
-and that there are no remaining bugs, logical problems, or uncovered edge cases.
-The merge request should also have a completed task list in its description and
-a passing CI pipeline to avoid unnecessary back and forth.
+and that there are no remaining bugs, logical problems, uncovered edge cases,
+or known vulnerabilities. The merge request should also have a completed task
+list in its description and a passing CI pipeline to avoid unnecessary back and
+forth.
To reach the required level of confidence in their solution, an author is expected
to involve other people in the investigation and implementation processes as
@@ -72,6 +87,23 @@ If an author is unsure if a merge request needs a domain expert's opinion, that'
usually a pretty good sign that it does, since without it the required level of
confidence in their solution will not have been reached.
+Before the review, the author is requested to submit comments on the merge
+request diff alerting the reviewer to anything important as well as for anything
+that demands further explanation or attention. Examples of content that may
+warrant a comment could be:
+
+- The addition of a linting rule (Rubocop, JS etc)
+- The addition of a library (Ruby gem, JS lib etc)
+- Where not obvious, a link to the parent class or method
+- Any benchmarking performed to complement the change
+- Potentially insecure code
+
+Do not add these comments directly to the source code, unless the
+reviewer requires you to do so.
+
+This
+[saves reviewers time and helps authors catch mistakes earlier](https://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/index.html#__RefHeading__97_174136755).
+
### The responsibility of the maintainer
Maintainers are responsible for the overall health, quality, and consistency of
@@ -85,8 +117,8 @@ Since a maintainer's job only depends on their knowledge of the overall GitLab
codebase, and not that of any specific domain, they can review, approve and merge
merge requests from any team and in any product area.
-In fact, authors are recommended to get their merge requests merged by maintainers
-from other teams than their own, to ensure that all code across GitLab is consistent
+In fact, authors are encouraged to get their merge requests merged by maintainers
+from teams other than their own, to ensure that all code across GitLab is consistent
and can be easily understood by all contributors, from both inside and outside the
company, without requiring team-specific expertise.
@@ -103,6 +135,18 @@ as the maintainer to ultimately approve and merge it.
Maintainers should check before merging if the merge request is approved by the
required approvers.
+Maintainers must check before merging if the merge request is introducing new
+vulnerabilities, by inspecting the list in the Merge Request [Security
+Widget](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate).
+When in doubt, a [Security Engineer][team] can be involved. The list of detected
+vulnerabilities must be either empty or containing:
+
+- dismissed vulnerabilities in case of false positives
+- vulnerabilities converted to issues
+
+Maintainers should **never** dismiss vulnerabilities to "empty" the list,
+without duly verifying them.
+
## Best practices
### Everyone
@@ -153,7 +197,7 @@ first time.
### Assigning a merge request for a review
-If you want to have your merge request reviewed you can assign it to any reviewer. The list of reviewers can be found on [Engineering projects](https://about.gitlab.com/handbook/engineering/projects/) page.
+If you want to have your merge request reviewed, you can assign it to any reviewer. The list of reviewers can be found on [Engineering projects](https://about.gitlab.com/handbook/engineering/projects/) page.
You can also use `ready for review` label. That means that your merge request is ready to be reviewed and any reviewer can pick it. It is recommended to use that label only if there isn't time pressure and make sure the merge request is assigned to a reviewer.
@@ -189,6 +233,9 @@ experience, refactors the existing code). Then:
subsequent revisions for anything that would be spotted after that.
- Consider using the [Squash and
merge][squash-and-merge] feature when the merge request has a lot of commits.
+ When merging code a maintainer should only use the squash feature if the
+ author has already set this option or if the merge request clearly contains a
+ messy commit history that is intended to be squashed.
[squash-and-merge]: https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html#squash-and-merge
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 233dc83f95b..7c7da50a149 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -175,7 +175,7 @@ Severity levels can be applied further depending on the facet of the impact; e.g
| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | |
| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future |
| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future |
-| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely |
+| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on GitLab.com | Degradation _may_ occur but it's not likely |
## Label for community contributors
diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md
index e4ff72aa349..97762a62a80 100644
--- a/doc/development/db_dump.md
+++ b/doc/development/db_dump.md
@@ -13,7 +13,7 @@ large database imports.
```
# On STAGING
echo "postgresql['checkpoint_segments'] = 64" | sudo tee -a /etc/gitlab/gitlab.rb
-sudo touch /etc/gitlab/skip-auto-migrations
+sudo touch /etc/gitlab/skip-auto-reconfigure
sudo gitlab-ctl reconfigure
sudo gitlab-ctl stop unicorn
sudo gitlab-ctl stop sidekiq
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 9aea03139ee..a227e2f6640 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -419,7 +419,7 @@ view. For instance the approval code in the project's settings page.
**Mitigations**
Blocks of code that are EE-specific should be moved to partials. This
-avoids conflicts with big chunks of HAML code that that are not fun to
+avoids conflicts with big chunks of HAML code that are not fun to
resolve when you add the indentation to the equation.
EE-specific views should be placed in `ee/app/views/`, using extra
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index 112ff3419d9..ce96a9fc8ae 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -123,7 +123,7 @@ droplab.init().addData([{
```
Alternatively, you can specify a specific dropdown to add this data to but passing
-the data as the second argument and and the `id` of the trigger element as the first argument.
+the data as the second argument and the `id` of the trigger element as the first argument.
```html
<a href="#" data-dropdown-trigger="#list" id="trigger">Toggle</a>
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
new file mode 100644
index 00000000000..f55f01720fd
--- /dev/null
+++ b/doc/development/fe_guide/graphql.md
@@ -0,0 +1,83 @@
+# GraphQL
+
+We use [Apollo] and [Vue Apollo][vue-apollo] for working with GraphQL
+on the frontend.
+
+In order to use GraphQL, you need to enable the `graphql` feature flag,
+read more about [Feature Flags][feature-flags].
+
+## Apollo Client
+
+To save duplicated clients getting created in different apps, we have a
+[default client][defualt-client] that should be used. This setups the
+Apollo client with the correct URL and also sets the CSRF headers.
+
+## GraphQL Queries
+
+To save query compilation at runtime, webpack can directly import `.graphql`
+files. This allows webpack to preprocess the query at compile time instead
+of the client doing compilation of queries.
+
+## Usage in Vue
+
+To use Vue Apollo, import the [Vue Apollo][vue-apollo] plugin as well
+as the default client. This should be created at the same point
+the Vue application is mounted.
+
+```javascript
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import defaultClient from '~/lib/graphql';
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient,
+});
+
+new Vue({
+ ...,
+ apolloProvider,
+ ...
+});
+```
+
+Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
+
+### Testing
+
+With [Vue test utils][vue-test-utils] it is easy to quickly test components that
+fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
+the data on the component
+
+```javascript
+it('tests apollo component', () => {
+ const vm = shallowMount(App);
+
+ vm.setData({
+ ...mock data
+ });
+});
+```
+
+## Usage outside of Vue
+
+It is also possible to use GraphQL outside of Vue by directly importing
+and using the default client with queries.
+
+```javascript
+import defaultClient from '~/lib/graphql';
+import query from './query.graphql';
+
+defaultClient.query(query)
+ .then(result => console.log(result));
+```
+
+Read more about the [Apollo] client in the [Apollo documentation][apollo-client-docs].
+
+[Apollo]: https://www.apollographql.com/
+[vue-apollo]: https://github.com/Akryum/vue-apollo/
+[vue-apollo-docs]: https://akryum.github.io/vue-apollo/
+[feature-flags]: ../feature_flags.md
+[default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js
+[apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html
+[vue-test-utils]: https://vue-test-utils.vuejs.org/
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 11b9a2e6a64..cca3ad6fae6 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -54,6 +54,9 @@ Vuex specific design patterns and practices.
## [Axios](axios.md)
Axios specific practices and gotchas.
+## [GraphQL](graphql.md)
+How to use GraphQL
+
## [Icons and Illustrations](icons.md)
How we use SVG for our Icons and Illustrations.
diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md
index e860bde48dc..863ac049db6 100644
--- a/doc/development/github_importer.md
+++ b/doc/development/github_importer.md
@@ -131,7 +131,7 @@ our import as failed because of this.
To prevent this from happening we periodically refresh the expiration time of
the import process. This works by storing the JID of the import job in the
database, then refreshing this JID's TTL at various stages throughout the import
-process. This is done by calling `Project#refresh_import_jid_expiration`. By
+process. This is done by calling `ProjectImportState#refresh_jid_expiration`. By
refreshing this TTL we can ensure our import does not get marked as failed so
long we're still performing work.
diff --git a/doc/development/logging.md b/doc/development/logging.md
index abd08c420da..5c1d96b9e0c 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -75,7 +75,7 @@ To create a new file:
module Import
class Logger < ::Gitlab::JsonLogger
def self.file_name_noext
- 'importer_json'
+ 'importer'
end
end
end
@@ -105,7 +105,7 @@ To create a new file:
```ruby
# GOOD
- logger.info("Unable to create project", project_id: project.id)
+ logger.info(message: "Unable to create project", project_id: project.id)
```
1. Be sure to create a common base structure of your log messages. For example,
@@ -118,13 +118,13 @@ To create a new file:
```ruby
# BAD
- logger.info("Import error", error: 1)
- logger.info("Import error", error: "I/O failure")
+ logger.info(message: "Import error", error: 1)
+ logger.info(message: "Import error", error: "I/O failure")
```
```ruby
# GOOD
- logger.info("Import error", error_code: 1, error: "I/O failure")
+ logger.info(message: "Import error", error_code: 1, error: "I/O failure")
```
## Additional steps with new log files
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 7727bd74c3c..72abda26e3d 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -394,7 +394,7 @@ This is especially useful whenever it's showing 500 internal server error.
### Shared contexts
-All shared contexts should be be placed under `spec/support/shared_contexts/`.
+All shared contexts should be placed under `spec/support/shared_contexts/`.
Shared contexts can be placed in subfolder if they apply to a certain type of
specs only (e.g. features, requests etc.) but shouldn't be if they apply to
multiple type of specs.
@@ -404,7 +404,7 @@ Each file should include only one context and have a descriptive name, e.g.
### Shared examples
-All shared examples should be be placed under `spec/support/shared_examples/`.
+All shared examples should be placed under `spec/support/shared_examples/`.
Shared examples can be placed in subfolder if they apply to a certain type of
specs only (e.g. features, requests etc.) but shouldn't be if they apply to
multiple type of specs.
@@ -416,7 +416,7 @@ Each file should include only one context and have a descriptive name, e.g.
Helpers are usually modules that provide some methods to hide the complexity of
specific RSpec examples. You can define helpers in RSpec files if they're not
-intended to be shared with other specs. Otherwise, they should be be placed
+intended to be shared with other specs. Otherwise, they should be placed
under `spec/support/helpers/`. Helpers can be placed in subfolder if they apply
to a certain type of specs only (e.g. features, requests etc.) but shouldn't be
if they apply to multiple type of specs.
@@ -470,7 +470,7 @@ GitLab uses [factory_bot] as a test fixture replacement.
### Fixtures
-All fixtures should be be placed under `spec/fixtures/`.
+All fixtures should be placed under `spec/fixtures/`.
### Repositories
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 1830641431e..a6ed9e85a41 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -7,31 +7,36 @@ Review Apps are automatically deployed by each pipeline, both in
## How does it work?
1. On every [pipeline][gitlab-pipeline] during the `test` stage, the
- [`review` job][review-job] is automatically started.
-1. The `review` job [triggers a pipeline][cng-pipeline] in the
- [`CNG-mirror`][cng-mirror] project.
- - We use the `CNG-mirror` project so that the `CNG`, (**C**loud **N**ative
- **G**itLab), project's registry is not overloaded with a lot of transient
- Docker images.
-1. The `CNG-mirror` pipeline creates the Docker images of each component (e.g.
- `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.) based on the commit from the
- [GitLab pipeline][gitlab-pipeline] and store them in its
- [registry][cng-mirror-registry].
-1. Once all images are built, the Review App is deployed using
- [the official GitLab Helm chart][helm-chart] to the
- [`review-apps-ee` Kubernetes cluster on GCP][review-apps-ee]
- - The actual scripts used to deploy the Review App can be found at
- [`scripts/review_apps/review-apps.sh`][review-apps.sh]
- - These scripts are basically
- [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
- default CNG images are overridden with the images built and stored in the
- [`CNG-mirror` project's registry][cng-mirror-registry].
- - Since we're using [the official GitLab Helm chart][helm-chart], this means
- you get a dedicated environment for your branch that's very close to what it
- would look in production.
-1. Once the `review` job succeeds, you should be able to use your Review App
- thanks to the direct link to it from the MR widget. The default username is
- `root` and its password can be found in the 1Password secure note named
+ [`review-deploy`][review-deploy-job] job is automatically started.
+1. The `review-deploy` job:
+ 1. Waits for the `gitlab:assets:compile` job to finish since the
+ [`CNG-mirror`][cng-mirror] pipeline triggerred in the following step
+ depends on it.
+ 1. [Triggers a pipeline][cng-pipeline] in the [`CNG-mirror`][cng-mirror]
+ project.
+ - We use the `CNG-mirror` project so that the `CNG`, (**C**loud
+ **N**ative **G**itLab), project's registry is not overloaded with a
+ lot of transient Docker images.
+ - The `CNG-mirror` pipeline creates the Docker images of each component
+ (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.) based on the
+ commit from the [GitLab pipeline][gitlab-pipeline] and store them in
+ its [registry][cng-mirror-registry].
+ 1. Once all images are built by [`CNG-mirror`][cng-mirror], the Review App
+ is deployed using [the official GitLab Helm chart][helm-chart] to the
+ [`review-apps-ce`][review-apps-ce] / [`review-apps-ee`][review-apps-ee]
+ Kubernetes cluster on GCP.
+ - The actual scripts used to deploy the Review App can be found at
+ [`scripts/review_apps/review-apps.sh`][review-apps.sh].
+ - These scripts are basically
+ [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
+ default CNG images are overridden with the images built and stored in the
+ [`CNG-mirror` project's registry][cng-mirror-registry].
+ - Since we're using [the official GitLab Helm chart][helm-chart], this means
+ you get a dedicated environment for your branch that's very close to what
+ it would look in production.
+1. Once the `review-deploy` job succeeds, you should be able to use your Review
+ App thanks to the direct link to it from the MR widget. The default username
+ is `root` and its password can be found in the 1Password secure note named
**gitlab-{ce,ee} Review App's root password** (note that there's currently
[a bug where the default password seems to be overridden][password-bug]).
@@ -39,16 +44,23 @@ Review Apps are automatically deployed by each pipeline, both in
- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using
[GitLab's Kubernetes integration][gitlab-k8s-integration]. This basically
- allows to have a link to the Review App directly from the merge request widget.
-- The manual `stop_review` in the `test` stage can be used to stop a Review App
- manually, and is also started by GitLab once a branch is deleted.
-- Review Apps are cleaned up regularly using a pipeline schedule that runs
- the [`scripts/review_apps/automated_cleanup.rb`][automated_cleanup.rb] script.
+ allows to have a link to the Review App directly from the merge request
+ widget.
- If the Review App deployment fails, you can simply retry it (there's no need
- to run the `stop_review` job first).
-- If you're unable to log in using the `root` username and password, you may
- encounter [this bug][password-bug]. Stop the Review App via the `stop_review`
- manual job and then retry the `review` job to redeploy the Review App.
+ to run the [`review-stop`][gitlab-ci-yml] job first).
+- The manual [`review-stop`][gitlab-ci-yml] in the `test` stage can be used to
+ stop a Review App manually, and is also started by GitLab once a branch is
+ deleted.
+- Review Apps are cleaned up regularly using a pipeline schedule that runs
+ the [`schedule:review-cleanup`][gitlab-ci-yml] job.
+
+## QA runs
+
+On every [pipeline][gitlab-pipeline] during the `test` stage, the
+`review-qa-smoke` job is automatically started: it runs the smoke QA suite.
+You can also manually start the `review-qa-all`: it runs the full QA suite.
+
+Note that both jobs first wait for the `review-deploy` job to be finished.
## Frequently Asked Questions
@@ -74,15 +86,17 @@ find a way to limit it to only us.**
> This isn't enabled for forks.
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/35850709
-[review-job]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/118076368
+[review-deploy-job]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/118076368
[cng-mirror]: https://gitlab.com/gitlab-org/build/CNG-mirror
[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/35883435
[cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry
[helm-chart]: https://gitlab.com/charts/gitlab/
+[review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps
[review-apps-ee]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps
[review-apps.sh]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/review-apps.sh
[automated_cleanup.rb]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/automated_cleanup.rb
[Auto-DevOps.gitlab-ci.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+[gitlab-ci-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml
[gitlab-k8s-integration]: https://docs.gitlab.com/ee/user/project/clusters/index.html
[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index 3ebbd87d05f..583ff19bc69 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/foundations/motion'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com).
diff --git a/doc/development/ux_guide/basics.md b/doc/development/ux_guide/basics.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/basics.md
+++ b/doc/development/ux_guide/basics.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/features.md b/doc/development/ux_guide/features.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/features.md
+++ b/doc/development/ux_guide/features.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/illustrations.md b/doc/development/ux_guide/illustrations.md
index fa67d7c2d74..ed072b6515f 100644
--- a/doc/development/ux_guide/illustrations.md
+++ b/doc/development/ux_guide/illustrations.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/foundations/illustration/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/index.md b/doc/development/ux_guide/index.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/index.md
+++ b/doc/development/ux_guide/index.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/principles.md b/doc/development/ux_guide/principles.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/principles.md
+++ b/doc/development/ux_guide/principles.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/resources.md b/doc/development/ux_guide/resources.md
index 895dd5fe831..baec235a8dd 100644
--- a/doc/development/ux_guide/resources.md
+++ b/doc/development/ux_guide/resources.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/resources/design-resources'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/surfaces.md b/doc/development/ux_guide/surfaces.md
index cec937da99b..1e84bf608f4 100644
--- a/doc/development/ux_guide/surfaces.md
+++ b/doc/development/ux_guide/surfaces.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/tips.md b/doc/development/ux_guide/tips.md
index 964ea6aae7d..1e84bf608f4 100644
--- a/doc/development/ux_guide/tips.md
+++ b/doc/development/ux_guide/tips.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/). \ No newline at end of file
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index 9258073cc14..11bac11257c 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -2,4 +2,4 @@
redirect_to: 'https://design.gitlab.com/getting-started/personas/'
---
-The content of this documented was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 53fe1a6b25b..ce61ace60a0 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -89,7 +89,7 @@ We'll now create a VPC, a virtual networking environment that you'll control:
### Subnets
Now, let's create some subnets in different Availability Zones. Make sure
-that each subnet is associated the the VPC we just created and
+that each subnet is associated to the VPC we just created and
that CIDR blocks don't overlap. This will also
allow us to enable multi AZ for redundancy.
@@ -168,7 +168,7 @@ The security group is basically the firewall:
1. Select **Security Groups** from the left menu.
1. Click **Create Security Group** and fill in the details. Give it a name,
add a description, and choose the VPC we created previously
-1. Select the security group from the list and at the the bottom select the
+1. Select the security group from the list and at the bottom select the
Inbound Rules tab. You will need to open the SSH, HTTP, and HTTPS ports. Set
the source to `0.0.0.0/0`.
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index 7835401cc0b..19a6e46f969 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -37,7 +37,7 @@ Once you have an Azure account, you can get started. Login to Azure using
![Azure Dashboard](img/azure-dashboard.png)
-The Dashboard gives you a quick overview of Azure resources, and from here you you can build VMs,
+The Dashboard gives you a quick overview of Azure resources, and from here you can build VMs,
create SQL Databases, author websites, and perform lots of other cloud tasks.
## Create New VM
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index f34d398a7f5..3c2f883f29d 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -84,7 +84,7 @@ rbac:
##
# serviceAccountName: default
-## Configuration for the Pods that that the runner launches for each new job
+## Configuration for the Pods that the runner launches for each new job
##
runners:
## Default container image to use for builds when none is specified
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 7a83b8e4b35..b8156b2b593 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -1,28 +1,32 @@
-# Integrate your server with GitHub
+# Integrate your GitLab instance with GitHub
-Import projects from GitHub and login to your GitLab instance with your GitHub account.
+You can integrate your GitLab instance with GitHub.com as well as GitHub Enterprise to enable users to import projects from GitHub and/or to login to your GitLab instance with your GitHub account.
-To enable the GitHub OmniAuth provider you must register your application with GitHub.
-GitHub will generate an application ID and secret key for you to use.
+## Enabling GitHub OAuth
-1. Sign in to GitHub.
+To enable GitHub OmniAuth provider, you must use GitHub's credentials for your GitLab instance.
+To get the credentials (a pair of Client ID and Client Secret), you must register an application as an OAuth App on GitHub.
-1. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
+1. Sign in to GitHub.
-1. Select "OAuth applications" in the left menu.
+1. Navigate to your individual user or organization settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
-1. If you already have applications listed, switch to the "Developer applications" tab.
+ - For individual accounts, select **Developer settings** from the left menu, then select **OAuth Apps**.
+ - For organization accounts, directly select **OAuth Apps** from the left menu.
-1. Select "Register new application".
+1. Select **Register an application** (if you don't have any OAuth App) or **New OAuth App** (if you already have OAuth Apps).
+ ![Register OAuth App](img/github_app_entry.png)
1. Provide the required details.
- Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- - Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
+ - Homepage URL: the URL to your GitLab installation. e.g., `https://gitlab.company.com`
- Application description: Fill this in if you wish.
- - Authorization callback URL is 'http(s)://${YOUR_DOMAIN}'. Please make sure the port is included if your GitLab instance is not configured on default port.
-1. Select "Register application".
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ ![Register OAuth App](img/github_register_app.png)
+
+1. Select **Register application**.
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+1. You should now see a pair of **Client ID** and **Client Secret** near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
![GitHub app](img/github_app.png)
@@ -97,9 +101,9 @@ GitHub will generate an application ID and secret key for you to use.
__Replace `https://github.example.com/` with your GitHub URL.__
-1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7.
+1. Change `YOUR_APP_ID` to the Client ID from the GitHub application page from step 6.
-1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
+1. Change `YOUR_APP_SECRET` to the Client Secret from the GitHub application page from step 6.
1. Save the configuration file.
diff --git a/doc/integration/img/github_app.png b/doc/integration/img/github_app.png
index d6c289a1de1..4a1523d41ac 100644
--- a/doc/integration/img/github_app.png
+++ b/doc/integration/img/github_app.png
Binary files differ
diff --git a/doc/integration/img/github_app_entry.png b/doc/integration/img/github_app_entry.png
new file mode 100644
index 00000000000..9e151f8cdff
--- /dev/null
+++ b/doc/integration/img/github_app_entry.png
Binary files differ
diff --git a/doc/integration/img/github_register_app.png b/doc/integration/img/github_register_app.png
new file mode 100644
index 00000000000..edd3f660f4e
--- /dev/null
+++ b/doc/integration/img/github_register_app.png
Binary files differ
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 18c9cd116f1..a63656fafef 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -604,6 +604,8 @@ If you fail to restore this encryption key file along with the application data
backup, users with two-factor authentication enabled and GitLab Runners will
lose access to your GitLab server.
+You may also want to restore any TLS keys, certificates, or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
Depending on your case, you might want to run the restore command with one or
more of the following options:
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9114b8d8417..d9ae3c08172 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -75,7 +75,7 @@ The minimum key size is 1024 bits, defaulting to 2048. If you wish to generate a
stronger RSA key pair, specify the `-b` flag with a higher bit value than the
default.
-The old, default password encoding for SSH private keys keys is
+The old, default password encoding for SSH private keys is
[insecure](https://latacora.singles/2018/08/03/the-default-openssh.html);
it's only a single round of an MD5 hash. Since OpenSSH version 6.5, you should
use the `-o` option to `ssh-keygen` to encode your private key in a new, more
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 394f3ea60b7..766a23c419d 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -44,6 +44,5 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth)
- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin)
-- [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/)
- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index 58d86f7d387..d7e1979217e 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -22,7 +22,7 @@ an extensive selection of dependency managed libraries and applications.
If you are sure you don't need access to any additional development libraries
or don't have approximately 15gb of available disk space for Xcode and Homebrew
-use one of the the aforementioned methods.
+use one of the aforementioned methods.
### Installing Xcode
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 6e0f71017c6..7c7e44d29e7 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -689,7 +689,7 @@ A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building softwa
### Webhooks
-A way for for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
+A way for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
### Wiki
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 74ce52859fa..3c73bc573a6 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -269,7 +269,7 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
GitLab 9.0.11 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
-a dependency on on the `re2` regular expression library. To install this dependency:
+a dependency on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index 3a806d2f8c8..7c9dacc9b90 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -269,7 +269,7 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
GitLab 9.1.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
-a dependency on on the `re2` regular expression library. To install this dependency:
+a dependency on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index 2fff6544797..b815242ab4e 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -227,7 +227,7 @@ sudo systemctl daemon-reload
### 10. Install libs, migrations, etc.
GitLab 9.2.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
-a dependency on on the `re2` regular expression library. To install this dependency:
+a dependency on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 1b36cf53f4c..a58b12cb81c 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -263,7 +263,7 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
GitLab 9.3.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
-a dependency on on the `re2` regular expression library. To install this dependency:
+a dependency on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index 210b6eb607d..0c87468334b 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -276,7 +276,7 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
GitLab 9.4 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
-a dependency on on the `re2` regular expression library. To install this dependency:
+a dependency on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 9801a0a14ed..d3ecfd42903 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -4,7 +4,7 @@ You can block email addresses of specific domains, or whitelist only some
specific domains via the **Application Settings** in the Admin area.
>**Note**: These restrictions are only applied during sign-up. An admin is
-able to add add a user through the admin panel with a disallowed domain. Also
+able to add a user through the admin panel with a disallowed domain. Also
note that the users can change their email addresses after signup to
disallowed domains.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 6735710e2bb..2aa7c7ef815 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -238,13 +238,10 @@ by GitLab before installing any of the above applications.
## Getting the external IP address
NOTE: **Note:**
-You need a load balancer installed in your cluster in order to obtain the
-external IP address with the following procedure. It can be deployed using the
-[**Ingress** application](#installing-applications).
-
-NOTE: **Note:**
-Knative will include its own load balancer in the form of [Istio](https://istio.io).
-At this time, to determine the external IP address, you will need to follow the manual approach.
+With the following procedure, a load balancer must be installed in your cluster
+to obtain the external IP address. You can use either
+[Ingress](#installing-applications), or Knative's own load balancer
+([Istio](https://istio.io)) if using [Knative](#installing-applications).
In order to publish your web application, you first need to find the external IP
address associated to your load balancer.
@@ -253,7 +250,7 @@ address associated to your load balancer.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
-If you installed the Ingress [via the **Applications**](#installing-applications),
+If you [installed Ingress or Knative](#installing-applications),
you should see the Ingress IP address on this same page within a few minutes.
If you don't see this, GitLab might not be able to determine the IP address of
your ingress application in which case you should manually determine it.
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index 925b969eebe..b46b995d8bb 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_core.png b/doc/user/project/img/issue_boards_core.png
index 9e819160861..8bc187482ad 100644
--- a/doc/user/project/img/issue_boards_core.png
+++ b/doc/user/project/img/issue_boards_core.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_premium.png b/doc/user/project/img/issue_boards_premium.png
index bd9164b2961..4e238ea6983 100644
--- a/doc/user/project/img/issue_boards_premium.png
+++ b/doc/user/project/img/issue_boards_premium.png
Binary files differ
diff --git a/doc/user/project/issues/img/issue_board.png b/doc/user/project/issues/img/issue_board.png
index df9d6f64985..c75c35a382e 100644
--- a/doc/user/project/issues/img/issue_board.png
+++ b/doc/user/project/issues/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index f7119f3bf3c..9c045dfcce4 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -97,7 +97,7 @@ From the group issue list page and the group merge request list page, you can [f
## Subscribing to labels
-From the project label list page and the group label list page, you can subscribe to [notifications](../../workflow/notifications.md) of a given label, to alert you that that label has been assigned to an issue or merge request.
+From the project label list page and the group label list page, you can subscribe to [notifications](../../workflow/notifications.md) of a given label, to alert you that the label has been assigned to an issue or merge request.
![Labels subscriptions](img/labels_subscriptions.png)
diff --git a/doc/user/project/merge_requests/img/merge_request_widget.png b/doc/user/project/merge_requests/img/merge_request_widget.png
index 6c2317b29b5..58562fcb034 100644
--- a/doc/user/project/merge_requests/img/merge_request_widget.png
+++ b/doc/user/project/merge_requests/img/merge_request_widget.png
Binary files differ
diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md
index ecbc8534eea..ccef2853e3f 100644
--- a/doc/user/project/merge_requests/resolve_conflicts.md
+++ b/doc/user/project/merge_requests/resolve_conflicts.md
@@ -1,15 +1,31 @@
-# Merge conflict resolution
+# Merge request conflict resolution
-> [Introduced][ce-5479] in GitLab 8.11.
+Merge conflicts occur when two branches have different changes that cannot be
+merged automatically.
-When a merge request has conflicts, GitLab may provide the option to resolve
-those conflicts in the GitLab UI. (See
-[conflicts available for resolution](#conflicts-available-for-resolution) for
-more information on when this is available.) If this is an option, you will see
-a **resolve these conflicts** link in the merge request widget:
+Git is able to automatically merge changes between branches in most cases, but
+there are situations where Git will require your assistance to resolve the
+conflicts manually. Typically, this is necessary when people change the same
+parts of the same files.
+
+GitLab will prevent merge requests from being merged until all conflicts are
+resolved. Conflicts can be resolved locally, or in many cases within GitLab
+(see [conflicts available for resolution](#conflicts-available-for-resolution)
+for information on when this is available).
![Merge request widget](img/merge_request_widget.png)
+NOTE: **Note:**
+GitLab resolves conflicts by creating a merge commit in the source branch that
+is not automatically merged into the target branch. This allows the merge
+commit to be reviewed and tested before the changes are merged, preventing
+unintended changes entering the target branch without review or breaking the
+build.
+
+## Resolve conflicts: interactive mode
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479) in GitLab 8.11.
+
Clicking this will show a list of files with conflicts, with conflict sections
highlighted:
@@ -21,9 +37,9 @@ request into the source branch, resolving the conflicts using the options
chosen. If the source branch is `feature` and the target branch is `master`,
this is similar to performing `git checkout feature; git merge master` locally.
-## Merge conflict editor
+## Resolve conflicts: inline editor
-> Introduced in GitLab 8.13.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6374) in GitLab 8.13.
The merge conflict resolution editor allows for more complex merge conflicts,
which require the user to manually modify a file in order to resolve a conflict,
@@ -50,5 +66,3 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For
example, this will not create a conflict: on branch `a`, doing `git mv file1
file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be
present in the branch after the merge request is merged.
-
-[ce-5479]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 7168fe63887..ac58a0b5c18 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -21,7 +21,7 @@ the milestone to the issue.
Milestones can be used as releases.
Set the milestone due date to represent the release date of your release.
(And leave the milestone start date blank.)
-Set the the milestone title to the version of your release,
+Set the milestone title to the version of your release,
such as `Version 9.4`.
Add an issue to your release by associating
the milestone to the issue.
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index 26891348b0c..247e8d2a6a0 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -11,7 +11,7 @@ date: 2017-02-22
Setting up GitLab Pages with custom domains, and adding SSL/TLS certificates to them, are optional features of GitLab Pages.
-These steps assume you've already [set your site up](getting_started_part_two.md) and and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`.
+These steps assume you've already [set your site up](getting_started_part_two.md) and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`.
## Adding your custom domain to GitLab Pages
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 60144fa1971..7de9ae0caea 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -166,7 +166,7 @@ with Pages, read through this series:
If you're using GitLab Pages default domain (`.gitlab.io`), your website will be
automatically secure and available under HTTPS. If you're using your own domain, you can
-optionally secure it with with SSL/TLS certificates. You can read the following
+optionally secure it with SSL/TLS certificates. You can read the following
tutorials to learn how to use these third-party certificates with GitLab Pages:
- [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 9f9b64ec20d..ed049e2e648 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -446,7 +446,9 @@ See also: [GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages doma
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
NOTE: **Note:**
-GitLab Pages access control is not activated on GitLab.com.
+GitLab Pages access control is not activated on GitLab.com. You can check its
+progress on the
+[infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5576).
You can enable Pages access control on your project, so that only
[members of your project](../../permissions.md#project-members-permissions)
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index c590ac4b0ba..020aa73f809 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -64,6 +64,8 @@ Below is the table of events users can be notified of:
|------------------------------|-------------------------------------------------------------------|------------------------------|
| New SSH key added | User | Security email, always sent. |
| New email added | User | Security email, always sent. |
+| Email changed | User | Security email, always sent. |
+| Password changed | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for omniauth (LDAP)|
| User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed |
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 8e259961828..449faf5f8da 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -50,6 +50,10 @@ module API
rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
+ rescue_from ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError do
+ rack_response({ 'message' => '409 Conflict: Resource lock' }.to_json, 409)
+ end
+
rescue_from UploadedFile::InvalidPathError do |e|
rack_response({ 'message' => e.message }.to_json, 400)
end
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 61357b3f1d6..af9b519ed9e 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -94,6 +94,7 @@ module API
Gitlab::Auth::TokenNotFoundError,
Gitlab::Auth::ExpiredError,
Gitlab::Auth::RevokedError,
+ Gitlab::Auth::ImpersonationDisabled,
Gitlab::Auth::InsufficientScopeError]
base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
@@ -121,6 +122,11 @@ module API
:invalid_token,
"Token was revoked. You have to re-authorize from the user.")
+ when Gitlab::Auth::ImpersonationDisabled
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token is an impersonation token but impersonation was disabled.")
+
when Gitlab::Auth::InsufficientScopeError
# FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
# does not include WWW-Authenticate header, which breaks the standard.
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index cff05643f3b..5dbfbb85e9e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -145,7 +145,9 @@ module API
expose :import_status
# TODO: Use `expose_nil` once we upgrade the grape-entity gem
- expose :import_error, if: lambda { |status, _ops| status.import_error }
+ expose :import_error, if: lambda { |project, _ops| project.import_state&.last_error } do |project|
+ project.import_state.last_error
+ end
end
class BasicProjectDetails < ProjectIdentity
@@ -248,7 +250,10 @@ module API
expose :creator_id
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
- expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
+
+ expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
+ project.import_state&.last_error
+ end
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
@@ -710,6 +715,10 @@ module API
expose :diff_refs, using: Entities::DiffRefs
+ # Allow the status of a rebase to be determined
+ expose :merge_error
+ expose :rebase_in_progress?, as: :rebase_in_progress, if: -> (_, options) { options[:include_rebase_in_progress] }
+
expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] }
def build_available?(options)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 16f07f16387..595b3641c52 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -74,6 +74,19 @@ module API
options
end
+ def authorize_push_to_merge_request!(merge_request)
+ forbidden!('Source branch does not exist') unless
+ merge_request.source_branch_exists?
+
+ user_access = Gitlab::UserAccess.new(
+ current_user,
+ project: merge_request.source_project
+ )
+
+ forbidden!('Cannot push to source branch') unless
+ user_access.can_push_to_branch?(merge_request.source_branch)
+ end
+
params :merge_requests_params do
optional :state, type: String, values: %w[opened closed locked merged all], default: 'all',
desc: 'Return opened, closed, locked, merged, or all merge requests'
@@ -239,6 +252,7 @@ module API
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
optional :render_html, type: Boolean, desc: 'Returns the description and title rendered HTML'
optional :include_diverged_commits_count, type: Boolean, desc: 'Returns the commits count behind the target branch'
+ optional :include_rebase_in_progress, type: Boolean, desc: 'Returns whether a rebase operation is ongoing '
end
desc 'Get a single merge request' do
success Entities::MergeRequest
@@ -246,7 +260,13 @@ module API
get ':id/merge_requests/:merge_request_iid' do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
- present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project, render_html: params[:render_html], include_diverged_commits_count: params[:include_diverged_commits_count]
+ present merge_request,
+ with: Entities::MergeRequest,
+ current_user: current_user,
+ project: user_project,
+ render_html: params[:render_html],
+ include_diverged_commits_count: params[:include_diverged_commits_count],
+ include_rebase_in_progress: params[:include_rebase_in_progress]
end
desc 'Get the participants of a merge request' do
@@ -378,6 +398,19 @@ module API
.cancel(merge_request)
end
+ desc 'Rebase the merge request against its target branch' do
+ detail 'This feature was added in GitLab 11.6'
+ end
+ put ':id/merge_requests/:merge_request_iid/rebase' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
+
+ authorize_push_to_merge_request!(merge_request)
+
+ RebaseWorker.perform_async(merge_request.id, current_user.id)
+
+ status :accepted
+ end
+
desc 'List issues that will be closed on merge' do
success Entities::MRNote
end
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index a27f1d46863..c6a3a763c23 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -17,6 +17,9 @@ module Banzai
# This is a small extension to the CommonMark spec. If they start allowing
# spaces in urls, we could then remove this filter.
#
+ # Note: Filter::SanitizationFilter should always be run sometime after this filter
+ # to prevent XSS attacks
+ #
class SpacedLinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index be75e34a673..96bea7ca935 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -12,13 +12,16 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::PlantumlFilter,
+
+ # Must always be before the SanitizationFilter to prevent XSS attacks
+ Filter::SpacedLinkFilter,
+
Filter::SanitizationFilter,
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
Filter::ColorFilter,
Filter::MermaidFilter,
- Filter::SpacedLinkFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 655278da711..b2c8d46ede1 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -110,11 +110,6 @@ module ExtractsPath
# resolved (e.g., when a user inserts an invalid path or ref).
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def assign_ref_vars
- # assign allowed options
- allowed_options = ["filter_ref"]
- @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
- @options = HashWithIndifferentAccess.new(@options)
-
@id = get_id
@ref, @path = extract_ref(@id)
@repo = @project.repository
diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb
index cb9f2582936..176766d1a8b 100644
--- a/lib/gitlab/auth/request_authenticator.rb
+++ b/lib/gitlab/auth/request_authenticator.rb
@@ -13,12 +13,18 @@ module Gitlab
@request = request
end
- def user
- find_sessionless_user || find_user_from_warden
+ def user(request_formats)
+ request_formats.each do |format|
+ user = find_sessionless_user(format)
+
+ return user if user
+ end
+
+ find_user_from_warden
end
- def find_sessionless_user
- find_user_from_access_token || find_user_from_feed_token
+ def find_sessionless_user(request_format)
+ find_user_from_web_access_token(request_format) || find_user_from_feed_token(request_format)
rescue Gitlab::Auth::AuthenticationError
nil
end
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index c304adc64db..a5efe33bdc6 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -7,6 +7,7 @@ module Gitlab
TokenNotFoundError = Class.new(AuthenticationError)
ExpiredError = Class.new(AuthenticationError)
RevokedError = Class.new(AuthenticationError)
+ ImpersonationDisabled = Class.new(AuthenticationError)
UnauthorizedError = Class.new(AuthenticationError)
class InsufficientScopeError < AuthenticationError
@@ -27,8 +28,8 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request?
end
- def find_user_from_feed_token
- return unless rss_request? || ics_request?
+ def find_user_from_feed_token(request_format)
+ return unless valid_rss_format?(request_format)
# NOTE: feed_token was renamed from rss_token but both needs to be supported because
# users might have already added the feed to their RSS reader before the rename
@@ -38,6 +39,17 @@ module Gitlab
User.find_by_feed_token(token) || raise(UnauthorizedError)
end
+ # We only allow Private Access Tokens with `api` scope to be used by web
+ # requests on RSS feeds or ICS files for backwards compatibility.
+ # It is also used by GraphQL/API requests.
+ def find_user_from_web_access_token(request_format)
+ return unless access_token && valid_web_access_format?(request_format)
+
+ validate_access_token!(scopes: [:api])
+
+ access_token.user || raise(UnauthorizedError)
+ end
+
def find_user_from_access_token
return unless access_token
@@ -56,6 +68,8 @@ module Gitlab
raise ExpiredError
when AccessTokenValidationService::REVOKED
raise RevokedError
+ when AccessTokenValidationService::IMPERSONATION_DISABLED
+ raise ImpersonationDisabled
end
end
@@ -109,6 +123,26 @@ module Gitlab
@current_request ||= ensure_action_dispatch_request(request)
end
+ def valid_web_access_format?(request_format)
+ case request_format
+ when :rss
+ rss_request?
+ when :ics
+ ics_request?
+ when :api
+ api_request?
+ end
+ end
+
+ def valid_rss_format?(request_format)
+ case request_format
+ when :rss
+ rss_request?
+ when :ics
+ ics_request?
+ end
+ end
+
def rss_request?
current_request.path.ends_with?('.atom') || current_request.format.atom?
end
@@ -116,6 +150,10 @@ module Gitlab
def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics?
end
+
+ def api_request?
+ current_request.path.starts_with?("/api/")
+ end
end
end
end
diff --git a/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb b/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb
new file mode 100644
index 00000000000..29fa0f18448
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This module is used to write the full path of all projects to
+ # the git repository config file.
+ # Storing the full project path in the git config allows admins to
+ # easily identify a project when it is using hashed storage.
+ module BackfillProjectFullpathInRepoConfig
+ OrphanedNamespaceError = Class.new(StandardError)
+
+ module Storage
+ # Class that returns the disk path for a project using hashed storage
+ class HashedProject
+ attr_accessor :project
+
+ ROOT_PATH_PREFIX = '@hashed'
+
+ def initialize(project)
+ @project = project
+ end
+
+ def disk_path
+ "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}"
+ end
+
+ def disk_hash
+ @disk_hash ||= Digest::SHA2.hexdigest(project.id.to_s) if project.id
+ end
+ end
+
+ # Class that returns the disk path for a project using legacy storage
+ class LegacyProject
+ attr_accessor :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def disk_path
+ project.full_path
+ end
+ end
+ end
+
+ # Concern used by Project and Namespace to determine the full
+ # route to the project
+ module Routable
+ extend ActiveSupport::Concern
+
+ def full_path
+ @full_path ||= build_full_path
+ end
+
+ def build_full_path
+ return path unless has_parent?
+
+ raise OrphanedNamespaceError if parent.nil?
+
+ parent.full_path + '/' + path
+ end
+
+ def has_parent?
+ read_attribute(association(:parent).reflection.foreign_key)
+ end
+ end
+
+ # Class used to interact with repository using Gitaly
+ class Repository
+ attr_reader :storage
+
+ def initialize(storage, relative_path)
+ @storage = storage
+ @relative_path = relative_path
+ end
+
+ def gitaly_repository
+ Gitaly::Repository.new(storage_name: @storage, relative_path: @relative_path)
+ end
+ end
+
+ # Namespace can be a user or group. It can be the root or a
+ # child of another namespace.
+ class Namespace < ActiveRecord::Base
+ self.table_name = 'namespaces'
+ self.inheritance_column = nil
+
+ include Routable
+
+ belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
+ has_many :projects, inverse_of: :parent
+ has_many :namespaces, inverse_of: :parent
+ end
+
+ # Project is where the repository (etc.) is stored
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+
+ include Routable
+ include EachBatch
+
+ FULLPATH_CONFIG_KEY = 'gitlab.fullpath'
+
+ belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
+ delegate :disk_path, to: :storage
+
+ def add_fullpath_config
+ entries = { FULLPATH_CONFIG_KEY => full_path }
+
+ repository_service.set_config(entries)
+ end
+
+ def remove_fullpath_config
+ repository_service.delete_config([FULLPATH_CONFIG_KEY])
+ end
+
+ def cleanup_repository
+ repository_service.cleanup
+ end
+
+ def storage
+ @storage ||=
+ if hashed_storage?
+ Storage::HashedProject.new(self)
+ else
+ Storage::LegacyProject.new(self)
+ end
+ end
+
+ def hashed_storage?
+ self.storage_version && self.storage_version >= 1
+ end
+
+ def repository
+ @repository ||= Repository.new(repository_storage, disk_path + '.git')
+ end
+
+ def repository_service
+ @repository_service ||= Gitlab::GitalyClient::RepositoryService.new(repository)
+ end
+ end
+
+ # Base class for Up and Down migration classes
+ class BackfillFullpathMigration
+ RETRY_DELAY = 15.minutes
+ MAX_RETRIES = 2
+
+ # Base class for retrying one project
+ class BaseRetryOne
+ def perform(project_id, retry_count)
+ project = Project.find(project_id)
+
+ return unless project
+
+ migration_class.new.safe_perform_one(project, retry_count)
+ end
+ end
+
+ def perform(start_id, end_id)
+ Project.includes(:parent).where(id: start_id..end_id).each do |project|
+ safe_perform_one(project)
+ end
+ end
+
+ def safe_perform_one(project, retry_count = 0)
+ perform_one(project)
+ rescue GRPC::NotFound, GRPC::InvalidArgument, OrphanedNamespaceError
+ nil
+ rescue GRPC::BadStatus
+ schedule_retry(project, retry_count + 1) if retry_count < MAX_RETRIES
+ end
+
+ def schedule_retry(project, retry_count)
+ BackgroundMigrationWorker.perform_in(RETRY_DELAY, self.class::RetryOne.name, [project.id, retry_count])
+ end
+ end
+
+ # Class to add the fullpath to the git repo config
+ class Up < BackfillFullpathMigration
+ # Class used to retry
+ class RetryOne < BaseRetryOne
+ def migration_class
+ Up
+ end
+ end
+
+ def perform_one(project)
+ project.cleanup_repository
+ project.add_fullpath_config
+ end
+ end
+
+ # Class to rollback adding the fullpath to the git repo config
+ class Down < BackfillFullpathMigration
+ # Class used to retry
+ class RetryOne < BaseRetryOne
+ def migration_class
+ Down
+ end
+ end
+
+ def perform_one(project)
+ project.cleanup_repository
+ project.remove_fullpath_config
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/encrypt_columns.rb b/lib/gitlab/background_migration/encrypt_columns.rb
index 0d333e47e7b..bd5f12276ab 100644
--- a/lib/gitlab/background_migration/encrypt_columns.rb
+++ b/lib/gitlab/background_migration/encrypt_columns.rb
@@ -17,6 +17,12 @@ module Gitlab
class EncryptColumns
def perform(model, attributes, from, to)
model = model.constantize if model.is_a?(String)
+
+ # If sidekiq hasn't undergone a restart, its idea of what columns are
+ # present may be inaccurate, so ensure this is as fresh as possible
+ model.reset_column_information
+ model.define_attribute_methods
+
attributes = expand_attributes(model, Array(attributes).map(&:to_sym))
model.transaction do
@@ -41,6 +47,14 @@ module Gitlab
raise "Couldn't determine encrypted column for #{klass}##{attribute}" if
crypt_column_name.nil?
+ raise "#{klass} source column: #{attribute} is missing" unless
+ klass.column_names.include?(attribute.to_s)
+
+ # Running the migration without the destination column being present
+ # leads to data loss
+ raise "#{klass} destination column: #{crypt_column_name} is missing" unless
+ klass.column_names.include?(crypt_column_name.to_s)
+
[attribute, crypt_column_name]
end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 45e550b3450..eaead41a720 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -35,7 +35,7 @@ module Gitlab
def handle_errors
return unless errors.any?
- project.update_column(:import_error, {
+ project.import_state.update_column(:last_error, {
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index 15aa4739ee9..d4080536d81 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -56,7 +56,7 @@ module Gitlab
def handle_errors
return unless errors.any?
- project.update_column(:import_error, {
+ project.import_state.update_column(:last_error, {
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index 8eccd262db9..bf5f2a31f0e 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -3,9 +3,11 @@
module Gitlab
module Ci
class Trace
- include ExclusiveLeaseGuard
+ include ::Gitlab::ExclusiveLeaseHelpers
- LEASE_TIMEOUT = 1.hour
+ LOCK_TTL = 1.minute
+ LOCK_RETRIES = 2
+ LOCK_SLEEP = 0.001.seconds
ArchiveError = Class.new(StandardError)
AlreadyArchivedError = Class.new(StandardError)
@@ -82,24 +84,10 @@ module Gitlab
stream&.close
end
- def write(mode)
- stream = Gitlab::Ci::Trace::Stream.new do
- if trace_artifact
- raise AlreadyArchivedError, 'Could not write to the archived trace'
- elsif current_path
- File.open(current_path, mode)
- elsif Feature.enabled?('ci_enable_live_trace')
- Gitlab::Ci::Trace::ChunkedIO.new(job)
- else
- File.open(ensure_path, mode)
- end
+ def write(mode, &blk)
+ in_write_lock do
+ unsafe_write!(mode, &blk)
end
-
- yield(stream).tap do
- job.touch if job.needs_touch?
- end
- ensure
- stream&.close
end
def erase!
@@ -117,13 +105,33 @@ module Gitlab
end
def archive!
- try_obtain_lease do
+ in_write_lock do
unsafe_archive!
end
end
private
+ def unsafe_write!(mode, &blk)
+ stream = Gitlab::Ci::Trace::Stream.new do
+ if trace_artifact
+ raise AlreadyArchivedError, 'Could not write to the archived trace'
+ elsif current_path
+ File.open(current_path, mode)
+ elsif Feature.enabled?('ci_enable_live_trace')
+ Gitlab::Ci::Trace::ChunkedIO.new(job)
+ else
+ File.open(ensure_path, mode)
+ end
+ end
+
+ yield(stream).tap do
+ job.touch if job.needs_touch?
+ end
+ ensure
+ stream&.close
+ end
+
def unsafe_archive!
raise AlreadyArchivedError, 'Could not archive again' if trace_artifact
raise ArchiveError, 'Job is not finished yet' unless job.complete?
@@ -146,6 +154,11 @@ module Gitlab
end
end
+ def in_write_lock(&blk)
+ lock_key = "trace:write:lock:#{job.id}"
+ in_lock(lock_key, ttl: LOCK_TTL, retries: LOCK_RETRIES, sleep_sec: LOCK_SLEEP, &blk)
+ end
+
def archive_stream!(stream)
clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
create_build_trace!(job, clone_path)
@@ -226,16 +239,6 @@ module Gitlab
def trace_artifact
job.job_artifacts_trace
end
-
- # For ExclusiveLeaseGuard concern
- def lease_key
- @lease_key ||= "trace:archive:#{job.id}"
- end
-
- # For ExclusiveLeaseGuard concern
- def lease_timeout
- LEASE_TIMEOUT
- end
end
end
end
diff --git a/lib/gitlab/ci/trace/chunked_io.rb b/lib/gitlab/ci/trace/chunked_io.rb
index e9b3199d56e..8c6fd56493f 100644
--- a/lib/gitlab/ci/trace/chunked_io.rb
+++ b/lib/gitlab/ci/trace/chunked_io.rb
@@ -13,7 +13,7 @@ module Gitlab
attr_reader :build
attr_reader :tell, :size
- attr_reader :chunk, :chunk_range
+ attr_reader :chunk_data, :chunk_range
alias_method :pos, :tell
@@ -75,14 +75,14 @@ module Gitlab
until length <= 0 || eof?
data = chunk_slice_from_offset
- break if data.empty?
+ raise FailedToGetChunkError if data.empty?
chunk_bytes = [CHUNK_SIZE - chunk_offset, length].min
- chunk_data = data.byteslice(0, chunk_bytes)
+ chunk_data_slice = data.byteslice(0, chunk_bytes)
- out << chunk_data
- @tell += chunk_data.bytesize
- length -= chunk_data.bytesize
+ out << chunk_data_slice
+ @tell += chunk_data_slice.bytesize
+ length -= chunk_data_slice.bytesize
end
out = out.join
@@ -100,11 +100,14 @@ module Gitlab
until eof?
data = chunk_slice_from_offset
+ raise FailedToGetChunkError if data.empty?
+
new_line = data.index("\n")
if !new_line.nil?
- out << data[0..new_line]
- @tell += new_line + 1
+ raw_data = data[0..new_line]
+ out << raw_data
+ @tell += raw_data.bytesize
break
else
out << data
@@ -121,13 +124,13 @@ module Gitlab
while tell < start_pos + data.bytesize
# get slice from current offset till the end where it falls into chunk
chunk_bytes = CHUNK_SIZE - chunk_offset
- chunk_data = data.byteslice(tell - start_pos, chunk_bytes)
+ data_slice = data.byteslice(tell - start_pos, chunk_bytes)
# append data to chunk, overwriting from that point
- ensure_chunk.append(chunk_data, chunk_offset)
+ ensure_chunk.append(data_slice, chunk_offset)
# move offsets within buffer
- @tell += chunk_data.bytesize
+ @tell += data_slice.bytesize
@size = [size, tell].max
end
@@ -183,12 +186,12 @@ module Gitlab
current_chunk.tap do |chunk|
raise FailedToGetChunkError unless chunk
- @chunk = chunk.data
+ @chunk_data = chunk.data
@chunk_range = chunk.range
end
end
- @chunk[chunk_offset..CHUNK_SIZE]
+ @chunk_data.byteslice(chunk_offset, CHUNK_SIZE)
end
def chunk_offset
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index bd40fdf59b1..0f23b95ba15 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -43,19 +43,14 @@ module Gitlab
def append(data, offset)
data = data.force_encoding(Encoding::BINARY)
- stream.truncate(offset)
- stream.seek(0, IO::SEEK_END)
+ stream.seek(offset, IO::SEEK_SET)
stream.write(data)
+ stream.truncate(offset + data.bytesize)
stream.flush()
end
def set(data)
- data = data.force_encoding(Encoding::BINARY)
-
- stream.seek(0, IO::SEEK_SET)
- stream.write(data)
- stream.truncate(data.bytesize)
- stream.flush()
+ append(data, 0)
end
def raw(last_lines: nil)
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 68ed53cf64a..6d40e00c035 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -35,7 +35,6 @@ module Gitlab
adapter_name.casecmp('postgresql').zero?
end
- # Overridden in EE
def self.read_only?
false
end
@@ -44,12 +43,14 @@ module Gitlab
!self.read_only?
end
- # check whether the underlying database is in read-only mode
+ # Check whether the underlying database is in read-only mode
def self.db_read_only?
if postgresql?
- ActiveRecord::Base.connection.execute('SELECT pg_is_in_recovery()')
- .first
- .fetch('pg_is_in_recovery') == 't'
+ pg_is_in_recovery =
+ ActiveRecord::Base.connection.execute('SELECT pg_is_in_recovery()')
+ .first.fetch('pg_is_in_recovery')
+
+ Gitlab::Utils.to_boolean(pg_is_in_recovery)
else
false
end
diff --git a/lib/gitlab/exclusive_lease_helpers.rb b/lib/gitlab/exclusive_lease_helpers.rb
index 4aaf2474763..7961d4bbd6e 100644
--- a/lib/gitlab/exclusive_lease_helpers.rb
+++ b/lib/gitlab/exclusive_lease_helpers.rb
@@ -12,6 +12,8 @@ module Gitlab
# because it holds the connection until all `retries` is consumed.
# This could potentially eat up all connection pools.
def in_lock(key, ttl: 1.minute, retries: 10, sleep_sec: 0.01.seconds)
+ raise ArgumentError, 'Key needs to be specified' unless key
+
lease = Gitlab::ExclusiveLease.new(key, timeout: ttl)
until uuid = lease.try_obtain
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 9b48031b2d3..0a541031884 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -723,11 +723,11 @@ module Gitlab
delete_refs(tmp_ref)
end
- def write_ref(ref_path, ref, old_ref: nil, shell: true)
+ def write_ref(ref_path, ref, old_ref: nil)
ref_path = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref_path}" unless ref_path.start_with?("refs/") || ref_path == "HEAD"
wrapped_gitaly_errors do
- gitaly_repository_client.write_ref(ref_path, ref, old_ref, shell)
+ gitaly_repository_client.write_ref(ref_path, ref, old_ref)
end
end
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index a90b69ff42b..3f13ebeb9d0 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -13,7 +13,11 @@ module Gitlab
return false if ref_name.start_with?(*not_allowed_prefixes)
return false if ref_name == 'HEAD'
- Rugged::Reference.valid_name? "refs/heads/#{ref_name}"
+ begin
+ Rugged::Reference.valid_name?("refs/heads/#{ref_name}")
+ rescue ArgumentError
+ return false
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index c32c2c0b2fb..22d2d149e65 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -385,7 +385,8 @@ module Gitlab
file_path: encode_binary(action[:file_path]),
previous_path: encode_binary(action[:previous_path]),
base64_content: action[:encoding] == 'base64',
- execute_filemode: !!action[:execute_filemode]
+ execute_filemode: !!action[:execute_filemode],
+ infer_content: !!action[:infer_content]
)
rescue RangeError
raise ArgumentError, "Unknown action '#{action[:action]}'"
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 12a0ee16649..8a1abfbf874 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -251,20 +251,15 @@ module Gitlab
)
end
- def write_ref(ref_path, ref, old_ref, shell)
+ def write_ref(ref_path, ref, old_ref)
request = Gitaly::WriteRefRequest.new(
repository: @gitaly_repo,
ref: ref_path.b,
- revision: ref.b,
- shell: shell
+ revision: ref.b
)
request.old_revision = old_ref.b unless old_ref.nil?
- response = GitalyClient.call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout)
-
- raise Gitlab::Git::CommandError, encode!(response.error) if response.error.present?
-
- true
+ GitalyClient.call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout)
end
def set_config(entries)
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index 374dc9d3c00..bc3ea9e9226 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -80,7 +80,7 @@ module Gitlab
end
def fail_import(message)
- project.mark_import_as_failed(message)
+ project.import_state.mark_as_failed(message)
false
end
end
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index a77ac1e4fa6..9d81441d96e 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -41,8 +41,7 @@ module Gitlab
Gitlab::SidekiqStatus
.set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
- project.ensure_import_state
- project.import_state&.update_column(:jid, jid)
+ project.import_state.update_column(:jid, jid)
Stage::ImportRepositoryWorker
.perform_async(project.id)
diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb
new file mode 100644
index 00000000000..5a0099dc6b1
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class BatchModelLoader
+ attr_reader :model_class, :model_id
+
+ def initialize(model_class, model_id)
+ @model_class, @model_id = model_class, model_id
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find
+ BatchLoader.for({ model: model_class, id: model_id }).batch do |loader_info, loader|
+ per_model = loader_info.group_by { |info| info[:model] }
+ per_model.each do |model, info|
+ ids = info.map { |i| i[:id] }
+ results = model.where(id: ids)
+
+ results.each { |record| loader.call({ model: model, id: record.id }, record) }
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/http_io.rb b/lib/gitlab/http_io.rb
index e768b8adb12..6a9fb85b054 100644
--- a/lib/gitlab/http_io.rb
+++ b/lib/gitlab/http_io.rb
@@ -85,11 +85,11 @@ module Gitlab
break if data.empty?
chunk_bytes = [BUFFER_SIZE - chunk_offset, length].min
- chunk_data = data.byteslice(0, chunk_bytes)
+ data_slice = data.byteslice(0, chunk_bytes)
- out << chunk_data
- @tell += chunk_data.bytesize
- length -= chunk_data.bytesize
+ out << data_slice
+ @tell += data_slice.bytesize
+ length -= data_slice.bytesize
end
out = out.join
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index b40eac3de9a..8380e86c128 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -98,13 +98,11 @@ excluded_attributes:
- :avatar
- :import_type
- :import_source
- - :import_error
- :mirror
- :runners_token
- :repository_storage
- :repository_read_only
- :lfs_enabled
- - :import_jid
- :created_at
- :updated_at
- :id
@@ -116,6 +114,9 @@ excluded_attributes:
- :remote_mirror_available_overridden
- :description_html
- :repository_languages
+ project_import_state:
+ - :last_error
+ - :jid
prometheus_metrics:
- :common
- :identifier
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index fd3d187cbc3..b9903e37f40 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -16,12 +16,16 @@ module Gitlab
create_cluster_role_binding(command)
create_config_map(command)
+ delete_pod!(command.pod_name)
kubeclient.create_pod(command.pod_resource)
end
def update(command)
namespace.ensure_exists!
+
update_config_map(command)
+
+ delete_pod!(command.pod_name)
kubeclient.create_pod(command.pod_resource)
end
@@ -42,6 +46,8 @@ module Gitlab
def delete_pod!(pod_name)
kubeclient.delete_pod(pod_name, namespace.name)
+ rescue ::Kubeclient::ResourceNotFoundError
+ # no-op
end
def get_config_map(config_map_name)
diff --git a/lib/gitlab/kubernetes/logger.rb b/lib/gitlab/kubernetes/logger.rb
new file mode 100644
index 00000000000..5e59482419b
--- /dev/null
+++ b/lib/gitlab/kubernetes/logger.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'kubernetes'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index 43695451b87..c526d31a591 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -80,8 +80,7 @@ module Gitlab
def handle_errors
return unless errors.any?
- project.ensure_import_state
- project.import_state&.update_column(:last_error, {
+ project.import_state.update_column(:last_error, {
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 86efe8ad114..b8040f73cee 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'resolv'
+require 'ipaddress'
module Gitlab
class UrlBlocker
@@ -10,11 +11,8 @@ module Gitlab
def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
return true if url.nil?
- begin
- uri = Addressable::URI.parse(url)
- rescue Addressable::URI::InvalidURIError
- raise BlockedUrlError, "URI is invalid"
- end
+ # Param url can be a string, URI or Addressable::URI
+ uri = parse_url(url)
# Allow imports from the GitLab instance itself but only from the configured ports
return true if internal?(uri)
@@ -26,7 +24,9 @@ module Gitlab
validate_hostname!(uri.hostname)
begin
- addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM)
+ addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
+ addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
+ end
rescue SocketError
return true
end
@@ -49,6 +49,18 @@ module Gitlab
private
+ def parse_url(url)
+ raise Addressable::URI::InvalidURIError if multiline?(url)
+
+ Addressable::URI.parse(url)
+ rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
+ raise BlockedUrlError, 'URI is invalid'
+ end
+
+ def multiline?(url)
+ CGI.unescape(url.to_s) =~ /\n|\r/
+ end
+
def validate_port!(port, ports)
return if port.blank?
# Only ports under 1024 are restricted
@@ -73,13 +85,14 @@ module Gitlab
def validate_hostname!(value)
return if value.blank?
+ return if IPAddress.valid?(value)
return if value =~ /\A\p{Alnum}/
- raise BlockedUrlError, "Hostname needs to start with an alphanumeric character"
+ raise BlockedUrlError, "Hostname or IP address invalid"
end
def validate_localhost!(addrs_info)
- local_ips = ["127.0.0.1", "::1", "0.0.0.0"]
+ local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty?
@@ -94,7 +107,7 @@ module Gitlab
end
def validate_local_network!(addrs_info)
- return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? }
+ return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
raise BlockedUrlError, "Requests to the local network are not allowed"
end
@@ -111,12 +124,14 @@ module Gitlab
end
def internal_web?(uri)
- uri.hostname == config.gitlab.host &&
+ uri.scheme == config.gitlab.protocol &&
+ uri.hostname == config.gitlab.host &&
(uri.port.blank? || uri.port == config.gitlab.port)
end
def internal_shell?(uri)
- uri.hostname == config.gitlab_shell.ssh_host &&
+ uri.scheme == 'ssh' &&
+ uri.hostname == config.gitlab_shell.ssh_host &&
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index e8ae5dfa540..451ba651674 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -6,7 +6,7 @@ namespace :gitlab do
desc "GitLab | Cleanup | Clean namespaces"
task dirs: :gitlab_environment do
namespaces = Set.new(Namespace.pluck(:path))
- namespaces << Storage::HashedProject::ROOT_PATH_PREFIX
+ namespaces << Storage::HashedProject::REPOSITORY_PATH_PREFIX
Gitaly::Server.all.each do |server|
all_dirs = Gitlab::GitalyClient::StorageService
@@ -49,7 +49,7 @@ namespace :gitlab do
# TODO ignoring hashed repositories for now. But revisit to fully support
# possible orphaned hashed repos
- next if repo_with_namespace.start_with?(Storage::HashedProject::ROOT_PATH_PREFIX)
+ next if repo_with_namespace.start_with?(Storage::HashedProject::REPOSITORY_PATH_PREFIX)
next if Project.find_by_full_path(repo_with_namespace)
new_path = path + move_suffix
diff --git a/lib/tasks/gitlab/site_statistics.rake b/lib/tasks/gitlab/site_statistics.rake
deleted file mode 100644
index d97f11b2ed5..00000000000
--- a/lib/tasks/gitlab/site_statistics.rake
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace :gitlab do
- desc "GitLab | Refresh Site Statistics counters"
- task refresh_site_statistics: :environment do
- puts 'Updating Site Statistics counters: '
-
- print '* Repositories... '
- SiteStatistic.transaction do
- # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967
- ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql?
- SiteStatistic.update_all('repositories_count = (SELECT COUNT(*) FROM projects)')
- end
- puts 'OK!'.color(:green)
- puts
- end
-end
diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake
index a16d4c47273..f912f521dfb 100644
--- a/lib/tasks/import.rake
+++ b/lib/tasks/import.rake
@@ -42,7 +42,7 @@ class GithubImport
end
def import!
- @project.force_import_start
+ @project.import_state.force_start
import_success = false
@@ -57,7 +57,7 @@ class GithubImport
puts "Import finished. Timings: #{timings}".color(:green)
else
puts "Import was not successful. Errors were as follows:"
- puts @project.import_error
+ puts @project.import_state.last_error
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 71374388a7d..076996e2231 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -158,6 +158,9 @@ msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr ""
+msgid "%{user_name} profile page"
+msgstr ""
+
msgid "+ %{count} more"
msgstr ""
@@ -804,6 +807,9 @@ msgstr ""
msgid "Automatically marked as default internal user"
msgstr ""
+msgid "Automatically resolved"
+msgstr ""
+
msgid "Available"
msgstr ""
@@ -1088,6 +1094,9 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "By %{user_name}"
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1379,13 +1388,13 @@ msgstr ""
msgid "Close"
msgstr ""
-msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgid "Closed"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|A Knative build extends Kubernetes and utilizes existing Kubernetes primitives to provide you with the ability to run on-cluster container builds from source. For example, you can write a build that uses Kubernetes-native resources to obtain your source code from a repository, build it into container a image, and then run that image."
+msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
msgstr ""
msgid "ClusterIntegration|API URL"
@@ -1454,6 +1463,9 @@ msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
+msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgstr ""
+
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
@@ -1556,9 +1568,15 @@ msgstr ""
msgid "ClusterIntegration|Knative"
msgstr ""
+msgid "ClusterIntegration|Knative (pronounced kay-nay-tiv) extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
+msgid "ClusterIntegration|Knative IP Address:"
+msgstr ""
+
msgid "ClusterIntegration|Kubernetes cluster"
msgstr ""
@@ -2570,6 +2588,9 @@ msgstr ""
msgid "Edit application"
msgstr ""
+msgid "Edit comment"
+msgstr ""
+
msgid "Edit environment"
msgstr ""
@@ -2822,6 +2843,9 @@ msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
msgid "Every day (at 4:00am)"
msgstr ""
@@ -3424,6 +3448,9 @@ msgstr ""
msgid "ImageDiffViewer|Swipe"
msgstr ""
+msgid "Impersonation has been disabled"
+msgstr ""
+
msgid "Import"
msgstr ""
@@ -3475,6 +3502,9 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
msgstr ""
@@ -3526,6 +3556,9 @@ msgstr ""
msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr ""
+msgid "Internal"
+msgstr ""
+
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr ""
@@ -3628,7 +3661,7 @@ msgstr ""
msgid "Job|The artifacts will be removed in"
msgstr ""
-msgid "Job|This job is stuck, because the project doesn't have any runners online assigned to it."
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
msgid "Jul"
@@ -3983,6 +4016,18 @@ msgstr ""
msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr ""
+msgid "MergeRequests|started a discussion"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
@@ -4354,6 +4399,9 @@ msgstr ""
msgid "Notes|Show history only"
msgstr ""
+msgid "Nothing here."
+msgstr ""
+
msgid "Notification events"
msgstr ""
@@ -4467,6 +4515,9 @@ msgstr ""
msgid "Open source software to collaborate on code"
msgstr ""
+msgid "Opened"
+msgstr ""
+
msgid "OpenedNDaysAgo|Opened"
msgstr ""
@@ -4812,6 +4863,9 @@ msgstr ""
msgid "Prioritized label"
msgstr ""
+msgid "Private"
+msgstr ""
+
msgid "Private - Project access must be granted explicitly to each user."
msgstr ""
@@ -5238,6 +5292,9 @@ msgstr ""
msgid "Provider"
msgstr ""
+msgid "Public"
+msgstr ""
+
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr ""
@@ -5453,6 +5510,9 @@ msgstr ""
msgid "Resolve discussion"
msgstr ""
+msgid "Resolved"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -5856,6 +5916,9 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Similar issues"
+msgstr ""
+
msgid "Size and domain settings for static websites"
msgstr ""
@@ -6018,6 +6081,9 @@ msgstr ""
msgid "Source is not available"
msgstr ""
+msgid "Source project cannot be found."
+msgstr ""
+
msgid "Spam Logs"
msgstr ""
@@ -6392,6 +6458,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
msgid "They can be managed using the %{link}."
msgstr ""
@@ -6488,10 +6557,10 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
-msgid "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:"
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
-msgid "This job is stuck, because you don't have any active runners that can run this job."
+msgid "This job is stuck because you don't have any active runners that can run this job."
msgstr ""
msgid "This job is the most recent deployment to %{link}."
@@ -7553,6 +7622,11 @@ msgstr ""
msgid "disabled"
msgstr ""
+msgid "discussion resolved"
+msgid_plural "discussions resolved"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "done"
msgstr ""
@@ -7840,6 +7914,9 @@ msgstr ""
msgid "this document"
msgstr ""
+msgid "updated"
+msgstr ""
+
msgid "username"
msgstr ""
diff --git a/package.json b/package.json
index 64df2532977..e573fb1ecc0 100644
--- a/package.json
+++ b/package.json
@@ -24,12 +24,14 @@
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
- "@gitlab/svgs": "^1.38.0",
+ "@gitlab/svgs": "^1.40.0",
"@gitlab/ui": "^1.11.0",
+ "apollo-boost": "^0.1.20",
+ "apollo-client": "^2.4.5",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.4",
- "bootstrap": "4.1.1",
+ "bootstrap": "4.1.3",
"brace-expansion": "^1.1.8",
"cache-loader": "^1.2.2",
"chart.js": "1.0.2",
@@ -40,7 +42,7 @@
"core-js": "^2.4.1",
"cropper": "^2.3.0",
"css-loader": "^1.0.0",
- "d3": "4.12.2",
+ "d3": "^4.13.0",
"d3-array": "^1.2.1",
"d3-axis": "^1.0.8",
"d3-brush": "^1.0.4",
@@ -60,6 +62,7 @@
"formdata-polyfill": "^3.0.11",
"fuzzaldrin-plus": "^0.5.0",
"glob": "^7.1.2",
+ "graphql": "^14.0.2",
"imports-loader": "^0.8.0",
"jed": "^1.1.1",
"jquery": "^3.2.1",
@@ -97,6 +100,7 @@
"url-loader": "^1.1.1",
"visibilityjs": "^1.2.4",
"vue": "^2.5.17",
+ "vue-apollo": "^3.0.0-beta.25",
"vue-loader": "^15.4.2",
"vue-resource": "^1.5.0",
"vue-router": "^3.0.1",
@@ -127,6 +131,7 @@
"eslint-plugin-jasmine": "^2.10.1",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
+ "graphql-tag": "^2.10.0",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
@@ -141,6 +146,6 @@
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.4",
"prettier": "1.15.2",
- "webpack-dev-server": "^3.1.8"
+ "webpack-dev-server": "^3.1.10"
}
}
diff --git a/qa/README.md b/qa/README.md
index 746bd5cf94b..08ba59e117d 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -80,6 +80,15 @@ GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sa
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
+### Sending additional cookies
+
+The environment variable `QA_COOKIES` can be set to send additional cookies
+on every request. This is necessary on gitlab.com to direct traffic to the
+canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`.
+
+To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
+
+
### Building a Docker image to test
Once you have made changes to the CE/EE repositories, you may want to build a
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 9aaf57e8d83..7fd2ba25527 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -117,6 +117,15 @@ module QA
def perform(&block)
visit(url)
+ if QA::Runtime::Env.qa_cookies
+ browser = Capybara.current_session.driver.browser
+ QA::Runtime::Env.qa_cookies.each do |cookie|
+ name, value = cookie.split("=")
+ value ||= ""
+ browser.manage.add_cookie name: name, value: value
+ end
+ end
+
yield.tap { clear! } if block_given?
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 7b2768548dd..3bc2b44ccd8 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -38,6 +38,10 @@ module QA
ENV['CI'] || ENV['CI_SERVER']
end
+ def qa_cookies
+ ENV['QA_COOKIES'] && ENV['QA_COOKIES'].split(';')
+ end
+
def signup_disabled?
enabled?(ENV['SIGNUP_DISABLED'], default: false)
end
diff --git a/rubocop/cop/migration/add_reference.rb b/rubocop/cop/migration/add_reference.rb
index 4b67270c97a..1d471b9797e 100644
--- a/rubocop/cop/migration/add_reference.rb
+++ b/rubocop/cop/migration/add_reference.rb
@@ -8,7 +8,7 @@ module RuboCop
class AddReference < RuboCop::Cop::Cop
include MigrationHelpers
- MSG = '`add_reference` requires `index: true`'
+ MSG = '`add_reference` requires `index: true` or `index: { options... }`'
def on_send(node)
return unless in_migration?(node)
@@ -33,7 +33,12 @@ module RuboCop
private
def index_enabled?(pair)
- hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type?
+ return unless hash_key_type(pair) == :sym
+ return unless hash_key_name(pair) == :index
+
+ index = pair.children[1]
+
+ index.true_type? || index.hash_type?
end
def hash_key_type(pair)
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 00e23f12bc0..f3f788e0217 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -1,6 +1,8 @@
[[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE"
+function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }
+
function check_kube_domain() {
if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set"
@@ -88,19 +90,16 @@ function deploy() {
replicas="1"
service_enabled="false"
postgres_enabled="$POSTGRES_ENABLED"
- gitlab_migrations_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-rails-ce"
- gitlab_sidekiq_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-sidekiq-ce"
- gitlab_unicorn_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-unicorn-ce"
- gitlab_gitaly_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitaly"
- gitlab_shell_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-shell"
- gitlab_workhorse_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-workhorse-ce"
-
- if [[ "$CI_PROJECT_NAME" == "gitlab-ee" ]]; then
- gitlab_migrations_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-rails-ee"
- gitlab_sidekiq_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-sidekiq-ee"
- gitlab_unicorn_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-unicorn-ee"
- gitlab_workhorse_image_repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-workhorse-ee"
- fi
+
+ IMAGE_REPOSITORY="registry.gitlab.com/gitlab-org/build/cng-mirror"
+ IMAGE_VERSION="${CI_PROJECT_NAME#gitlab-}"
+ gitlab_migrations_image_repository="${IMAGE_REPOSITORY}/gitlab-rails-${IMAGE_VERSION}"
+ gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-${IMAGE_VERSION}"
+ gitlab_unicorn_image_repository="${IMAGE_REPOSITORY}/gitlab-unicorn-${IMAGE_VERSION}"
+ gitlab_task_runner_image_repository="${IMAGE_REPOSITORY}/gitlab-task-runner-${IMAGE_VERSION}"
+ gitlab_gitaly_image_repository="${IMAGE_REPOSITORY}/gitaly"
+ gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
+ gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
# canary uses stable db
[[ "$track" == "canary" ]] && postgres_enabled="false"
@@ -155,6 +154,8 @@ HELM_CMD=$(cat << EOF
--set gitlab.sidekiq.image.tag="$CI_COMMIT_REF_NAME" \
--set gitlab.unicorn.image.repository="$gitlab_unicorn_image_repository" \
--set gitlab.unicorn.image.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.task-runner.image.repository="$gitlab_task_runner_image_repository" \
+ --set gitlab.task-runner.image.tag="$CI_COMMIT_REF_NAME" \
--set gitlab.gitaly.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitaly" \
--set gitlab.gitaly.image.tag="v$GITALY_VERSION" \
--set gitlab.gitlab-shell.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-shell" \
@@ -229,3 +230,121 @@ function install_external_dns() {
--set rbac.create="true"
fi
}
+
+function get_pod() {
+ local app_name="${1}"
+ local status="${2-Running}"
+ get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
+ echoerr "Running '${get_pod_cmd}'"
+
+ while true; do
+ local pod_name="$(eval $get_pod_cmd)"
+ [[ "${pod_name}" == "" ]] || break
+
+ echoerr "Waiting till '${app_name}' pod is ready";
+ sleep 5;
+ done
+
+ echoerr "The pod name is '${pod_name}'."
+ echo "${pod_name}"
+}
+
+function add_license() {
+ if [ -z "${REVIEW_APPS_EE_LICENSE}" ]; then echo "License not found" && return; fi
+
+ task_runner_pod=$(get_pod "task-runner");
+ if [ -z "${task_runner_pod}" ]; then echo "Task runner pod not found" && return; fi
+
+ echo "${REVIEW_APPS_EE_LICENSE}" > /tmp/license.gitlab
+ kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab ${task_runner_pod}:/tmp/license.gitlab
+ rm /tmp/license.gitlab
+
+ kubectl -n "$KUBE_NAMESPACE" exec -it ${task_runner_pod} -- /srv/gitlab/bin/rails runner -e production \
+ '
+ content = File.read("/tmp/license.gitlab").strip;
+ FileUtils.rm_f("/tmp/license.gitlab");
+
+ unless License.where(data:content).empty?
+ puts "License already exists";
+ Kernel.exit 0;
+ end
+
+ unless License.new(data: content).save
+ puts "Could not add license";
+ Kernel.exit 0;
+ end
+
+ puts "License added";
+ '
+}
+
+function get_job_id() {
+ local job_name="${1}"
+ local query_string="${2:+&${2}}"
+
+ local max_page=3
+ local page=1
+
+ while true; do
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
+ echoerr "GET ${url}"
+
+ local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".[] | select(.name == \"${job_name}\") | .id")
+ [[ "${job_id}" == "" && "${page}" -lt "$max_page" ]] || break
+
+ ((page++))
+ done
+
+ if [[ "${job_id}" == "" ]]; then
+ echoerr "The '${job_name}' job ID couldn't be retrieved!"
+ else
+ echoerr "The '${job_name}' job ID is ${job_id}"
+ echo "${job_id}"
+ fi
+}
+
+function play_job() {
+ local job_name="${1}"
+ local job_id=$(get_job_id "${job_name}" "scope=manual");
+ if [ -z "${job_id}" ]; then return; fi
+
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
+ echoerr "POST ${url}"
+
+ local job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".web_url")
+ echo "Manual job '${job_name}' started at: ${job_url}"
+}
+
+function wait_for_job_to_be_done() {
+ local job_name="${1}"
+ local query_string="${2}"
+ local job_id=$(get_job_id "${job_name}" "${query_string}");
+ if [ -z "${job_id}" ]; then return; fi
+
+ echoerr "Waiting for the '${job_name}' job to finish..."
+
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
+ echo "GET ${url}"
+
+ # In case the job hasn't finished yet. Keep trying until the job times out.
+ local interval=30
+ local elapsed=0
+ while true; do
+ local job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".status" | sed -e s/\"//g)
+ [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
+
+ printf "."
+ ((elapsed+=$interval))
+ sleep ${interval}
+ done
+
+ echoerr "Waited '${job_name}' for ${elapsed} seconds."
+
+ if [[ "${job_status}" == "failed" ]]; then
+ echo "The '${job_name}' failed."
+ elif [[ "${job_status}" == "manual" ]]; then
+ echo "The '${job_name}' is manual."
+ else
+ echo "The '${job_name}' passed."
+ fi
+}
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index f350641a643..3dd0b2623ac 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -264,5 +264,17 @@ describe Admin::UsersController do
expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
end
end
+
+ context "when impersonation is disabled" do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ end
+
+ it "shows error page" do
+ post :impersonate, id: user.username
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index efc3ce74627..1b585bcd4c6 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -107,59 +107,6 @@ describe ApplicationController do
end
end
- describe "#authenticate_user_from_personal_access_token!" do
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- controller(described_class) do
- def index
- render text: 'authenticated'
- end
- end
-
- let(:personal_access_token) { create(:personal_access_token, user: user) }
-
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, private_token: personal_access_token.token
-
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
- end
-
- context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- @request.headers["PRIVATE-TOKEN"] = personal_access_token.token
- get :index
-
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
- end
-
- it "doesn't log the user in otherwise" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, private_token: "token"
-
- expect(response.status).not_to eq(200)
- expect(response.body).not_to eq('authenticated')
- end
- end
-
describe 'session expiration' do
controller(described_class) do
# The anonymous controller will report 401 and fail to run any actions.
@@ -224,74 +171,6 @@ describe ApplicationController do
end
end
- describe '#authenticate_sessionless_user!' do
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- describe 'authenticating a user from a feed token' do
- controller(described_class) do
- def index
- render text: 'authenticated'
- end
- end
-
- context "when the 'feed_token' param is populated with the feed token" do
- context 'when the request format is atom' do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status 200
- expect(response.body).to eq 'authenticated'
- end
- end
-
- context 'when the request format is ics' do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, feed_token: user.feed_token, format: :ics
-
- expect(response).to have_gitlab_http_status 200
- expect(response.body).to eq 'authenticated'
- end
- end
-
- context 'when the request format is neither atom nor ics' do
- it "doesn't log the user in" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, feed_token: user.feed_token
-
- expect(response.status).not_to have_gitlab_http_status 200
- expect(response.body).not_to eq 'authenticated'
- end
- end
- end
-
- context "when the 'feed_token' param is populated with an invalid feed token" do
- it "doesn't log the user" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, feed_token: 'token', format: :atom
-
- expect(response.status).not_to eq 200
- expect(response.body).not_to eq 'authenticated'
- end
- end
- end
- end
-
describe '#route_not_found' do
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)
@@ -557,36 +436,6 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(200)
end
-
- context 'for sessionless users' do
- render_views
-
- before do
- sign_out user
- end
-
- it 'renders a 403 when the sessionless user did not accept the terms' do
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status(403)
- end
-
- it 'renders the error message when the format was html' do
- get :index,
- private_token: create(:personal_access_token, user: user).token,
- format: :html
-
- expect(response.body).to have_content /accept the terms of service/i
- end
-
- it 'renders a 200 when the sessionless user accepted the terms' do
- accept_terms(user)
-
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
end
end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
new file mode 100644
index 00000000000..2975205e09c
--- /dev/null
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Dashboard::ProjectsController do
+ it_behaves_like 'authenticates sessionless user', :index, :atom
+end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index b4a731fd3a3..e2c799f5205 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -42,6 +42,16 @@ describe Dashboard::TodosController do
end
end
+ context 'group authorization' do
+ it 'renders 404 when user does not have read access on given group' do
+ unauthorized_group = create(:group, :private)
+
+ get :index, group_id: unauthorized_group.id
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context 'when using pagination' do
let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 187542ba30c..c857a78d5e8 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -1,21 +1,26 @@
require 'spec_helper'
describe DashboardController do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
+ context 'signed in' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- describe 'GET issues' do
- it_behaves_like 'issuables list meta-data', :issue, :issues
- it_behaves_like 'issuables requiring filter', :issues
- end
+ describe 'GET issues' do
+ it_behaves_like 'issuables list meta-data', :issue, :issues
+ it_behaves_like 'issuables requiring filter', :issues
+ end
- describe 'GET merge requests' do
- it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
- it_behaves_like 'issuables requiring filter', :merge_requests
+ describe 'GET merge requests' do
+ it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+ it_behaves_like 'issuables requiring filter', :merge_requests
+ end
end
+
+ it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
+ it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 1449036e148..949ad532365 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -52,15 +52,58 @@ describe GraphqlController do
end
end
+ context 'token authentication' do
+ before do
+ stub_authentication_activity_metrics(debug: false)
+ end
+
+ let(:user) { create(:user, username: 'Simon') }
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ context "when the 'personal_access_token' param is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ run_test_query!(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(query_response).to eq('echo' => '"Simon" says: test success')
+ end
+ end
+
+ context 'when the personal access token has no api scope' do
+ it 'does not log the user in' do
+ personal_access_token.update(scopes: [:read_user])
+
+ run_test_query!(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(query_response).to eq('echo' => 'nil says: test success')
+ end
+ end
+
+ context 'without token' do
+ it 'shows public data' do
+ run_test_query!
+
+ expect(query_response).to eq('echo' => 'nil says: test success')
+ end
+ end
+ end
+
# Chosen to exercise all the moving parts in GraphqlController#execute
- def run_test_query!(variables: { 'text' => 'test success' })
+ def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil)
query = <<~QUERY
query Echo($text: String) {
echo(text: $text)
}
QUERY
- post :execute, query: query, operationName: 'Echo', variables: variables
+ post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token
end
def query_response
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 4de61b65f71..f6c85102830 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -606,4 +606,24 @@ describe GroupsController do
end
end
end
+
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(id: group)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :issues, :atom, public: true do
+ before do
+ default_params.merge!(id: group, author_id: user.id)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics, public: true do
+ before do
+ default_params.merge!(id: group)
+ end
+ end
+ end
end
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index 77060fdc3be..db912641894 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -126,7 +126,7 @@ describe Import::BitbucketServerController do
end
it 'assigns repository categories' do
- created_project = create(:project, import_type: 'bitbucket_server', creator_id: user.id, import_status: 'finished', import_source: @created_repo.browse_url)
+ created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
repos = instance_double(BitbucketServer::Collection)
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index ace8a954e92..b4219856fc0 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -40,6 +40,23 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(profile_path)
end
+
+ context 'redirect_uri' do
+ render_views
+
+ it 'shows an error for a forbidden URI' do
+ invalid_uri_params = {
+ doorkeeper_application: {
+ name: 'foo',
+ redirect_uri: 'javascript://alert()'
+ }
+ }
+
+ post :create, invalid_uri_params
+
+ expect(response.body).to include 'Redirect URI is forbidden by the server'
+ end
+ end
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index a43bdd3ea80..5c72dab698c 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -5,87 +5,115 @@ describe Projects::CommitsController do
let(:user) { create(:user) }
before do
- sign_in(user)
project.add_maintainer(user)
end
- describe "GET commits_root" do
- context "no ref is provided" do
- it 'should redirect to the default branch of the project' do
- get(:commits_root,
- namespace_id: project.namespace,
- project_id: project)
+ context 'signed in' do
+ before do
+ sign_in(user)
+ end
+
+ describe "GET commits_root" do
+ context "no ref is provided" do
+ it 'should redirect to the default branch of the project' do
+ get(:commits_root,
+ namespace_id: project.namespace,
+ project_id: project)
- expect(response).to redirect_to project_commits_path(project)
+ expect(response).to redirect_to project_commits_path(project)
+ end
end
end
- end
- describe "GET show" do
- render_views
+ describe "GET show" do
+ render_views
- context 'with file path' do
- before do
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
- end
+ context 'with file path' do
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id)
+ end
- context "valid branch, valid file" do
- let(:id) { 'master/README.md' }
+ context "valid branch, valid file" do
+ let(:id) { 'master/README.md' }
- it { is_expected.to respond_with(:success) }
- end
+ it { is_expected.to respond_with(:success) }
+ end
- context "valid branch, invalid file" do
- let(:id) { 'master/invalid-path.rb' }
+ context "valid branch, invalid file" do
+ let(:id) { 'master/invalid-path.rb' }
- it { is_expected.to respond_with(:not_found) }
- end
+ it { is_expected.to respond_with(:not_found) }
+ end
- context "invalid branch, valid file" do
- let(:id) { 'invalid-branch/README.md' }
+ context "invalid branch, valid file" do
+ let(:id) { 'invalid-branch/README.md' }
- it { is_expected.to respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
+ end
end
- end
- context "when the ref name ends in .atom" do
- context "when the ref does not exist with the suffix" do
- before do
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ context "when the ref name ends in .atom" do
+ context "when the ref does not exist with the suffix" do
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom")
+ end
+
+ it "renders as atom" do
+ expect(response).to be_success
+ expect(response.content_type).to eq('application/atom+xml')
+ end
+
+ it 'renders summary with type=html' do
+ expect(response.body).to include('<summary type="html">')
+ end
end
- it "renders as atom" do
- expect(response).to be_success
- expect(response.content_type).to eq('application/atom+xml')
- end
+ context "when the ref exists with the suffix" do
+ before do
+ commit = project.repository.commit('master')
- it 'renders summary with type=html' do
- expect(response.body).to include('<summary type="html">')
+ allow_any_instance_of(Repository).to receive(:commit).and_call_original
+ allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
+
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom")
+ end
+
+ it "renders as HTML" do
+ expect(response).to be_success
+ expect(response.content_type).to eq('text/html')
+ end
end
end
+ end
+ end
- context "when the ref exists with the suffix" do
+ context 'token authentication' do
+ context 'public project' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
- commit = project.repository.commit('master')
+ public_project = create(:project, :repository, :public)
- allow_any_instance_of(Repository).to receive(:commit).and_call_original
- allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
-
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
end
+ end
+ end
+
+ context 'private project' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
+ before do
+ private_project = create(:project, :repository, :private)
+ private_project.add_maintainer(user)
- it "renders as HTML" do
- expect(response).to be_success
- expect(response.content_type).to eq('text/html')
+ default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
end
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index bc17331f531..5fa0488014f 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -217,7 +217,10 @@ describe Projects::EnvironmentsController do
end
it 'loads the terminals for the environment' do
- expect_any_instance_of(Environment).to receive(:terminals)
+ # In EE we have to stub EE::Environment since it overwrites the
+ # "terminals" method.
+ expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
+ .to receive(:terminals)
get :terminal, environment_params
end
@@ -240,7 +243,9 @@ describe Projects::EnvironmentsController do
context 'and valid id' do
it 'returns the first terminal for the environment' do
- expect_any_instance_of(Environment)
+ # In EE we have to stub EE::Environment since it overwrites the
+ # "terminals" method.
+ expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
.to receive(:terminals)
.and_return([:fake_terminal])
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index adf3c78ae51..cdc63f5aab3 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -26,10 +26,11 @@ describe Projects::ImportsController do
context 'when repository exists' do
let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+ let(:import_state) { project.import_state }
context 'when import is in progress' do
before do
- project.update(import_status: :started)
+ import_state.update(status: :started)
end
it 'renders template' do
@@ -47,7 +48,7 @@ describe Projects::ImportsController do
context 'when import failed' do
before do
- project.update(import_status: :failed)
+ import_state.update(status: :failed)
end
it 'redirects to new_namespace_project_import_path' do
@@ -59,7 +60,7 @@ describe Projects::ImportsController do
context 'when import finished' do
before do
- project.update(import_status: :finished)
+ import_state.update(status: :finished)
end
context 'when project is a fork' do
@@ -108,7 +109,7 @@ describe Projects::ImportsController do
context 'when import never happened' do
before do
- project.update(import_status: :none)
+ import_state.update(status: :none)
end
it 'redirects to namespace_project_path' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 80138183c07..02930edbf72 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1068,4 +1068,40 @@ describe Projects::IssuesController do
end
end
end
+
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index ccd4fc4db3a..658aa2a6738 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -143,11 +143,27 @@ describe Projects::MilestonesController do
end
describe '#promote' do
+ let(:group) { create(:group) }
+
+ before do
+ project.update(namespace: group)
+ end
+
+ context 'when user does not have permission to promote milestone' do
+ before do
+ group.add_guest(user)
+ end
+
+ it 'renders 404' do
+ post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context 'promotion succeeds' do
before do
- group = create(:group)
group.add_developer(user)
- milestone.project.update(namespace: group)
end
it 'shows group milestone' do
@@ -166,12 +182,17 @@ describe Projects::MilestonesController do
end
end
- context 'promotion fails' do
- it 'shows project milestone' do
+ context 'when user cannot admin group milestones' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'renders 404' do
+ project.update(namespace: user.namespace)
+
post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
- expect(response).to redirect_to(project_milestone_path(project, milestone))
- expect(flash[:alert]).to eq('Promotion failed - Project does not belong to a group.')
+ expect(response).to have_gitlab_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 9ac7b8ee8a8..d2a26068362 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -283,14 +283,14 @@ describe Projects::NotesController do
def post_create(extra_params = {})
post :create, {
- note: { note: 'some other note' },
- namespace_id: project.namespace,
- project_id: project,
- target_type: 'merge_request',
- target_id: merge_request.id,
- note_project_id: forked_project.id,
- in_reply_to_discussion_id: existing_comment.discussion_id
- }.merge(extra_params)
+ note: { note: 'some other note', noteable_id: merge_request.id },
+ namespace_id: project.namespace,
+ project_id: project,
+ target_type: 'merge_request',
+ target_id: merge_request.id,
+ note_project_id: forked_project.id,
+ in_reply_to_discussion_id: existing_comment.discussion_id
+ }.merge(extra_params)
end
context 'when the note_project_id is not correct' do
@@ -324,6 +324,30 @@ describe Projects::NotesController do
end
end
+ context 'when target_id and noteable_id do not match' do
+ let(:locked_issue) { create(:issue, :locked, project: project) }
+ let(:issue) {create(:issue, project: project)}
+
+ before do
+ project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ project.project_member(user).destroy
+ end
+
+ it 'uses target_id and ignores noteable_id' do
+ request_params = {
+ note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
+ target_type: 'issue',
+ target_id: issue.id,
+ project_id: project,
+ namespace_id: project.namespace
+ }
+
+ expect { post :create, request_params }.to change { issue.notes.count }.by(1)
+ .and change { locked_issue.notes.count }.by(0)
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
context 'when the merge request discussion is locked' do
before do
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
@@ -376,35 +400,60 @@ describe Projects::NotesController do
end
describe 'PUT update' do
- let(:request_params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- id: note,
- format: :json,
- note: {
- note: "New comment"
+ context "should update the note with a valid issue" do
+ let(:request_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :json,
+ note: {
+ note: "New comment"
+ }
}
- }
- end
+ end
- before do
- sign_in(note.author)
- project.add_developer(note.author)
+ before do
+ sign_in(note.author)
+ project.add_developer(note.author)
+ end
+
+ it "updates the note" do
+ expect { put :update, request_params }.to change { note.reload.note }
+ end
end
+ context "doesnt update the note" do
+ let(:issue) { create(:issue, :confidential, project: project) }
+ let(:note) { create(:note, noteable: issue, project: project) }
- it "updates the note" do
- expect { put :update, request_params }.to change { note.reload.note }
+ before do
+ sign_in(user)
+ project.add_guest(user)
+ end
+
+ it "disallows edits when the issue is confidential and the user has guest permissions" do
+ request_params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :json,
+ note: {
+ note: "New comment"
+ }
+ }
+ expect { put :update, request_params }.not_to change { note.reload.note }
+ expect(response).to have_gitlab_http_status(404)
+ end
end
end
describe 'DELETE destroy' do
let(:request_params) do
{
- namespace_id: project.namespace,
- project_id: project,
- id: note,
- format: :js
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :js
}
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index c48f41ca12e..6fbf75d0259 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -35,4 +35,26 @@ describe Projects::TagsController do
it { is_expected.to respond_with(:not_found) }
end
end
+
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :repository, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :repository, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 3bc9cbe64c5..7849bec4762 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -882,6 +882,28 @@ describe ProjectsController do
end
end
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :show, :atom do
+ before do
+ default_params.merge!(id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 071f96a729e..fe438e71e9e 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -395,6 +395,14 @@ describe UsersController do
end
end
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(username: user.username)
+ end
+ end
+ end
+
def user_moved_message(redirect_route, user)
"User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 90754319f05..07c1fc31152 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -308,7 +308,7 @@ FactoryBot.define do
trait :with_runner_session do
after(:build) do |build|
- build.build_runner_session(url: 'ws://localhost')
+ build.build_runner_session(url: 'https://localhost')
end
end
end
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index 7fc3d16e864..fe56ac5b71d 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -59,6 +59,7 @@ FactoryBot.define do
end
factory :clusters_applications_runner, class: Clusters::Applications::Runner do
+ runner factory: %i(ci_runner)
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
diff --git a/spec/factories/import_state.rb b/spec/factories/import_state.rb
index 15d0a9d466a..d6de26dccbc 100644
--- a/spec/factories/import_state.rb
+++ b/spec/factories/import_state.rb
@@ -5,6 +5,7 @@ FactoryBot.define do
transient do
import_url { generate(:url) }
+ import_type nil
end
trait :repository do
@@ -32,7 +33,11 @@ FactoryBot.define do
end
after(:create) do |import_state, evaluator|
- import_state.project.update_columns(import_url: evaluator.import_url)
+ columns = {}
+ columns[:import_url] = evaluator.import_url unless evaluator.import_url.blank?
+ columns[:import_type] = evaluator.import_type unless evaluator.import_type.blank?
+
+ import_state.project.update_columns(columns)
end
end
end
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
new file mode 100644
index 00000000000..2ed0844ed47
--- /dev/null
+++ b/spec/factories/pool_repositories.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory :pool_repository do
+ shard
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index e4823a5adf1..1906c06a211 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -30,6 +30,8 @@ FactoryBot.define do
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
group_runners_enabled nil
+ import_status nil
+ import_jid nil
end
after(:create) do |project, evaluator|
@@ -64,6 +66,13 @@ FactoryBot.define do
# assign the delegated `#ci_cd_settings` attributes after create
project.reload.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+
+ if evaluator.import_status
+ import_state = project.import_state || project.build_import_state
+ import_state.status = evaluator.import_status
+ import_state.jid = evaluator.import_jid
+ import_state.save
+ end
end
trait :public do
diff --git a/spec/factories/shards.rb b/spec/factories/shards.rb
new file mode 100644
index 00000000000..c095fa5f0a0
--- /dev/null
+++ b/spec/factories/shards.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory :shard do
+ name "default"
+ end
+end
diff --git a/spec/factories/site_statistics.rb b/spec/factories/site_statistics.rb
deleted file mode 100644
index 2533d0eecc2..00000000000
--- a/spec/factories/site_statistics.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryBot.define do
- factory :site_statistics, class: 'SiteStatistic' do
- id 1
- repositories_count 999
- end
-end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index f7c7a257075..d5516b334b9 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -205,75 +205,118 @@ describe "Admin::Users" do
describe 'Impersonation' do
let(:another_user) { create(:user) }
- before do
- visit admin_user_path(another_user)
- end
-
context 'before impersonating' do
- it 'shows impersonate button for other users' do
- expect(page).to have_content('Impersonate')
+ subject { visit admin_user_path(user_to_visit) }
+
+ let(:user_to_visit) { another_user }
+
+ context 'for other users' do
+ it 'shows impersonate button for other users' do
+ subject
+
+ expect(page).to have_content('Impersonate')
+ end
end
- it 'does not show impersonate button for admin itself' do
- visit admin_user_path(current_user)
+ context 'for admin itself' do
+ let(:user_to_visit) { current_user }
- expect(page).not_to have_content('Impersonate')
+ it 'does not show impersonate button for admin itself' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
end
- it 'does not show impersonate button for blocked user' do
- another_user.block
+ context 'for blocked user' do
+ before do
+ another_user.block
+ end
- visit admin_user_path(another_user)
+ it 'does not show impersonate button for blocked user' do
+ subject
- expect(page).not_to have_content('Impersonate')
+ expect(page).not_to have_content('Impersonate')
+ end
+ end
+
+ context 'when impersonation is disabled' do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ end
- another_user.activate
+ it 'does not show impersonate button' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
end
end
context 'when impersonating' do
+ subject { click_link 'Impersonate' }
+
before do
- click_link 'Impersonate'
+ visit admin_user_path(another_user)
end
it 'logs in as the user when impersonate is clicked' do
+ subject
+
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
end
it 'sees impersonation log out icon' do
- icon = first('.fa.fa-user-secret')
+ subject
+ icon = first('.fa.fa-user-secret')
expect(icon).not_to be nil
end
- it 'logs out of impersonated user back to original user' do
- find(:css, 'li.impersonation a').click
-
- expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
- end
+ context 'a user with an expired password' do
+ before do
+ another_user.update(password_expires_at: Time.now - 5.minutes)
+ end
- it 'is redirected back to the impersonated users page in the admin after stopping' do
- find(:css, 'li.impersonation a').click
+ it 'does not redirect to password change page' do
+ subject
- expect(current_path).to eq("/admin/users/#{another_user.username}")
+ expect(current_path).to eq('/')
+ end
end
end
- context 'when impersonating a user with an expired password' do
+ context 'ending impersonation' do
+ subject { find(:css, 'li.impersonation a').click }
+
before do
- another_user.update(password_expires_at: Time.now - 5.minutes)
+ visit admin_user_path(another_user)
click_link 'Impersonate'
end
- it 'does not redirect to password change page' do
- expect(current_path).to eq('/')
+ it 'logs out of impersonated user back to original user' do
+ subject
+
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
end
it 'is redirected back to the impersonated users page in the admin after stopping' do
- find(:css, 'li.impersonation a').click
+ subject
expect(current_path).to eq("/admin/users/#{another_user.username}")
end
+
+ context 'a user with an expired password' do
+ before do
+ another_user.update(password_expires_at: Time.now - 5.minutes)
+ end
+
+ it 'is redirected back to the impersonated users page in the admin after stopping' do
+ subject
+
+ expect(current_path).to eq("/admin/users/#{another_user.username}")
+ end
+ end
end
end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
deleted file mode 100644
index 259f22139ef..00000000000
--- a/spec/features/explore/new_menu_spec.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-require 'spec_helper'
-
-describe 'Top Plus Menu', :js do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
- let(:public_project) { create(:project, :public) }
-
- before do
- group.add_owner(user)
- end
-
- context 'used by full user' do
- before do
- sign_in(user)
- end
-
- it 'click on New project shows new project page' do
- visit root_dashboard_path
-
- click_topmenuitem("New project")
-
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project name')
- end
-
- it 'click on New group shows new group page' do
- visit root_dashboard_path
-
- click_topmenuitem("New group")
-
- expect(page).to have_content('Group URL')
- expect(page).to have_content('Group name')
- end
-
- it 'click on New snippet shows new snippet page' do
- visit root_dashboard_path
-
- click_topmenuitem("New snippet")
-
- expect(page).to have_content('New Snippet')
- expect(page).to have_content('Title')
- end
-
- it 'click on New issue shows new issue page' do
- visit project_path(project)
-
- click_topmenuitem("New issue")
-
- expect(page).to have_content('New Issue')
- expect(page).to have_content('Title')
- end
-
- it 'click on New merge request shows new merge request page' do
- visit project_path(project)
-
- click_topmenuitem("New merge request")
-
- expect(page).to have_content('New Merge Request')
- expect(page).to have_content('Source branch')
- expect(page).to have_content('Target branch')
- end
-
- it 'click on New project snippet shows new snippet page' do
- visit project_path(project)
-
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- find('.header-new-project-snippet a').click
- end
-
- expect(page).to have_content('New Snippet')
- expect(page).to have_content('Title')
- end
-
- it 'Click on New subgroup shows new group page', :nested_groups do
- visit group_path(group)
-
- click_topmenuitem("New subgroup")
-
- expect(page).to have_content('Group URL')
- expect(page).to have_content('Group name')
- end
-
- it 'Click on New project in group shows new project page' do
- visit group_path(group)
-
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- find('.header-new-group-project a').click
- end
-
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project name')
- end
- end
-
- context 'used by guest user' do
- let(:guest_user) { create(:user) }
-
- before do
- group.add_guest(guest_user)
- project.add_guest(guest_user)
-
- sign_in(guest_user)
- end
-
- it 'click on New issue shows new issue page' do
- visit project_path(project)
-
- click_topmenuitem("New issue")
-
- expect(page).to have_content('New Issue')
- expect(page).to have_content('Title')
- end
-
- it 'has no New merge request menu item' do
- visit project_path(project)
-
- hasnot_topmenuitem("New merge request")
- end
-
- it 'has no New project snippet menu item' do
- visit project_path(project)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
- end
-
- it 'public project has no New merge request menu item' do
- visit project_path(public_project)
-
- hasnot_topmenuitem("New merge request")
- end
-
- it 'public project has no New project snippet menu item' do
- visit project_path(public_project)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
- end
-
- it 'has no New subgroup menu item' do
- visit group_path(group)
-
- hasnot_topmenuitem("New subgroup")
- end
-
- it 'has no New project for group menu item' do
- visit group_path(group)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project')
- end
- end
-
- def click_topmenuitem(item_name)
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- click_link item_name
- end
- end
-
- def hasnot_topmenuitem(item_name)
- expect(find('.header-new.dropdown')).not_to have_content(item_name)
- end
-end
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index ba5b80ed04b..b4b9a589ba3 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -40,6 +40,18 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content
end
+
+ it "does not render html content in mermaid" do
+ html_content = "<img onerror=location=`javascript\\u003aalert\\u0028document.domain\\u0029` src=x>"
+ mermaid_content = "graph LR\n B-->D(#{html_content});"
+ comment = "```mermaid\n#{mermaid_content}\n```"
+
+ add_note(comment)
+
+ wait_for_requests
+
+ expect(page.find('svg.mermaid')).to have_content html_content
+ end
end
context "when editing comments" do
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index ca234321235..43369f7609f 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -1,15 +1,15 @@
require 'rails_helper'
-describe 'New issue breadcrumbs' do
+describe 'New issue breadcrumb' do
let(:project) { create(:project) }
- let(:user) { project.creator }
+ let(:user) { project.creator }
before do
sign_in(user)
- visit new_project_issue_path(project)
+ visit(new_project_issue_path(project))
end
- it 'display a link to project issues and new issue pages' do
+ it 'displays link to project issues and new issue' do
page.within '.breadcrumbs' do
expect(find_link('Issues')[:href]).to end_with(project_issues_path(project))
expect(find_link('New')[:href]).to end_with(new_project_issue_path(project))
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 5c1ffb76351..406e80e91aa 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -682,6 +682,18 @@ describe 'Issues' do
expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug')
end
end
+
+ context 'suggestions', :js do
+ it 'displays list of related issues' do
+ create(:issue, project: project, title: 'test issue')
+
+ visit new_project_issue_path(project)
+
+ fill_in 'issue_title', with: issue.title
+
+ expect(page).to have_selector('.suggestion-item', count: 1)
+ end
+ end
end
describe 'new issue by email' do
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index a25d701ee35..7008b361394 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -18,7 +18,7 @@ describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
%w[A B C D].each do |label|
- expect(page).to have_selector('svg foreignObject', text: label)
+ expect(page).to have_selector('svg text', text: label)
end
end
end
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 87613a4fdce..328f96e6ed7 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -50,7 +50,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
find('.line-resolve-btn').click
expect(page).to have_selector('.line-resolve-btn.is-active')
- expect(find('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(find('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}")
end
page.within '.diff-content' do
@@ -243,7 +243,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
resolve_button.click
wait_for_requests
- expect(resolve_button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(resolve_button['aria-label']).to eq("Resolved by #{user.name}")
end
end
@@ -266,7 +266,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
wait_for_requests
- expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(first('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}")
end
expect(page).to have_content('Last updated')
@@ -285,7 +285,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
wait_for_requests
resolve_buttons.each do |button|
- expect(button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(button['aria-label']).to eq("Resolved by #{user.name}")
end
page.within '.line-resolve-all-container' do
@@ -357,13 +357,12 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
resolve_button.click
wait_for_requests
- expect(resolve_button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(resolve_button['aria-label']).to eq("Resolved by #{user.name}")
end
end
- it 'shows jump to next discussion button, apart from the last one' do
- expect(page).to have_selector('.discussion-reply-holder', count: 2)
- expect(page).to have_selector('.discussion-reply-holder .discussion-next-btn', count: 1)
+ it 'shows jump to next discussion button' do
+ expect(page.all('.discussion-reply-holder', count: 2)).to all(have_selector('.discussion-next-btn'))
end
it 'displays next discussion even if hidden' do
diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
index f17acb35a5a..18d204da17a 100644
--- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
@@ -1,15 +1,15 @@
require 'rails_helper'
-describe 'New merge request breadcrumbs' do
+describe 'New merge request breadcrumb' do
let(:project) { create(:project, :repository) }
- let(:user) { project.creator }
+ let(:user) { project.creator }
before do
sign_in(user)
- visit project_new_merge_request_path(project)
+ visit(project_new_merge_request_path(project))
end
- it 'display a link to project merge requests and new merge request pages' do
+ it 'displays link to project merge requests and new merge request' do
page.within '.breadcrumbs' do
expect(find_link('Merge Requests')[:href]).to end_with(project_merge_requests_path(project))
expect(find_link('New')[:href]).to end_with(project_new_merge_request_path(project))
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 74290c0fff9..3e40179ad9a 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -65,7 +65,20 @@ describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
- expect(page).to have_content("Deploying to #{environment.name}")
+ expect(page).to have_content("Will deploy to #{environment.name}")
+ expect(page).not_to have_css('.js-deploy-time')
+ end
+ end
+
+ context 'when deployment was cancelled' do
+ let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, :canceled, environment: environment, sha: sha, ref: ref, deployable: build) }
+
+ it 'displays that the environment name' do
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+
+ expect(page).to have_content("Failed to deploy to #{environment.name}")
expect(page).not_to have_css('.js-deploy-time')
end
end
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 7b8c3bacfe2..4ab9a87ad4b 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -53,13 +53,11 @@ describe 'Merge request > User sees discussions', :js do
shared_examples 'a functional discussion' do
let(:discussion_id) { note.discussion_id(merge_request) }
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'is displayed' do
+ it 'is displayed' do
expect(page).to have_css(".discussion[data-discussion-id='#{discussion_id}']")
end
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'can be replied to' do
+ it 'can be replied to' do
within(".discussion[data-discussion-id='#{discussion_id}']") do
click_button 'Reply...'
fill_in 'note[note]', with: 'Test!'
@@ -74,16 +72,21 @@ describe 'Merge request > User sees discussions', :js do
visit project_merge_request_path(project, merge_request)
end
- context 'a regular commit comment' do
- let(:note) { create(:note_on_commit, project: project) }
-
- it_behaves_like 'a functional discussion'
- end
+ # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+ # context 'a regular commit comment' do
+ # let(:note) { create(:note_on_commit, project: project) }
+ #
+ # it_behaves_like 'a functional discussion'
+ # end
context 'a commit diff comment' do
let(:note) { create(:diff_note_on_commit, project: project) }
it_behaves_like 'a functional discussion'
+
+ it 'displays correct header' do
+ expect(page).to have_content "started a discussion on commit #{note.commit_id[0...7]}"
+ end
end
end
end
diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb
new file mode 100644
index 00000000000..df1bc502134
--- /dev/null
+++ b/spec/features/milestones/user_promotes_milestone_spec.rb
@@ -0,0 +1,32 @@
+require 'rails_helper'
+
+describe 'User promotes milestone' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:milestone) { create(:milestone, project: project) }
+
+ context 'when user can admin group milestones' do
+ before do
+ group.add_developer(user)
+ sign_in(user)
+ visit(project_milestones_path(project))
+ end
+
+ it "shows milestone promote button" do
+ expect(page).to have_selector('.js-promote-project-milestone-button')
+ end
+ end
+
+ context 'when user cannot admin group milestones' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ visit(project_milestones_path(project))
+ end
+
+ it "does not show milestone promote button" do
+ expect(page).not_to have_selector('.js-promote-project-milestone-button')
+ end
+ end
+end
diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
new file mode 100644
index 00000000000..d3906ea73bd
--- /dev/null
+++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+describe 'New project milestone breadcrumb' do
+ let(:project) { create(:project) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ visit(new_project_milestone_path(project))
+ end
+
+ it 'displays link to project milestones and new project milestone' do
+ page.within '.breadcrumbs' do
+ expect(find_link('Milestones')[:href]).to end_with(project_milestones_path(project))
+ expect(find_link('New')[:href]).to end_with(new_project_milestone_path(project))
+ end
+ end
+end
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 534cfe1eb12..2159adf49fc 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -4,10 +4,9 @@ describe 'User browses commits' do
include RepoHelpers
let(:user) { create(:user) }
- let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:project) { create(:project, :public, :repository, namespace: user.namespace) }
before do
- project.add_maintainer(user)
sign_in(user)
end
@@ -127,6 +126,26 @@ describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end
+ context 'when a commit links to a confidential issue' do
+ let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
+
+ before do
+ project.repository.create_file(user, 'dummy-file', 'dummy content',
+ branch_name: 'feature',
+ message: "Linking #{confidential_issue.to_reference}")
+ end
+
+ context 'when the user cannot see confidential issues but was cached with a link', :use_clean_rails_memory_store_fragment_caching do
+ it 'does not render the confidential issue' do
+ visit project_commits_path(project, 'feature')
+ sign_in(create(:user))
+ visit project_commits_path(project, 'feature')
+
+ expect(page).not_to have_link(href: project_issue_path(project, confidential_issue))
+ end
+ end
+ end
+
context 'master branch' do
before do
visit_commits_page
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 9772a7bacac..a8a3b6910fb 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -165,8 +165,14 @@ describe 'Environment' do
context 'web terminal', :js do
before do
- # Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
- allow_any_instance_of(Environment).to receive(:terminals) { nil }
+ # Stub #terminals as it causes js-enabled feature specs to
+ # render the page incorrectly
+ #
+ # In EE we have to stub EE::Environment since it overwrites
+ # the "terminals" method.
+ allow_any_instance_of(defined?(EE) ? EE::Environment : Environment)
+ .to receive(:terminals) { nil }
+
visit terminal_project_environment_path(project, environment)
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 24352be592a..d7c4abffddd 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -754,7 +754,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because no runners are active' do
expect(page).to have_css('.js-stuck-no-active-runner')
- expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
+ expect(page).to have_content("This job is stuck because you don't have any active runners that can run this job.")
end
end
@@ -764,7 +764,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
- expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
+ expect(page).to have_content("This job is stuck because you don't have any active runners online with any of these tags assigned to them:")
end
end
@@ -774,7 +774,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
- expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
+ expect(page).to have_content("This job is stuck because you don't have any active runners online with any of these tags assigned to them:")
end
end
@@ -783,7 +783,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because not runners are available' do
expect(page).to have_css('.js-stuck-no-active-runner')
- expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
+ expect(page).to have_content("This job is stuck because you don't have any active runners that can run this job.")
end
end
@@ -793,7 +793,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because runners are offline' do
expect(page).to have_css('.js-stuck-no-runners')
- expect(page).to have_content("This job is stuck, because the project doesn't have any runners online assigned to it.")
+ expect(page).to have_content("This job is stuck because the project doesn't have any runners online assigned to it.")
end
end
end
diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
new file mode 100644
index 00000000000..0c0501f438a
--- /dev/null
+++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe 'New project label breadcrumb' do
+ let(:project) { create(:project) }
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it 'displays link to project labels and new project label' do
+ page.within '.breadcrumbs' do
+ expect(find_link('Labels')[:href]).to end_with(project_labels_path(project))
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 571104d3467..48a0d675f2d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -85,7 +85,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "test"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Test").and have_content("Create Page")
+ expect(page).to have_content("test").and have_content("Create Page")
end
click_link("Home")
@@ -97,7 +97,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "api"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("Api")
+ expect(page).to have_content("Create").and have_content("api")
end
click_link("Home")
@@ -109,7 +109,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "raketasks"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("Rake")
+ expect(page).to have_content("Create").and have_content("rake")
end
end
@@ -200,7 +200,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Foo")
+ expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -215,7 +215,7 @@ describe "User creates wiki page" do
end
# Commit message field should have correct value.
- expect(page).to have_field("wiki[message]", with: "Create spaces in the name")
+ expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
page.within(".wiki-form") do
fill_in(:wiki_content, with: "My awesome wiki!")
@@ -246,7 +246,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Hyphens in the name")
+ expect(page).to have_content("hyphens in the name")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -293,7 +293,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Foo")
+ expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -311,7 +311,7 @@ describe "User creates wiki page" do
it 'renders a default sidebar when there is no customized sidebar' do
visit(project_wikis_path(project))
- expect(page).to have_content('Another')
+ expect(page).to have_content('another')
expect(page).to have_content('More Pages')
end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 7ad7fec922a..f76e577b0d6 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -39,11 +39,11 @@ describe 'User updates wiki page' do
end
expect(current_path).to include('one/two/three-test')
- expect(find('.wiki-pages')).to have_content('Three')
+ expect(find('.wiki-pages')).to have_content('three')
- first(:link, text: 'Three').click
+ first(:link, text: 'three').click
- expect(find('.nav-text')).to have_content('Three')
+ expect(find('.nav-text')).to have_content('three')
click_on('Edit')
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 3c93d71ab00..d4691b669c1 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -38,7 +38,7 @@ describe 'User views a wiki page' do
it 'shows the history of a page that has a path', :js do
expect(current_path).to include('one/two/three-test')
- first(:link, text: 'Three').click
+ first(:link, text: 'three').click
click_on('Page history')
expect(current_path).to include('one/two/three-test')
@@ -50,11 +50,11 @@ describe 'User views a wiki page' do
it 'shows an old version of a page', :js do
expect(current_path).to include('one/two/three-test')
- expect(find('.wiki-pages')).to have_content('Three')
+ expect(find('.wiki-pages')).to have_content('three')
- first(:link, text: 'Three').click
+ first(:link, text: 'three').click
- expect(find('.nav-text')).to have_content('Three')
+ expect(find('.nav-text')).to have_content('three')
click_on('Edit')
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index ef0e55a1468..e2b3444272e 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -1,25 +1,22 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'GPG signed commits', :js do
- set(:ref) { :'2d1096e3a0ecf1d2baf6dee036cc80775d4940ba' }
- let(:project) { create(:project, :repository) }
+describe 'GPG signed commits' do
+ let(:project) { create(:project, :public, :repository) }
it 'changes from unverified to verified when the user changes his email to match the gpg key' do
- user = create :user, email: 'unrelated.user@example.org'
- project.add_maintainer(user)
+ ref = GpgHelpers::SIGNED_AND_AUTHORED_SHA
+ user = create(:user, email: 'unrelated.user@example.org')
perform_enqueued_jobs do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
- sign_in(user)
-
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
+ expect(page).to have_link 'Unverified'
+ expect(page).not_to have_link 'Verified'
# user changes his email which makes the gpg key verified
perform_enqueued_jobs do
@@ -27,41 +24,33 @@ describe 'GPG signed commits', :js do
user.update!(email: GpgHelpers::User1.emails.first)
end
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
+ expect(page).not_to have_link 'Unverified'
+ expect(page).to have_link 'Verified'
end
it 'changes from unverified to verified when the user adds the missing gpg key' do
- user = create :user, email: GpgHelpers::User1.emails.first
- project.add_maintainer(user)
+ ref = GpgHelpers::SIGNED_AND_AUTHORED_SHA
+ user = create(:user, email: GpgHelpers::User1.emails.first)
- sign_in(user)
+ visit project_commit_path(project, ref)
- visit project_commits_path(project, ref)
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
+ expect(page).to have_link 'Unverified'
+ expect(page).not_to have_link 'Verified'
# user adds the gpg key which makes the signature valid
perform_enqueued_jobs do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
+ expect(page).not_to have_link 'Unverified'
+ expect(page).to have_link 'Verified'
end
- context 'shows popover badges' do
+ context 'shows popover badges', :js do
let(:user_1) do
create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
end
@@ -85,19 +74,10 @@ describe 'GPG signed commits', :js do
end
end
- before do
- user = create :user
- project.add_maintainer(user)
-
- sign_in(user)
- end
-
it 'unverified signature' do
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
- within(find('.commit', text: 'signed commit by bette cartwright')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content 'This commit was signed with an unverified signature.'
@@ -108,11 +88,9 @@ describe 'GPG signed commits', :js do
it 'unverified signature: user email does not match the committer email, but is the same user' do
user_2_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::DIFFERING_EMAIL_SHA)
- within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.'
@@ -125,11 +103,9 @@ describe 'GPG signed commits', :js do
it 'unverified signature: user email does not match the committer email' do
user_2_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
- within(find('.commit', text: 'signed commit by bette cartwright')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content "This commit was signed with a different user's verified signature."
@@ -142,11 +118,9 @@ describe 'GPG signed commits', :js do
it 'verified and the gpg user has a gitlab profile' do
user_1_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- click_on 'Verified'
- end
+ click_on 'Verified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
@@ -159,20 +133,16 @@ describe 'GPG signed commits', :js do
it "verified and the gpg user's profile doesn't exist anymore" do
user_1_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
# wait for the signature to get generated
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- expect(page).to have_content 'Verified'
- end
+ expect(page).to have_link 'Verified'
user_1.destroy!
refresh
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- click_on 'Verified'
- end
+ click_on 'Verified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
new file mode 100644
index 00000000000..ca90673521c
--- /dev/null
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Resolvers::IssuesResolver do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:issue) { create(:issue, project: project) }
+ set(:issue2) { create(:issue, project: project, title: 'foo') }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ describe '#resolve' do
+ it 'finds all issues' do
+ expect(resolve_issues).to contain_exactly(issue, issue2)
+ end
+
+ it 'searches issues' do
+ expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
+ end
+
+ it 'sort issues' do
+ expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue]
+ end
+
+ it 'returns issues user can see' do
+ project.add_guest(current_user)
+
+ create(:issue, confidential: true)
+
+ expect(resolve_issues).to contain_exactly(issue, issue2)
+ end
+ end
+
+ def resolve_issues(args = {}, context = { current_user: current_user })
+ resolve(described_class, obj: project, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
new file mode 100644
index 00000000000..63a07647a60
--- /dev/null
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Issue'] do
+ it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
+
+ it { expect(described_class.graphql_name).to eq('Issue') }
+end
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
new file mode 100644
index 00000000000..c3f84629aa2
--- /dev/null
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe Types::PermissionTypes::Issue do
+ it do
+ expected_permissions = [
+ :read_issue, :admin_issue, :update_issue,
+ :create_note, :reopen_issue
+ ]
+
+ expect(described_class).to have_graphql_fields(expected_permissions)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 49606c397b9..61d4c42665a 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -14,5 +14,9 @@ describe GitlabSchema.types['Project'] do
end
end
+ describe 'nested issues' do
+ it { expect(described_class).to have_graphql_field(:issues) }
+ end
+
it { is_expected.to have_graphql_field(:pipelines) }
end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index ab4566e261b..4a62e696cd9 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -5,6 +5,16 @@ describe TreeHelper do
let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
+ def create_file(filename)
+ project.repository.create_file(
+ project.creator,
+ filename,
+ 'test this',
+ message: "Automatically created file #{filename}",
+ branch_name: 'master'
+ )
+ end
+
describe '.render_tree' do
before do
@id = sha
@@ -57,6 +67,15 @@ describe TreeHelper do
expect(fast_path).to start_with('/gitlab/root')
end
+
+ it 'encodes files starting with #' do
+ filename = '#test-file'
+ create_file(filename)
+
+ fast_path = fast_project_blob_path(project, filename)
+
+ expect(fast_path).to end_with('%23test-file')
+ end
end
describe '.fast_project_tree_path' do
@@ -73,6 +92,15 @@ describe TreeHelper do
expect(fast_path).to start_with('/gitlab/root')
end
+
+ it 'encodes files starting with #' do
+ filename = '#test-file'
+ create_file(filename)
+
+ fast_path = fast_project_tree_path(project, filename)
+
+ expect(fast_path).to end_with('%23test-file')
+ end
end
describe 'flatten_tree' do
diff --git a/spec/initializers/attr_encrypted_no_db_connection_spec.rb b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
new file mode 100644
index 00000000000..2da9f1cbd96
--- /dev/null
+++ b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe 'GitLab monkey-patches to AttrEncrypted' do
+ describe '#attribute_instance_methods_as_symbols_available?' do
+ it 'returns false' do
+ expect(ActiveRecord::Base.__send__(:attribute_instance_methods_as_symbols_available?)).to be_falsy
+ end
+
+ it 'does not define virtual attributes' do
+ klass = Class.new(ActiveRecord::Base) do
+ # We need some sort of table to work on
+ self.table_name = 'projects'
+
+ attr_encrypted :foo
+ end
+
+ instance = klass.new
+
+ aggregate_failures do
+ %w[
+ encrypted_foo encrypted_foo=
+ encrypted_foo_iv encrypted_foo_iv=
+ encrypted_foo_salt encrypted_foo_salt=
+ ].each do |method_name|
+ expect(instance).not_to respond_to(method_name)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 091edf13cfe..7de38913bae 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -123,7 +123,7 @@ describe('Api', () => {
});
});
- describe('mergerequest', () => {
+ describe('projectMergeRequest', () => {
it('fetches a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -132,7 +132,7 @@ describe('Api', () => {
title: 'test',
});
- Api.mergeRequest(projectPath, mergeRequestId)
+ Api.projectMergeRequest(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.title).toBe('test');
})
@@ -141,7 +141,7 @@ describe('Api', () => {
});
});
- describe('mergerequest changes', () => {
+ describe('projectMergeRequestChanges', () => {
it('fetches the changes of a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -150,7 +150,7 @@ describe('Api', () => {
title: 'test',
});
- Api.mergeRequestChanges(projectPath, mergeRequestId)
+ Api.projectMergeRequestChanges(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.title).toBe('test');
})
@@ -159,7 +159,7 @@ describe('Api', () => {
});
});
- describe('mergerequest versions', () => {
+ describe('projectMergeRequestVersions', () => {
it('fetches the versions of a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -170,7 +170,7 @@ describe('Api', () => {
},
]);
- Api.mergeRequestVersions(projectPath, mergeRequestId)
+ Api.projectMergeRequestVersions(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.length).toBe(1);
expect(data[0].id).toBe(123);
diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js
new file mode 100644
index 00000000000..759d170af77
--- /dev/null
+++ b/spec/javascripts/blob_edit/blob_bundle_spec.js
@@ -0,0 +1,30 @@
+import blobBundle from '~/blob_edit/blob_bundle';
+import $ from 'jquery';
+
+window.ace = {
+ config: {
+ set: () => {},
+ loadModule: () => {},
+ },
+ edit: () => ({ focus: () => {} }),
+};
+
+describe('EditBlob', () => {
+ beforeEach(() => {
+ setFixtures(`
+ <div class="js-edit-blob-form">
+ <button class="js-commit-button"></button>
+ </div>`);
+ blobBundle();
+ });
+
+ it('sets the window beforeunload listener to a function returning a string', () => {
+ expect(window.onbeforeunload()).toBe('');
+ });
+
+ it('removes beforeunload listener if commit button is clicked', () => {
+ $('.js-commit-button').click();
+
+ expect(window.onbeforeunload).toBeNull();
+ });
+});
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 6a08d08f33e..7ea0878ad45 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -107,6 +107,7 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
hostname: null,
+ externalIp: null,
},
cert_manager: {
title: 'Cert-Manager',
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 9530b50c729..b77907ff26f 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -464,7 +464,11 @@ describe('diff_file_header', () => {
propsCopy.addMergeRequestButtons = true;
propsCopy.diffFile.deleted_file = true;
- const discussionGetter = () => [diffDiscussionMock];
+ const discussionGetter = () => [
+ {
+ ...diffDiscussionMock,
+ },
+ ];
const notesModuleMock = notesModule();
notesModuleMock.getters.discussions = discussionGetter;
vm = mountComponentWithStore(Component, {
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
index 81b66cf7c9b..b983dc35a57 100644
--- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -62,6 +62,7 @@ describe('DiffLineNoteForm', () => {
component.$nextTick(() => {
expect(component.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[0].line_code,
+ fileHash: component.diffFileHash,
});
expect(component.resetAutoSave).toHaveBeenCalled();
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index a274377f389..205138bd845 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -319,13 +319,13 @@ describe('DiffsStoreActions', () => {
describe('showCommentForm', () => {
it('should call mutation to show comment form', done => {
- const payload = { lineCode: 'lineCode' };
+ const payload = { lineCode: 'lineCode', fileHash: 'hash' };
testAction(
showCommentForm,
payload,
{},
- [{ type: types.ADD_COMMENT_FORM_LINE, payload }],
+ [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: true } }],
[],
done,
);
@@ -334,13 +334,13 @@ describe('DiffsStoreActions', () => {
describe('cancelCommentForm', () => {
it('should call mutation to cancel comment form', done => {
- const payload = { lineCode: 'lineCode' };
+ const payload = { lineCode: 'lineCode', fileHash: 'hash' };
testAction(
cancelCommentForm,
payload,
{},
- [{ type: types.REMOVE_COMMENT_FORM_LINE, payload }],
+ [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: false } }],
[],
done,
);
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index eef95c823fb..582535e0a53 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -186,77 +186,6 @@ describe('Diffs Module Getters', () => {
});
});
- describe('shouldRenderParallelCommentRow', () => {
- let line;
-
- beforeEach(() => {
- line = {};
-
- discussionMock.expanded = true;
-
- line.left = {
- line_code: 'ABC',
- discussions: [discussionMock],
- };
-
- line.right = {
- line_code: 'DEF',
- discussions: [discussionMock1],
- };
- });
-
- it('returns true when discussion is expanded', () => {
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
- });
-
- it('returns false when no discussion was found', () => {
- line.left.discussions = [];
- line.right.discussions = [];
-
- localState.diffLineCommentForms.ABC = false;
- localState.diffLineCommentForms.DEF = false;
-
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(false);
- });
-
- it('returns true when discussionForm was found', () => {
- localState.diffLineCommentForms.ABC = {};
-
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
- });
- });
-
- describe('shouldRenderInlineCommentRow', () => {
- let line;
-
- beforeEach(() => {
- discussionMock.expanded = true;
-
- line = {
- lineCode: 'ABC',
- discussions: [discussionMock],
- };
- });
-
- it('returns true when diffLineCommentForms has form', () => {
- localState.diffLineCommentForms.ABC = {};
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
- });
-
- it('returns false when no line discussions were found', () => {
- line.discussions = [];
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(false);
- });
-
- it('returns true if all found discussions are expanded', () => {
- discussionMock.expanded = true;
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
- });
- });
-
describe('getDiffFileDiscussions', () => {
it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
discussionMock.diff_file.file_hash = diffFileMock.file_hash;
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 9844a8c9bdf..7a06c178f0b 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -55,32 +55,6 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('ADD_COMMENT_FORM_LINE', () => {
- it('should set a truthy reference for the given line code in diffLineCommentForms', () => {
- const state = { diffLineCommentForms: {} };
- const lineCode = 'FDE';
-
- mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
- });
- });
-
- describe('REMOVE_COMMENT_FORM_LINE', () => {
- it('should remove given reference from diffLineCommentForms', () => {
- const state = { diffLineCommentForms: {} };
- const lineCode = 'FDE';
-
- mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
-
- mutations[types.REMOVE_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeUndefined();
- });
- });
-
describe('EXPAND_ALL_FILES', () => {
it('should change the collapsed prop from diffFiles', () => {
const diffFile = {
@@ -98,7 +72,9 @@ describe('DiffsStoreMutations', () => {
it('should call utils.addContextLines with proper params', () => {
const options = {
lineNumbers: { oldLineNumber: 1, newLineNumber: 2 },
- contextLines: [{ old_line: 1, new_line: 1, line_code: 'ff9200_1_1', discussions: [] }],
+ contextLines: [
+ { old_line: 1, new_line: 1, line_code: 'ff9200_1_1', discussions: [], hasForm: false },
+ ],
fileHash: 'ff9200',
params: {
bottom: true,
@@ -393,4 +369,35 @@ describe('DiffsStoreMutations', () => {
expect(state.highlightedRow).toBe('ABC_123');
});
});
+
+ describe('TOGGLE_LINE_HAS_FORM', () => {
+ it('sets hasForm on lines', () => {
+ const file = {
+ file_hash: 'hash',
+ parallel_diff_lines: [
+ { left: { line_code: '123', hasForm: false }, right: {} },
+ { left: {}, right: { line_code: '124', hasForm: false } },
+ ],
+ highlighted_diff_lines: [
+ { line_code: '123', hasForm: false },
+ { line_code: '124', hasForm: false },
+ ],
+ };
+ const state = {
+ diffFiles: [file],
+ };
+
+ mutations[types.TOGGLE_LINE_HAS_FORM](state, {
+ lineCode: '123',
+ hasForm: true,
+ fileHash: 'hash',
+ });
+
+ expect(file.highlighted_diff_lines[0].hasForm).toBe(true);
+ expect(file.highlighted_diff_lines[1].hasForm).toBe(false);
+
+ expect(file.parallel_diff_lines[0].left.hasForm).toBe(true);
+ expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
index 04925d37ac4..9e2ba1f5ce9 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
@@ -14,10 +14,14 @@ import testAction from '../../../../helpers/vuex_action_helper';
describe('IDE merge requests actions', () => {
let mockedState;
+ let mockedRootState;
let mock;
beforeEach(() => {
mockedState = state();
+ mockedRootState = {
+ currentProjectId: 7,
+ };
mock = new MockAdapter(axios);
});
@@ -86,13 +90,16 @@ describe('IDE merge requests actions', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(200, mergeRequests);
+ mock.onGet(/\/api\/v4\/merge_requests\/?/).replyOnce(200, mergeRequests);
});
it('calls API with params', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
- fetchMergeRequests({ dispatch() {}, state: mockedState }, { type: 'created' });
+ fetchMergeRequests(
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
+ { type: 'created' },
+ );
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
@@ -107,7 +114,7 @@ describe('IDE merge requests actions', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
fetchMergeRequests(
- { dispatch() {}, state: mockedState },
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
{ type: 'created', search: 'testing search' },
);
@@ -139,6 +146,49 @@ describe('IDE merge requests actions', () => {
});
});
+ describe('success without type', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/.+\/merge_requests\/?$/).replyOnce(200, mergeRequests);
+ });
+
+ it('calls API with project', () => {
+ const apiSpy = spyOn(axios, 'get').and.callThrough();
+
+ fetchMergeRequests(
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
+ { type: null, search: 'testing search' },
+ );
+
+ expect(apiSpy).toHaveBeenCalledWith(
+ jasmine.stringMatching(`projects/${mockedRootState.currentProjectId}/merge_requests`),
+ {
+ params: {
+ state: 'opened',
+ search: 'testing search',
+ },
+ },
+ );
+ });
+
+ it('dispatches success with received data', done => {
+ testAction(
+ fetchMergeRequests,
+ { type: null },
+ { ...mockedState, ...mockedRootState },
+ [],
+ [
+ { type: 'requestMergeRequests' },
+ { type: 'resetMergeRequests' },
+ {
+ type: 'receiveMergeRequestsSuccess',
+ payload: mergeRequests,
+ },
+ ],
+ done,
+ );
+ });
+ });
+
describe('error', () => {
beforeEach(() => {
mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(500);
diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/javascripts/issuable_suggestions/components/app_spec.js
new file mode 100644
index 00000000000..7bb8e26b81a
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/components/app_spec.js
@@ -0,0 +1,96 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/issuable_suggestions/components/app.vue';
+import Suggestion from '~/issuable_suggestions/components/item.vue';
+
+describe('Issuable suggestions app component', () => {
+ let vm;
+
+ function createComponent(search = 'search') {
+ vm = shallowMount(App, {
+ propsData: {
+ search,
+ projectPath: 'project',
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('does not render with empty search', () => {
+ createComponent('');
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ describe('with data', () => {
+ let data;
+
+ beforeEach(() => {
+ data = { issues: [{ id: 1 }, { id: 2 }] };
+ });
+
+ it('renders component', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(vm.isEmpty()).toBe(false);
+ });
+
+ it('does not render with empty search', () => {
+ createComponent('');
+ vm.setData(data);
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('does not render when loading', () => {
+ createComponent();
+ vm.setData({
+ ...data,
+ loading: 1,
+ });
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('does not render with empty issues data', () => {
+ createComponent();
+ vm.setData({ issues: [] });
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('renders list of issues', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(vm.findAll(Suggestion).length).toBe(2);
+ });
+
+ it('adds margin class to first item', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(
+ vm
+ .findAll('li')
+ .at(0)
+ .is('.append-bottom-default'),
+ ).toBe(true);
+ });
+
+ it('does not add margin class to last item', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(
+ vm
+ .findAll('li')
+ .at(1)
+ .is('.append-bottom-default'),
+ ).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/javascripts/issuable_suggestions/components/item_spec.js
new file mode 100644
index 00000000000..7bd1fe678f4
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/components/item_spec.js
@@ -0,0 +1,139 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlTooltip, GlLink } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import Suggestion from '~/issuable_suggestions/components/item.vue';
+import mockData from '../mock_data';
+
+describe('Issuable suggestions suggestion component', () => {
+ let vm;
+
+ function createComponent(suggestion = {}) {
+ vm = shallowMount(Suggestion, {
+ propsData: {
+ suggestion: {
+ ...mockData(),
+ ...suggestion,
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders title', () => {
+ createComponent();
+
+ expect(vm.text()).toContain('Test issue');
+ });
+
+ it('renders issue link', () => {
+ createComponent();
+
+ const link = vm.find(GlLink);
+
+ expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`);
+ });
+
+ it('renders IID', () => {
+ createComponent();
+
+ expect(vm.text()).toContain('#1');
+ });
+
+ describe('opened state', () => {
+ it('renders icon', () => {
+ createComponent();
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('issue-open-m');
+ });
+
+ it('renders created timeago', () => {
+ createComponent({
+ closedAt: '',
+ });
+
+ const tooltip = vm.find(GlTooltip);
+
+ expect(tooltip.find('.d-block').text()).toContain('Opened');
+ expect(tooltip.text()).toContain('3 days ago');
+ });
+ });
+
+ describe('closed state', () => {
+ it('renders icon', () => {
+ createComponent({
+ state: 'closed',
+ });
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('issue-close');
+ });
+
+ it('renders closed timeago', () => {
+ createComponent();
+
+ const tooltip = vm.find(GlTooltip);
+
+ expect(tooltip.find('.d-block').text()).toContain('Opened');
+ expect(tooltip.text()).toContain('1 day ago');
+ });
+ });
+
+ describe('author', () => {
+ it('renders author info', () => {
+ createComponent();
+
+ const link = vm.findAll(GlLink).at(1);
+
+ expect(link.text()).toContain('Author Name');
+ expect(link.text()).toContain('@author.username');
+ });
+
+ it('renders author image', () => {
+ createComponent();
+
+ const image = vm.find(UserAvatarImage);
+
+ expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`);
+ });
+ });
+
+ describe('counts', () => {
+ it('renders upvotes count', () => {
+ createComponent();
+
+ const count = vm.findAll('.suggestion-counts span').at(0);
+
+ expect(count.text()).toContain('1');
+ expect(count.find(Icon).props('name')).toBe('thumb-up');
+ });
+
+ it('renders notes count', () => {
+ createComponent();
+
+ const count = vm.findAll('.suggestion-counts span').at(1);
+
+ expect(count.text()).toContain('2');
+ expect(count.find(Icon).props('name')).toBe('comment');
+ });
+ });
+
+ describe('confidential', () => {
+ it('renders confidential icon', () => {
+ createComponent({
+ confidential: true,
+ });
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('eye-slash');
+ expect(icon.attributes('data-original-title')).toBe('Confidential');
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/javascripts/issuable_suggestions/mock_data.js
new file mode 100644
index 00000000000..4f0f9ef8d62
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/mock_data.js
@@ -0,0 +1,26 @@
+function getDate(daysMinus) {
+ const today = new Date();
+ today.setDate(today.getDate() - daysMinus);
+
+ return today.toISOString();
+}
+
+export default () => ({
+ id: 1,
+ iid: 1,
+ state: 'opened',
+ upvotes: 1,
+ userNotesCount: 2,
+ closedAt: getDate(1),
+ createdAt: getDate(3),
+ updatedAt: getDate(2),
+ confidential: false,
+ webUrl: `${gl.TEST_HOST}/test/issue/1`,
+ title: 'Test issue',
+ author: {
+ avatarUrl: `${gl.TEST_HOST}/avatar`,
+ name: 'Author Name',
+ username: 'author.username',
+ webUrl: `${gl.TEST_HOST}/author`,
+ },
+});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index fcf3780f0ea..ba5d672f189 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -160,9 +160,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners that can run this job.",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
done();
}, 0);
});
@@ -195,9 +193,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
done();
}, 0);
});
@@ -230,9 +226,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
done();
}, 0);
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 1c7691f865a..1ec1e8a8dd9 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -346,6 +346,24 @@ describe('common_utils', () => {
});
});
+ describe('parseBoolean', () => {
+ it('returns true for "true"', () => {
+ expect(commonUtils.parseBoolean('true')).toEqual(true);
+ });
+
+ it('returns false for "false"', () => {
+ expect(commonUtils.parseBoolean('false')).toEqual(false);
+ });
+
+ it('returns false for "something"', () => {
+ expect(commonUtils.parseBoolean('something')).toEqual(false);
+ });
+
+ it('returns false for null', () => {
+ expect(commonUtils.parseBoolean(null)).toEqual(false);
+ });
+ });
+
describe('convertPermissionToBoolean', () => {
it('should convert a boolean in a string to a boolean', () => {
expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true);
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 7714197c821..c8df05eccf5 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -239,4 +239,38 @@ describe('MergeRequestTabs', function() {
expect($('.content-wrapper')).toContainElement('.container-limited');
});
});
+
+ describe('tabShown', function() {
+ const mainContent = document.createElement('div');
+ const tabContent = document.createElement('div');
+
+ beforeEach(function() {
+ spyOn(mainContent, 'getBoundingClientRect').and.returnValue({ top: 10 });
+ spyOn(tabContent, 'getBoundingClientRect').and.returnValue({ top: 100 });
+ spyOn(document, 'querySelector').and.callFake(function(selector) {
+ return selector === '.content-wrapper' ? mainContent : tabContent;
+ });
+ this.class.currentAction = 'commits';
+ });
+
+ it('calls window scrollTo with options if document has scrollBehavior', function() {
+ document.documentElement.style.scrollBehavior = '';
+
+ spyOn(window, 'scrollTo');
+
+ this.class.tabShown('commits', 'foobar');
+
+ expect(window.scrollTo.calls.first().args[0]).toEqual({ top: 39, behavior: 'smooth' });
+ });
+
+ it('calls window scrollTo with two args if document does not have scrollBehavior', function() {
+ spyOnProperty(document.documentElement, 'style', 'get').and.returnValue({});
+
+ spyOn(window, 'scrollTo');
+
+ this.class.tabShown('commits', 'foobar');
+
+ expect(window.scrollTo.calls.first().args).toEqual([0, 39]);
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js
index 95461396f10..0752bd05904 100644
--- a/spec/javascripts/notes/components/diff_with_note_spec.js
+++ b/spec/javascripts/notes/components/diff_with_note_spec.js
@@ -17,7 +17,7 @@ describe('diff_with_note', () => {
};
const selectors = {
get container() {
- return vm.$refs.fileHolder;
+ return vm.$el;
},
get diffTable() {
return this.container.querySelector('.diff-content table');
@@ -70,7 +70,6 @@ describe('diff_with_note', () => {
it('shows image diff', () => {
vm = mountComponentWithStore(Component, { props, store });
- expect(selectors.container).toHaveClass('js-image-file');
expect(selectors.diffTable).not.toExist();
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 4b4403689d9..76e9cd03d2d 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -80,43 +80,6 @@ describe('noteable_discussion component', () => {
});
describe('computed', () => {
- describe('hasMultipleUnresolvedDiscussions', () => {
- it('is false if there are no unresolved discussions', done => {
- spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([]);
-
- Vue.nextTick()
- .then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('is false if there is one unresolved discussion', done => {
- spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([discussionMock]);
-
- Vue.nextTick()
- .then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('is true if there are two unresolved discussions', done => {
- const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
- discussion.notes[0].resolved = false;
- vm.$store.dispatch('setInitialNotes', [discussion, discussion]);
-
- Vue.nextTick()
- .then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(true);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
describe('isRepliesCollapsed', () => {
it('should return false for diff discussions', done => {
const diffDiscussion = getJSONFixture(diffDiscussionFixture)[0];
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index fcdd834e4a0..24c2b3e6570 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import $ from 'jquery';
import _ from 'underscore';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
@@ -330,10 +331,14 @@ describe('Actions Notes Store', () => {
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
+
+ $('body').attr('data-page', '');
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+
+ $('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
@@ -353,6 +358,39 @@ describe('Actions Notes Store', () => {
{
type: 'updateMergeRequestWidget',
},
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches removeDiscussionsFromDiff on merge request page', done => {
+ const note = { path: `${gl.TEST_HOST}`, id: 1 };
+
+ $('body').attr('data-page', 'projects:merge_requests:show');
+
+ testAction(
+ actions.deleteNote,
+ note,
+ store.state,
+ [
+ {
+ type: 'DELETE_NOTE',
+ payload: note,
+ },
+ ],
+ [
+ {
+ type: 'updateMergeRequestWidget',
+ },
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
+ type: 'diffs/removeDiscussionsFromDiff',
+ },
],
done,
);
@@ -399,6 +437,9 @@ describe('Actions Notes Store', () => {
{
type: 'startTaskList',
},
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
],
done,
);
@@ -472,6 +513,9 @@ describe('Actions Notes Store', () => {
],
[
{
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
type: 'updateMergeRequestWidget',
},
],
@@ -494,6 +538,9 @@ describe('Actions Notes Store', () => {
],
[
{
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
type: 'updateMergeRequestWidget',
},
],
@@ -525,4 +572,17 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('updateResolvableDiscussonsCounts', () => {
+ it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
+ testAction(
+ actions.updateResolvableDiscussonsCounts,
+ null,
+ {},
+ [{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index f853f9ff088..c066975a43b 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -117,17 +117,15 @@ describe('Getters Notes Store', () => {
describe('allResolvableDiscussions', () => {
it('should return only resolvable discussions in same order', () => {
- const localGetters = {
- allDiscussions: [
- discussion3,
- unresolvableDiscussion,
- discussion1,
- unresolvableDiscussion,
- discussion2,
- ],
- };
+ state.discussions = [
+ discussion3,
+ unresolvableDiscussion,
+ discussion1,
+ unresolvableDiscussion,
+ discussion2,
+ ];
- expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([
+ expect(getters.allResolvableDiscussions(state)).toEqual([
discussion3,
discussion1,
discussion2,
@@ -135,11 +133,9 @@ describe('Getters Notes Store', () => {
});
it('should return empty array if there are no resolvable discussions', () => {
- const localGetters = {
- allDiscussions: [unresolvableDiscussion, unresolvableDiscussion],
- };
+ state.discussions = [unresolvableDiscussion, unresolvableDiscussion];
- expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([]);
+ expect(getters.allResolvableDiscussions(state)).toEqual([]);
});
});
@@ -236,7 +232,7 @@ describe('Getters Notes Store', () => {
it('should return the ID of the discussion after the ID provided', () => {
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('123')).toBe('456');
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('456')).toBe('789');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe(undefined);
+ expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe('123');
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 461de5a3106..1c4449d1055 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -437,4 +437,51 @@ describe('Notes Store mutations', () => {
expect(state.commentsDisabled).toEqual(true);
});
});
+
+ describe('UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', () => {
+ it('updates resolvableDiscussionsCount', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [] },
+ { individual_note: true, resolvable: true, notes: [] },
+ { individual_note: false, resolvable: false, notes: [] },
+ ],
+ resolvableDiscussionsCount: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.resolvableDiscussionsCount).toBe(1);
+ });
+
+ it('updates unresolvedDiscussionsCount', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: true, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ ],
+ unresolvedDiscussionsCount: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.unresolvedDiscussionsCount).toBe(1);
+ });
+
+ it('updates hasUnresolvedDiscussions', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ ],
+ hasUnresolvedDiscussions: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.hasUnresolvedDiscussions).toBe(true);
+ });
+ });
});
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index df24cef0b8b..91b0499375d 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -104,5 +104,17 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include("src=\"test%20image.png\"")
end
+
+ it 'sanitizes the fixed link' do
+ markdown_xss = "[xss](javascript: alert%28document.domain%29)"
+ output = described_class.to_html(markdown_xss, project: project)
+
+ expect(output).not_to include("javascript")
+
+ markdown_xss = "<invalidtag>\n[xss](javascript:alert%28document.domain%29)"
+ output = described_class.to_html(markdown_xss, project: project)
+
+ expect(output).not_to include("javascript")
+ end
end
end
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 242ab4a91dd..3d979132880 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -19,17 +19,17 @@ describe Gitlab::Auth::RequestAuthenticator do
allow_any_instance_of(described_class).to receive(:find_sessionless_user).and_return(sessionless_user)
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
- expect(subject.user).to eq sessionless_user
+ expect(subject.user([:api])).to eq sessionless_user
end
it 'returns session user if no sessionless user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
- expect(subject.user).to eq session_user
+ expect(subject.user([:api])).to eq session_user
end
it 'returns nil if no user found' do
- expect(subject.user).to be_blank
+ expect(subject.user([:api])).to be_blank
end
it 'bubbles up exceptions' do
@@ -42,26 +42,26 @@ describe Gitlab::Auth::RequestAuthenticator do
let!(:feed_token_user) { build(:user) }
it 'returns access_token user first' do
- allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user)
+ allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user).to eq access_token_user
+ expect(subject.find_sessionless_user([:api])).to eq access_token_user
end
it 'returns feed_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user).to eq feed_token_user
+ expect(subject.find_sessionless_user([:api])).to eq feed_token_user
end
it 'returns nil if no user found' do
- expect(subject.find_sessionless_user).to be_blank
+ expect(subject.find_sessionless_user([:api])).to be_blank
end
it 'rescue Gitlab::Auth::AuthenticationError exceptions' do
- allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
+ allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
- expect(subject.find_sessionless_user).to be_blank
+ expect(subject.find_sessionless_user([:api])).to be_blank
end
end
end
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 454ad1589b9..4e4c8b215c2 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => ''
}
end
- let(:request) { Rack::Request.new(env)}
+ let(:request) { Rack::Request.new(env) }
def set_param(key, value)
request.update_param(key, value)
@@ -49,6 +49,7 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
+ env['SCRIPT_NAME'] = 'url.atom'
env['HTTP_ACCEPT'] = 'application/atom+xml'
end
@@ -56,17 +57,17 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid feed_token' do
set_param(:feed_token, user.feed_token)
- expect(find_user_from_feed_token).to eq user
+ expect(find_user_from_feed_token(:rss)).to eq user
end
it 'returns nil if feed_token is blank' do
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
it 'returns exception if invalid feed_token' do
set_param(:feed_token, 'invalid_token')
- expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
@@ -74,34 +75,38 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid rssd_token' do
set_param(:rss_token, user.feed_token)
- expect(find_user_from_feed_token).to eq user
+ expect(find_user_from_feed_token(:rss)).to eq user
end
it 'returns nil if rss_token is blank' do
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token')
- expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
end
context 'when the request format is not atom' do
it 'returns nil' do
+ env['SCRIPT_NAME'] = 'json'
+
set_param(:feed_token, user.feed_token)
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
end
context 'when the request format is empty' do
it 'the method call does not modify the original value' do
+ env['SCRIPT_NAME'] = 'url.atom'
+
env.delete('action_dispatch.request.formats')
- find_user_from_feed_token
+ find_user_from_feed_token(:rss)
expect(env['action_dispatch.request.formats']).to be_nil
end
@@ -111,8 +116,12 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ before do
+ env['SCRIPT_NAME'] = 'url.atom'
+ end
+
it 'returns nil if no access_token present' do
- expect(find_personal_access_token).to be_nil
+ expect(find_user_from_access_token).to be_nil
end
context 'when validate_access_token! returns valid' do
@@ -131,9 +140,59 @@ describe Gitlab::Auth::UserAuthFinders do
end
end
+ describe '#find_user_from_web_access_token' do
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ before do
+ env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
+ end
+
+ it 'returns exception if token has no user' do
+ allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
+
+ expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+
+ context 'no feed or API requests' do
+ it 'returns nil if the request is not RSS' do
+ expect(find_user_from_web_access_token(:rss)).to be_nil
+ end
+
+ it 'returns nil if the request is not ICS' do
+ expect(find_user_from_web_access_token(:ics)).to be_nil
+ end
+
+ it 'returns nil if the request is not API' do
+ expect(find_user_from_web_access_token(:api)).to be_nil
+ end
+ end
+
+ it 'returns the user for RSS requests' do
+ env['SCRIPT_NAME'] = 'url.atom'
+
+ expect(find_user_from_web_access_token(:rss)).to eq(user)
+ end
+
+ it 'returns the user for ICS requests' do
+ env['SCRIPT_NAME'] = 'url.ics'
+
+ expect(find_user_from_web_access_token(:ics)).to eq(user)
+ end
+
+ it 'returns the user for API requests' do
+ env['SCRIPT_NAME'] = '/api/endpoint'
+
+ expect(find_user_from_web_access_token(:api)).to eq(user)
+ end
+ end
+
describe '#find_personal_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ before do
+ env['SCRIPT_NAME'] = 'url.atom'
+ end
+
context 'passed as header' do
it 'returns token if valid personal_access_token' do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
@@ -220,5 +279,20 @@ describe Gitlab::Auth::UserAuthFinders do
expect { validate_access_token!(scopes: [:sudo]) }.to raise_error(Gitlab::Auth::InsufficientScopeError)
end
end
+
+ context 'with impersonation token' do
+ let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ context 'when impersonation is disabled' do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ allow_any_instance_of(described_class).to receive(:access_token).and_return(personal_access_token)
+ end
+
+ it 'returns Gitlab::Auth::ImpersonationDisabled' do
+ expect { validate_access_token! }.to raise_error(Gitlab::Auth::ImpersonationDisabled)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
new file mode 100644
index 00000000000..c66d7cd6148
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migration, schema: 20181010133639 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) }
+
+ describe described_class::Storage::HashedProject do
+ let(:project) { double(id: 555) }
+ subject(:project_storage) { described_class.new(project) }
+
+ it 'has the correct disk_path' do
+ expect(project_storage.disk_path).to eq('@hashed/91/a7/91a73fd806ab2c005c13b4dc19130a884e909dea3f72d46e30266fe1a1f588d8')
+ end
+ end
+
+ describe described_class::Storage::LegacyProject do
+ let(:project) { double(full_path: 'this/is/the/full/path') }
+ subject(:project_storage) { described_class.new(project) }
+
+ it 'has the correct disk_path' do
+ expect(project_storage.disk_path).to eq('this/is/the/full/path')
+ end
+ end
+
+ describe described_class::Project do
+ let(:project_record) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') }
+ subject(:project) { described_class.find(project_record.id) }
+
+ describe '#full_path' do
+ it 'returns path containing all parent namespaces' do
+ expect(project.full_path).to eq('foo/bar/baz')
+ end
+
+ it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
+ subgroup.update_attribute(:parent_id, namespaces.maximum(:id).succ)
+
+ expect { project.full_path }.to raise_error(Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig::OrphanedNamespaceError)
+ end
+ end
+ end
+
+ describe described_class::Up do
+ describe '#perform' do
+ subject(:migrate) { described_class.new.perform(projects.minimum(:id), projects.maximum(:id)) }
+
+ it 'asks the gitaly client to set config' do
+ projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz')
+ projects.create!(namespace_id: subgroup.id, name: 'buzz', path: 'buzz', storage_version: 1)
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => 'foo/bar/baz')
+ end
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => 'foo/bar/buzz')
+ end
+
+ migrate
+ end
+ end
+ end
+
+ describe described_class::Down do
+ describe '#perform' do
+ subject(:migrate) { described_class.new.perform(projects.minimum(:id), projects.maximum(:id)) }
+
+ it 'asks the gitaly client to set config' do
+ projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz')
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:delete_config).with(['gitlab.fullpath'])
+ end
+
+ migrate
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
index 2a869446753..1d9bac79dcd 100644
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -65,5 +65,30 @@ describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 201809
expect(hook).to have_attributes(values)
end
+
+ it 'reloads the model column information' do
+ expect(model).to receive(:reset_column_information).and_call_original
+ expect(model).to receive(:define_attribute_methods).and_call_original
+
+ subject.perform(model, [:token, :url], 1, 1)
+ end
+
+ it 'fails if a source column is not present' do
+ columns = model.columns.reject { |c| c.name == 'url' }
+ allow(model).to receive(:columns) { columns }
+
+ expect do
+ subject.perform(model, [:token, :url], 1, 1)
+ end.to raise_error(/source column: url is missing/)
+ end
+
+ it 'fails if a destination column is not present' do
+ columns = model.columns.reject { |c| c.name == 'encrypted_url' }
+ allow(model).to receive(:columns) { columns }
+
+ expect do
+ subject.perform(model, [:token, :url], 1, 1)
+ end.to raise_error(/destination column: encrypted_url is missing/)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 6259b952add..546a9e7d0cc 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -116,6 +116,19 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
chunked_io.each_line { |line| }
end
end
+
+ context 'when buffer consist of many empty lines' do
+ let(:sample_trace_raw) { Array.new(10, " ").join("\n") }
+
+ before do
+ build.trace.set(sample_trace_raw)
+ end
+
+ it 'yields lines' do
+ expect { |b| chunked_io.each_line(&b) }
+ .to yield_successive_args(*string_io.each_line.to_a)
+ end
+ end
end
context "#read" do
@@ -143,6 +156,22 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
end
end
+ context 'when chunk is missing data' do
+ let(:length) { nil }
+
+ before do
+ stub_buffer_size(1024)
+ build.trace.set(sample_trace_raw)
+
+ # make second chunk to not have data
+ build.trace_chunks.second.append('', 0)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error described_class::FailedToGetChunkError
+ end
+ end
+
context 'when read only first 100 bytes' do
let(:length) { 100 }
@@ -266,6 +295,40 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
expect(chunked_io.readline).to eq(string_io.readline)
end
end
+
+ context 'when chunk is missing data' do
+ let(:length) { nil }
+
+ before do
+ build.trace.set(sample_trace_raw)
+
+ # make first chunk to have invalid data
+ build.trace_chunks.first.append('data', 0)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error described_class::FailedToGetChunkError
+ end
+ end
+
+ context 'when utf-8 is being used' do
+ let(:sample_trace_raw) { sample_trace_raw_utf8.force_encoding(Encoding::BINARY) }
+ let(:sample_trace_raw_utf8) { "😺\n😺\n😺\n😺" }
+
+ before do
+ stub_buffer_size(3) # the utf-8 character has 4 bytes
+
+ build.trace.set(sample_trace_raw_utf8)
+ end
+
+ it 'has known length' do
+ expect(sample_trace_raw_utf8.bytesize).to eq(4 * 4 + 3 * 1)
+ expect(sample_trace_raw.bytesize).to eq(4 * 4 + 3 * 1)
+ expect(chunked_io.size).to eq(4 * 4 + 3 * 1)
+ end
+
+ it_behaves_like 'all line matching'
+ end
end
context "#write" do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 4f49958dd33..38626f728d7 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -257,7 +257,8 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
let!(:last_result) { stream.html_with_state }
before do
- stream.append("5678", 4)
+ data_stream.seek(4, IO::SEEK_SET)
+ data_stream.write("5678")
stream.seek(0)
end
@@ -271,25 +272,29 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
end
context 'when stream is StringIO' do
+ let(:data_stream) do
+ StringIO.new("1234")
+ end
+
let(:stream) do
- described_class.new do
- StringIO.new("1234")
- end
+ described_class.new { data_stream }
end
it_behaves_like 'html_with_states'
end
context 'when stream is ChunkedIO' do
- let(:stream) do
- described_class.new do
- Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
- chunked_io.write("1234")
- chunked_io.seek(0, IO::SEEK_SET)
- end
+ let(:data_stream) do
+ Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
+ chunked_io.write("1234")
+ chunked_io.seek(0, IO::SEEK_SET)
end
end
+ let(:stream) do
+ described_class.new { data_stream }
+ end
+
it_behaves_like 'html_with_states'
end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 7d76519dddd..fc295b2deff 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -443,11 +443,17 @@ describe Gitlab::Database do
end
end
+ describe '.read_only?' do
+ it 'returns false' do
+ expect(described_class.read_only?).to be_falsey
+ end
+ end
+
describe '.db_read_only?' do
context 'when using PostgreSQL' do
before do
allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
- expect(described_class).to receive(:postgresql?).and_return(true)
+ allow(described_class).to receive(:postgresql?).and_return(true)
end
it 'detects a read only database' do
@@ -456,11 +462,25 @@ describe Gitlab::Database do
expect(described_class.db_read_only?).to be_truthy
end
+ # TODO: remove rails5-only tag after removing rails4 tests
+ it 'detects a read only database', :rails5 do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
+
+ expect(described_class.db_read_only?).to be_truthy
+ end
+
it 'detects a read write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
+
+ # TODO: remove rails5-only tag after removing rails4 tests
+ it 'detects a read write database', :rails5 do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
+
+ expect(described_class.db_read_only?).to be_falsey
+ end
end
context 'when using MySQL' do
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 2e3656b52fb..5107e1efbbd 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -11,6 +11,14 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
let(:options) { {} }
+ context 'when unique key is not set' do
+ let(:unique_key) { }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error ArgumentError
+ end
+ end
+
context 'when the lease is not obtained yet' do
before do
stub_exclusive_lease(unique_key, 'uuid')
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 1fe73c12fc0..852ee9c96af 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1469,6 +1469,19 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
end
+
+ it 'writes the HEAD' do
+ repository.write_ref('HEAD', 'refs/heads/feature')
+
+ expect(repository.commit('HEAD')).to eq(repository.commit('feature'))
+ expect(repository.root_ref).to eq('feature')
+ end
+
+ it 'writes other refs' do
+ repository.write_ref('refs/heads/feature', SeedRepo::Commit::ID)
+
+ expect(repository.commit('feature').sha).to eq(SeedRepo::Commit::ID)
+ end
end
describe '#write_config' do
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index ba7fb168a3b..3ab04a1c46d 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -27,4 +27,5 @@ describe Gitlab::GitRefValidator do
it { expect(described_class.validate('-branch')).to be_falsey }
it { expect(described_class.validate('.tag')).to be_falsey }
it { expect(described_class.validate('my branch')).to be_falsey }
+ it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey }
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index d8f01dcb76b..77f5b2ffa37 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -218,7 +218,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
describe '#fail_import' do
it 'marks the import as failed' do
- expect(project).to receive(:mark_import_as_failed).with('foo')
+ expect(project.import_state).to receive(:mark_as_failed).with('foo')
expect(importer.fail_import('foo')).to eq(false)
end
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index 20b48c1de68..f5df38c9aaf 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::GithubImport::ParallelImporter do
it 'updates the import JID of the project' do
importer.execute
- expect(project.reload.import_jid).to eq("github-importer/#{project.id}")
+ expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}")
end
end
end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
new file mode 100644
index 00000000000..4609593ef6a
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchModelLoader do
+ describe '#find' do
+ let(:issue) { create(:issue) }
+ let(:user) { create(:user) }
+
+ it 'finds a model by id' do
+ issue_result = described_class.new(Issue, issue.id).find
+ user_result = described_class.new(User, user.id).find
+
+ expect(issue_result.__sync).to eq(issue)
+ expect(user_result.__sync).to eq(user)
+ end
+
+ it 'only queries once per model' do
+ other_user = create(:user)
+ user
+ issue
+
+ expect do
+ [described_class.new(User, other_user.id).find,
+ described_class.new(User, user.id).find,
+ described_class.new(Issue, issue.id).find].map(&:__sync)
+ end.not_to exceed_query_limit(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 8bce7a4cdf5..c7f92cbb143 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -40,6 +40,7 @@ describe Gitlab::Kubernetes::Helm::Api do
allow(client).to receive(:create_config_map).and_return(nil)
allow(client).to receive(:create_service_account).and_return(nil)
allow(client).to receive(:create_cluster_role_binding).and_return(nil)
+ allow(client).to receive(:delete_pod).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@@ -50,6 +51,13 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.install(command)
end
+ it 'removes an existing pod before installing' do
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.install(command)
+ end
+
context 'with a ConfigMap' do
let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
@@ -180,6 +188,7 @@ describe Gitlab::Kubernetes::Helm::Api do
allow(client).to receive(:update_config_map).and_return(nil)
allow(client).to receive(:create_pod).and_return(nil)
+ allow(client).to receive(:delete_pod).and_return(nil)
end
it 'ensures the namespace exists before creating the pod' do
@@ -189,6 +198,13 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.update(command)
end
+ it 'removes an existing pod before updating' do
+ expect(client).to receive(:delete_pod).with('upgrade-app-name', 'gitlab-managed-apps').once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.update(command)
+ end
+
it 'updates the config map on kubeclient when one exists' do
resource = Gitlab::Kubernetes::ConfigMap.new(
application_name, files
@@ -224,9 +240,18 @@ describe Gitlab::Kubernetes::Helm::Api do
describe '#delete_pod!' do
it 'deletes the POD from kubernetes cluster' do
- expect(client).to receive(:delete_pod).with(command.pod_name, gitlab_namespace).once
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once
- subject.delete_pod!(command.pod_name)
+ subject.delete_pod!('install-app-name')
+ end
+
+ context 'when the resource being deleted does not exist' do
+ it 'catches the error' do
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps')
+ .and_raise(Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil))
+
+ subject.delete_pod!('install-app-name')
+ end
end
end
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 20514486727..d2df21d7bb5 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -174,7 +174,7 @@ describe Gitlab::LegacyGithubImport::Importer do
described_class.new(project).execute
- expect(project.import_error).to eq error.to_json
+ expect(project.import_state.last_error).to eq error.to_json
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 8df0facdab3..39e0a17a307 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -10,8 +10,8 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?(import_url)).to be false
end
- it 'allows imports from configured SSH host and port' do
- import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
+ it 'allows mirroring from configured SSH host and port' do
+ import_url = "ssh://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
expect(described_class.blocked_url?(import_url)).to be false
end
@@ -29,24 +29,46 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
end
+ it 'returns true for bad protocol on configured web/SSH host and ports' do
+ web_url = "javascript://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git%0aalert(1)"
+ expect(described_class.blocked_url?(web_url)).to be true
+
+ ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
+ expect(described_class.blocked_url?(ssh_url)).to be true
+ end
+
it 'returns true for localhost IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:0:0:0]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true
- expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
- expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::]/foo/foo.git')).to be true
end
it 'returns true for loopback IP' do
expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
end
it 'returns true for alternative version of 127.0.0.1 (0177.1)' do
expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (017700000001)' do
+ expect(described_class.blocked_url?('https://017700000001:65535/foo/foo.git')).to be true
+ end
+
it 'returns true for alternative version of 127.0.0.1 (0x7f.1)' do
expect(described_class.blocked_url?('https://0x7f.1:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (0x7f.0.0.1)' do
+ expect(described_class.blocked_url?('https://0x7f.0.0.1:65535/foo/foo.git')).to be true
+ end
+
+ it 'returns true for alternative version of 127.0.0.1 (0x7f000001)' do
+ expect(described_class.blocked_url?('https://0x7f000001:65535/foo/foo.git')).to be true
+ end
+
it 'returns true for alternative version of 127.0.0.1 (2130706433)' do
expect(described_class.blocked_url?('https://2130706433:65535/foo/foo.git')).to be true
end
@@ -55,6 +77,27 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://127.000.000.001:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (127.0.1)' do
+ expect(described_class.blocked_url?('https://127.0.1:65535/foo/foo.git')).to be true
+ end
+
+ context 'with ipv6 mapped address' do
+ it 'returns true for localhost IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:0.0.0.0]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:0.0.0.0]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:0:0]/foo/foo.git')).to be true
+ end
+
+ it 'returns true for loopback IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:127.0.0.1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:7f00:1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.2]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:127.0.0.2]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:7f00:2]/foo/foo.git')).to be true
+ end
+ end
+
it 'returns true for a non-alphanumeric hostname' do
stub_resolv
@@ -78,7 +121,22 @@ describe Gitlab::UrlBlocker do
end
context 'when allow_local_network is' do
- let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
+ let(:local_ips) do
+ [
+ '192.168.1.2',
+ '[0:0:0:0:0:ffff:192.168.1.2]',
+ '[::ffff:c0a8:102]',
+ '10.0.0.2',
+ '[0:0:0:0:0:ffff:10.0.0.2]',
+ '[::ffff:a00:2]',
+ '172.16.0.2',
+ '[0:0:0:0:0:ffff:172.16.0.2]',
+ '[::ffff:ac10:20]',
+ '[feef::1]',
+ '[fee2::]',
+ '[fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa]'
+ ]
+ end
let(:fake_domain) { 'www.fakedomain.fake' }
context 'true (default)' do
@@ -109,10 +167,14 @@ describe Gitlab::UrlBlocker do
expect(described_class).not_to be_blocked_url('http://169.254.168.100')
end
- # This is blocked due to the hostname check: https://gitlab.com/gitlab-org/gitlab-ce/issues/50227
- it 'blocks IPv6 link-local endpoints' do
- expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]')
- expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]')
+ it 'allows IPv6 link-local endpoints' do
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]')
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]')
+ expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]')
end
end
@@ -135,14 +197,20 @@ describe Gitlab::UrlBlocker do
end
it 'blocks IPv6 link-local endpoints' do
+ expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a9fe]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]', allow_local_network: false)
- expect(described_class).to be_blocked_url('http://[FE80::C800:EFF:FE74:8]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a864]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[fe80::c800:eff:fe74:8]', allow_local_network: false)
end
end
def stub_domain_resolv(domain, ip)
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)])
+ address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
+ allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end
def unstub_domain_resolv
@@ -183,6 +251,36 @@ describe Gitlab::UrlBlocker do
end
end
+ describe '#validate_hostname!' do
+ let(:ip_addresses) do
+ [
+ '2001:db8:1f70::999:de8:7648:6e8',
+ 'FE80::C800:EFF:FE74:8',
+ '::ffff:127.0.0.1',
+ '::ffff:169.254.168.100',
+ '::ffff:7f00:1',
+ '0:0:0:0:0:ffff:0.0.0.0',
+ 'localhost',
+ '127.0.0.1',
+ '127.000.000.001',
+ '0x7f000001',
+ '0x7f.0.0.1',
+ '0x7f.0.0.1',
+ '017700000001',
+ '0177.1',
+ '2130706433',
+ '::',
+ '::1'
+ ]
+ end
+
+ it 'does not raise error for valid Ip addresses' do
+ ip_addresses.each do |ip|
+ expect { described_class.send(:validate_hostname!, ip) }.not_to raise_error
+ end
+ end
+ end
+
# Resolv does not support resolving UTF-8 domain names
# See https://bugs.ruby-lang.org/issues/4270
def stub_resolv
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index ff1a5aa2536..150c00e4bfe 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -522,7 +522,7 @@ describe Notify do
let(:project_snippet) { create(:project_snippet, project: project) }
let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) }
- subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
+ subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { project_snippet }
diff --git a/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
new file mode 100644
index 00000000000..34f4a36d63d
--- /dev/null
+++ b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20181010133639_backfill_store_project_full_path_in_repo.rb')
+
+describe BackfillStoreProjectFullPathInRepo, :migration do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) }
+
+ subject(:migration) { described_class.new }
+
+ around do |example|
+ Sidekiq::Testing.inline! do
+ example.run
+ end
+ end
+
+ describe '#up' do
+ shared_examples_for 'writes the full path to git config' do
+ it 'writes the git config' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => expected_path)
+ end
+
+ migration.up
+ end
+
+ it 'retries in case of failure' do
+ repository_service = spy(:repository_service)
+
+ allow(Gitlab::GitalyClient::RepositoryService).to receive(:new).and_return(repository_service)
+
+ allow(repository_service).to receive(:set_config).and_raise(GRPC::BadStatus, 'Retry me')
+ expect(repository_service).to receive(:set_config).exactly(3).times
+
+ migration.up
+ end
+
+ it 'cleans up repository before writing the config' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ expect(repository_service).to receive(:cleanup).ordered
+ expect(repository_service).to receive(:set_config).ordered
+ end
+
+ migration.up
+ end
+
+ context 'legacy storage' do
+ it 'finds the repository at the correct location' do
+ Project.find(project.id).create_repository
+
+ expect { migration.up }.not_to raise_error
+ end
+ end
+
+ context 'hashed storage' do
+ it 'finds the repository at the correct location' do
+ project.update_attribute(:storage_version, 1)
+
+ Project.find(project.id).create_repository
+
+ expect { migration.up }.not_to raise_error
+ end
+ end
+ end
+
+ context 'project in group' do
+ let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') }
+ let(:expected_path) { 'foo/baz' }
+
+ it_behaves_like 'writes the full path to git config'
+ end
+
+ context 'project in subgroup' do
+ let!(:project) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') }
+ let(:expected_path) { 'foo/bar/baz' }
+
+ it_behaves_like 'writes the full path to git config'
+ end
+ end
+
+ describe '#down' do
+ context 'project in group' do
+ let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') }
+
+ it 'deletes the gitlab full config value' do
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService)
+ .to receive(:delete_config).with(['gitlab.fullpath'])
+
+ migration.down
+ end
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_environments_external_url_spec.rb b/spec/migrations/cleanup_environments_external_url_spec.rb
new file mode 100644
index 00000000000..07ddaf3d38f
--- /dev/null
+++ b/spec/migrations/cleanup_environments_external_url_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20181108091549_cleanup_environments_external_url.rb')
+
+describe CleanupEnvironmentsExternalUrl, :migration do
+ let(:environments) { table(:environments) }
+ let(:invalid_entries) { environments.where(environments.arel_table[:external_url].matches('javascript://%')) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+
+ before do
+ namespace = namespaces.create(name: 'foo', path: 'foo')
+ project = projects.create!(namespace_id: namespace.id)
+
+ environments.create!(id: 1, project_id: project.id, name: 'poisoned', slug: 'poisoned', external_url: 'javascript://alert("1")')
+ end
+
+ it 'clears every environment with a javascript external_url' do
+ expect do
+ subject.up
+ end.to change { invalid_entries.count }.from(1).to(0)
+ end
+
+ it 'do not removes environments' do
+ expect do
+ subject.up
+ end.not_to change { environments.count }
+ end
+end
diff --git a/spec/migrations/migrate_forbidden_redirect_uris_spec.rb b/spec/migrations/migrate_forbidden_redirect_uris_spec.rb
new file mode 100644
index 00000000000..0bc13a3974a
--- /dev/null
+++ b/spec/migrations/migrate_forbidden_redirect_uris_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20181026091631_migrate_forbidden_redirect_uris.rb')
+
+describe MigrateForbiddenRedirectUris, :migration do
+ let(:oauth_application) { table(:oauth_applications) }
+ let(:oauth_access_grant) { table(:oauth_access_grants) }
+
+ let!(:control_app) { oauth_application.create(random_params) }
+ let!(:control_access_grant) { oauth_application.create(random_params) }
+ let!(:forbidden_js_app) { oauth_application.create(random_params.merge(redirect_uri: 'javascript://alert()')) }
+ let!(:forbidden_vb_app) { oauth_application.create(random_params.merge(redirect_uri: 'VBSCRIPT://alert()')) }
+ let!(:forbidden_access_grant) { oauth_application.create(random_params.merge(redirect_uri: 'vbscript://alert()')) }
+
+ context 'oauth application' do
+ it 'migrates forbidden javascript URI' do
+ expect { migrate! }.to change { forbidden_js_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'migrates forbidden VBScript URI' do
+ expect { migrate! }.to change { forbidden_vb_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'does not migrate a valid URI' do
+ expect { migrate! }.not_to change { control_app.reload.redirect_uri }
+ end
+ end
+
+ context 'access grant' do
+ it 'migrates forbidden VBScript URI' do
+ expect { migrate! }.to change { forbidden_access_grant.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'does not migrate a valid URI' do
+ expect { migrate! }.not_to change { control_access_grant.reload.redirect_uri }
+ end
+ end
+
+ def random_params
+ {
+ name: 'test',
+ secret: 'test',
+ uid: Doorkeeper::OAuth::Helpers::UniqueToken.generate,
+ redirect_uri: 'http://valid.com'
+ }
+ end
+end
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
index 9220b49a736..0016f058a17 100644
--- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -18,33 +18,33 @@ describe MigrateIssuesToGhostUser, :migration do
let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') }
it 'does not create a new user' do
- expect { schema_migrate_up! }.not_to change { User.count }
+ expect { migrate! }.not_to change { User.count }
end
it 'migrates issues where author = nil to the ghost user' do
- schema_migrate_up!
+ migrate!
expect(issues.first.reload.author_id).to eq(ghost.id)
end
it 'does not change issues authored by an existing user' do
- expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ expect { migrate! }.not_to change { issues.second.reload.author_id}
end
end
context 'when ghost user does not exist' do
it 'creates a new user' do
- expect { schema_migrate_up! }.to change { User.count }.by(1)
+ expect { migrate! }.to change { User.count }.by(1)
end
it 'migrates issues where author = nil to the ghost user' do
- schema_migrate_up!
+ migrate!
expect(issues.first.reload.author_id).to eq(User.ghost.id)
end
it 'does not change issues authored by an existing user' do
- expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ expect { migrate! }.not_to change { issues.second.reload.author_id}
end
end
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 59c861a74db..859287bb0c8 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -436,32 +436,47 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :redis }
context 'when data exists' do
- let(:data) { 'Sample data in redis' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'persists the data' do
- expect(build_trace_chunk.redis?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'persists the data' do
+ expect(build_trace_chunk.redis?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+
+ subject
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in redis' }
+
+ it 'does not persist the data and the orignal data is intact' do
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
+
+ expect(build_trace_chunk.redis?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ end
+ end
end
context 'when data does not exist' do
it 'does not persist' do
- expect { subject }.to raise_error('Can not persist empty data')
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
end
end
end
@@ -470,32 +485,47 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :database }
context 'when data exists' do
- let(:data) { 'Sample data in database' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'persists the data' do
- expect(build_trace_chunk.database?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
- expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'persists the data' do
+ expect(build_trace_chunk.database?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ subject
+
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in database' }
+
+ it 'does not persist the data and the orignal data is intact' do
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
+
+ expect(build_trace_chunk.database?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ end
+ end
end
context 'when data does not exist' do
it 'does not persist' do
- expect { subject }.to raise_error('Can not persist empty data')
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
end
end
end
@@ -504,27 +534,37 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :fog }
context 'when data exists' do
- let(:data) { 'Sample data in fog' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'does not change data store' do
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'does not change data store' do
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ subject
+
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in fog' }
+
+ it 'does not raise error' do
+ expect { subject }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 6b0b23eeab3..cfe0e216c78 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Ingress do
include_examples 'cluster application core specs', :clusters_applications_ingress
include_examples 'cluster application status specs', :clusters_applications_ingress
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_ingress
before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index faaabafddb7..a40edbf267b 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_jupyter
it { is_expected.to belong_to(:oauth_application) }
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index be2a91d566b..d43d88c2924 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -7,6 +7,11 @@ describe Clusters::Applications::Knative do
include_examples 'cluster application status specs', :clusters_applications_knative
include_examples 'cluster application helm specs', :clusters_applications_knative
+ before do
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+ end
+
describe '.installed' do
subject { described_class.installed }
@@ -45,6 +50,48 @@ describe Clusters::Applications::Knative do
it { is_expected.to contain_exactly(cluster) }
end
+ describe 'make_installed with external_ip' do
+ before do
+ application.make_installed!
+ end
+
+ let(:application) { create(:clusters_applications_knative, :installing) }
+
+ it 'schedules a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).to have_received(:perform_in)
+ .with(Clusters::Applications::Knative::FETCH_IP_ADDRESS_DELAY, 'knative', application.id)
+ end
+ end
+
+ describe '#schedule_status_update with external_ip' do
+ let(:application) { create(:clusters_applications_knative, :installed) }
+
+ before do
+ application.schedule_status_update
+ end
+
+ it 'schedules a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).to have_received(:perform_async)
+ .with('knative', application.id)
+ end
+
+ context 'when the application is not installed' do
+ let(:application) { create(:clusters_applications_knative, :installing) }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'when there is already an external_ip' do
+ let(:application) { create(:clusters_applications_knative, :installed, external_ip: '111.222.222.111') }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
+ end
+ end
+ end
+
describe '#install_command' do
subject { knative.install_command }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index b5aa1dcece5..893ed3e3f64 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :clusters_applications_prometheus
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_prometheus
describe '.installed' do
subject { described_class.installed }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index a8b28a335d6..97e50809647 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Runner do
include_examples 'cluster application core specs', :clusters_applications_runner
include_examples 'cluster application status specs', :clusters_applications_runner
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_runner
it { is_expected.to belong_to(:runner) }
@@ -90,7 +90,7 @@ describe Clusters::Applications::Runner do
context 'without a runner' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
- let(:application) { create(:clusters_applications_runner, cluster: cluster) }
+ let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) }
it 'creates a runner' do
expect do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index f9be61e4768..bcdfe3cf1eb 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -517,7 +517,7 @@ describe Note do
describe '#to_ability_name' do
it 'returns snippet for a project snippet note' do
- expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet')
+ expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet')
end
it 'returns personal_snippet for a personal snippet note' do
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
new file mode 100644
index 00000000000..6c904710fb5
--- /dev/null
+++ b/spec/models/pool_repository_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe PoolRepository do
+ describe 'associations' do
+ it { is_expected.to belong_to(:shard) }
+ it { is_expected.to have_many(:member_projects) }
+ end
+
+ describe 'validations' do
+ let!(:pool_repository) { create(:pool_repository) }
+
+ it { is_expected.to validate_presence_of(:shard) }
+ end
+
+ describe '#disk_path' do
+ it 'sets the hashed disk_path' do
+ pool = create(:pool_repository)
+
+ elements = File.split(pool.disk_path)
+
+ expect(elements).to all( match(/\d{2,}/) )
+ end
+ end
+end
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index f7033b28c76..e3b2d971419 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -10,4 +10,116 @@ describe ProjectImportState, type: :model do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
end
+
+ describe 'Project import job' do
+ let(:import_state) { create(:import_state, import_url: generate(:url)) }
+ let(:project) { import_state.project }
+
+ before do
+ allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
+ .with(project.import_url).and_return(true)
+
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ allow(Project).to receive(:find).with(project.id).and_return(project)
+ expect(project.repository).to receive(:after_import).and_call_original
+ expect(project.wiki.repository).to receive(:after_import).and_call_original
+ end
+
+ it 'imports a project' do
+ expect(RepositoryImportWorker).to receive(:perform_async).and_call_original
+
+ expect { import_state.schedule }.to change { import_state.jid }
+ expect(import_state.status).to eq('finished')
+ end
+ end
+
+ describe '#human_status_name' do
+ context 'when import_state exists' do
+ it 'returns the humanized status name' do
+ import_state = build(:import_state, :started)
+
+ expect(import_state.human_status_name).to eq("started")
+ end
+ end
+ end
+
+ describe 'import state transitions' do
+ context 'state transition: [:started] => [:finished]' do
+ let(:after_import_service) { spy(:after_import_service) }
+ let(:housekeeping_service) { spy(:housekeeping_service) }
+
+ before do
+ allow(Projects::AfterImportService)
+ .to receive(:new) { after_import_service }
+
+ allow(after_import_service)
+ .to receive(:execute) { housekeeping_service.execute }
+
+ allow(Projects::HousekeepingService)
+ .to receive(:new) { housekeeping_service }
+ end
+
+ it 'resets last_error' do
+ error_message = 'Some error'
+ import_state = create(:import_state, :started, last_error: error_message)
+
+ expect { import_state.finish }.to change { import_state.last_error }.from(error_message).to(nil)
+ end
+
+ it 'performs housekeeping when an import of a fresh project is completed' do
+ project = create(:project_empty_repo, :import_started, import_type: :github)
+
+ project.import_state.finish
+
+ expect(after_import_service).to have_received(:execute)
+ expect(housekeeping_service).to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project repository does not exist' do
+ project = create(:project, :import_started, import_type: :github)
+
+ project.import_state.finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project does not have a valid import type' do
+ project = create(:project, :import_started, import_type: nil)
+
+ project.import_state.finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+ end
+ end
+
+ describe '#remove_jid', :clean_gitlab_redis_cache do
+ let(:project) { }
+
+ context 'without an JID' do
+ it 'does nothing' do
+ import_state = create(:import_state)
+
+ expect(Gitlab::SidekiqStatus)
+ .not_to receive(:unset)
+
+ import_state.remove_jid
+ end
+ end
+
+ context 'with an JID' do
+ it 'unsets the JID' do
+ import_state = create(:import_state, jid: '123')
+
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:unset)
+ .with('123')
+ .and_call_original
+
+ import_state.remove_jid
+
+ expect(import_state.jid).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index f2cb927df37..b6cf4c72450 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -13,6 +13,23 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to :project }
end
+ context 'redirects' do
+ it 'does not follow redirects' do
+ redirect_to = 'https://redirected.example.com'
+ redirect_req_stub = stub_prometheus_request(prometheus_query_url('1'), status: 302, headers: { location: redirect_to })
+ redirected_req_stub = stub_prometheus_request(redirect_to, body: { 'status': 'success' })
+
+ result = service.test
+
+ # result = { success: false, result: error }
+ expect(result[:success]).to be_falsy
+ expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::Error)
+
+ expect(redirect_req_stub).to have_been_requested
+ expect(redirected_req_stub).not_to have_been_requested
+ end
+ end
+
describe 'Validations' do
context 'when manual_configuration is enabled' do
before do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 51278836604..e98c69e636a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -109,22 +109,6 @@ describe Project do
end
end
- context 'Site Statistics' do
- context 'when creating a new project' do
- it 'tracks project in SiteStatistic' do
- expect { create(:project) }.to change { SiteStatistic.fetch.repositories_count }.by(1)
- end
- end
-
- context 'when deleting a project' do
- it 'untracks project in SiteStatistic' do
- project = create(:project)
-
- expect { project.destroy }.to change { SiteStatistic.fetch.repositories_count }.by(-1)
- end
- end
- end
-
context 'updating cd_cd_settings' do
it 'does not raise an error' do
project = create(:project)
@@ -234,76 +218,93 @@ describe Project do
end
end
- it 'does not allow an invalid URI as import_url' do
- project = build(:project, import_url: 'invalid://')
+ describe 'import_url' do
+ it 'does not allow an invalid URI as import_url' do
+ project = build(:project, import_url: 'invalid://')
- expect(project).not_to be_valid
- end
+ expect(project).not_to be_valid
+ end
- it 'does allow a SSH URI as import_url for persisted projects' do
- project = create(:project)
- project.import_url = 'ssh://test@gitlab.com/project.git'
+ it 'does allow a SSH URI as import_url for persisted projects' do
+ project = create(:project)
+ project.import_url = 'ssh://test@gitlab.com/project.git'
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'does not allow a SSH URI as import_url for new projects' do
- project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+ it 'does not allow a SSH URI as import_url for new projects' do
+ project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
- expect(project).not_to be_valid
- end
+ expect(project).not_to be_valid
+ end
- it 'does allow a valid URI as import_url' do
- project = build(:project, import_url: 'http://gitlab.com/project.git')
+ it 'does allow a valid URI as import_url' do
+ project = build(:project, import_url: 'http://gitlab.com/project.git')
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'allows an empty URI' do
- project = build(:project, import_url: '')
+ it 'allows an empty URI' do
+ project = build(:project, import_url: '')
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'does not produce import data on an empty URI' do
- project = build(:project, import_url: '')
+ it 'does not produce import data on an empty URI' do
+ project = build(:project, import_url: '')
- expect(project.import_data).to be_nil
- end
+ expect(project.import_data).to be_nil
+ end
- it 'does not produce import data on an invalid URI' do
- project = build(:project, import_url: 'test://')
+ it 'does not produce import data on an invalid URI' do
+ project = build(:project, import_url: 'test://')
- expect(project.import_data).to be_nil
- end
+ expect(project.import_data).to be_nil
+ end
- it "does not allow import_url pointing to localhost" do
- project = build(:project, import_url: 'http://localhost:9000/t.git')
+ it "does not allow import_url pointing to localhost" do
+ project = build(:project, import_url: 'http://localhost:9000/t.git')
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
+ end
- it "does not allow import_url with invalid ports for new projects" do
- project = build(:project, import_url: 'http://github.com:25/t.git')
+ it "does not allow import_url with invalid ports for new projects" do
+ project = build(:project, import_url: 'http://github.com:25/t.git')
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
+ end
- it "does not allow import_url with invalid ports for persisted projects" do
- project = create(:project)
- project.import_url = 'http://github.com:25/t.git'
+ it "does not allow import_url with invalid ports for persisted projects" do
+ project = create(:project)
+ project.import_url = 'http://github.com:25/t.git'
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
+ end
- it "does not allow import_url with invalid user" do
- project = build(:project, import_url: 'http://$user:password@github.com/t.git')
+ it "does not allow import_url with invalid user" do
+ project = build(:project, import_url: 'http://$user:password@github.com/t.git')
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ end
+
+ include_context 'invalid urls'
+
+ it 'does not allow urls with CR or LF characters' do
+ project = build(:project)
+
+ aggregate_failures do
+ urls_with_CRLF.each do |url|
+ project.import_url = url
+
+ expect(project).not_to be_valid
+ expect(project.errors.full_messages.first).to match(/is blocked: URI is invalid/)
+ end
+ end
+ end
end
describe 'project pending deletion' do
@@ -1702,6 +1703,16 @@ describe Project do
end
end
+ describe 'handling import URL' do
+ it 'returns the sanitized URL' do
+ project = create(:project, :import_started, import_url: 'http://user:pass@test.com')
+
+ project.import_state.finish
+
+ expect(project.reload.import_url).to eq('http://test.com')
+ end
+ end
+
describe '#container_registry_url' do
let(:project) { create(:project) }
@@ -1815,106 +1826,6 @@ describe Project do
end
end
- describe '#human_import_status_name' do
- context 'when import_state exists' do
- it 'returns the humanized status name' do
- project = create(:project)
- create(:import_state, :started, project: project)
-
- expect(project.human_import_status_name).to eq("started")
- end
- end
-
- context 'when import_state was not created yet' do
- let(:project) { create(:project, :import_started) }
-
- it 'ensures import_state is created and returns humanized status name' do
- expect do
- project.human_import_status_name
- end.to change { ProjectImportState.count }.from(0).to(1)
- end
-
- it 'returns humanized status name' do
- expect(project.human_import_status_name).to eq("started")
- end
- end
- end
-
- describe 'Project import job' do
- let(:project) { create(:project, import_url: generate(:url)) }
-
- before do
- allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
- .with(project.repository_storage, project.disk_path, project.import_url)
- .and_return(true)
-
- # Works around https://github.com/rspec/rspec-mocks/issues/910
- allow(described_class).to receive(:find).with(project.id).and_return(project)
- expect(project.repository).to receive(:after_import)
- .and_call_original
- expect(project.wiki.repository).to receive(:after_import)
- .and_call_original
- end
-
- it 'imports a project' do
- expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
-
- expect { project.import_schedule }.to change { project.import_jid }
- expect(project.reload.import_status).to eq('finished')
- end
- end
-
- describe 'project import state transitions' do
- context 'state transition: [:started] => [:finished]' do
- let(:after_import_service) { spy(:after_import_service) }
- let(:housekeeping_service) { spy(:housekeeping_service) }
-
- before do
- allow(Projects::AfterImportService)
- .to receive(:new) { after_import_service }
-
- allow(after_import_service)
- .to receive(:execute) { housekeeping_service.execute }
-
- allow(Projects::HousekeepingService)
- .to receive(:new) { housekeeping_service }
- end
-
- it 'resets project import_error' do
- error_message = 'Some error'
- mirror = create(:project_empty_repo, :import_started)
- mirror.import_state.update(last_error: error_message)
-
- expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
- end
-
- it 'performs housekeeping when an import of a fresh project is completed' do
- project = create(:project_empty_repo, :import_started, import_type: :github)
-
- project.import_finish
-
- expect(after_import_service).to have_received(:execute)
- expect(housekeeping_service).to have_received(:execute)
- end
-
- it 'does not perform housekeeping when project repository does not exist' do
- project = create(:project, :import_started, import_type: :github)
-
- project.import_finish
-
- expect(housekeeping_service).not_to have_received(:execute)
- end
-
- it 'does not perform housekeeping when project does not have a valid import type' do
- project = create(:project, :import_started, import_type: nil)
-
- project.import_finish
-
- expect(housekeeping_service).not_to have_received(:execute)
- end
- end
- end
-
describe '#latest_successful_builds_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
@@ -1994,6 +1905,42 @@ describe Project do
end
end
+ describe '#import_status' do
+ context 'with import_state' do
+ it 'returns the right status' do
+ project = create(:project, :import_started)
+
+ expect(project.import_status).to eq("started")
+ end
+ end
+
+ context 'without import_state' do
+ it 'returns none' do
+ project = create(:project)
+
+ expect(project.import_status).to eq('none')
+ end
+ end
+ end
+
+ describe '#human_import_status_name' do
+ context 'with import_state' do
+ it 'returns the right human import status' do
+ project = create(:project, :import_started)
+
+ expect(project.human_import_status_name).to eq('started')
+ end
+ end
+
+ context 'without import_state' do
+ it 'returns none' do
+ project = create(:project)
+
+ expect(project.human_import_status_name).to eq('none')
+ end
+ end
+ end
+
describe '#add_import_job' do
let(:import_jid) { '123' }
@@ -2203,12 +2150,6 @@ describe Project do
project.change_head(project.default_branch)
end
- it 'creates the new reference with rugged' do
- expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false)
-
- project.change_head(project.default_branch)
- end
-
it 'copies the gitattributes' do
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
project.change_head(project.default_branch)
@@ -3436,13 +3377,14 @@ describe Project do
describe '#after_import' do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
it 'runs the correct hooks' do
expect(project.repository).to receive(:after_import)
expect(project.wiki.repository).to receive(:after_import)
- expect(project).to receive(:import_finish)
+ expect(import_state).to receive(:finish)
expect(project).to receive(:update_project_counter_caches)
- expect(project).to receive(:remove_import_jid)
+ expect(import_state).to receive(:remove_jid)
expect(project).to receive(:after_create_default_branch)
expect(project).to receive(:refresh_markdown_cache!)
@@ -3452,6 +3394,10 @@ describe Project do
context 'branch protection' do
let(:project) { create(:project, :repository) }
+ before do
+ create(:import_state, :started, project: project)
+ end
+
it 'does not protect when branch protection is disabled' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
@@ -3507,37 +3453,6 @@ describe Project do
end
end
- describe '#remove_import_jid', :clean_gitlab_redis_cache do
- let(:project) { }
-
- context 'without an import JID' do
- it 'does nothing' do
- project = create(:project)
-
- expect(Gitlab::SidekiqStatus)
- .not_to receive(:unset)
-
- project.remove_import_jid
- end
- end
-
- context 'with an import JID' do
- it 'unsets the import JID' do
- project = create(:project)
- create(:import_state, project: project, jid: '123')
-
- expect(Gitlab::SidekiqStatus)
- .to receive(:unset)
- .with('123')
- .and_call_original
-
- project.remove_import_jid
-
- expect(project.import_jid).to be_nil
- end
- end
- end
-
describe '#wiki_repository_exists?' do
it 'returns true when the wiki repository exists' do
project = create(:project, :wiki_repo)
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index da61a5f2771..b12ca79847c 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -174,7 +174,15 @@ describe RemoteMirror do
end
context 'with remote mirroring enabled' do
+ it 'defaults to disabling only protected branches' do
+ expect(remote_mirror.only_protected_branches?).to be_falsey
+ end
+
context 'with only protected branches enabled' do
+ before do
+ remote_mirror.only_protected_branches = true
+ end
+
context 'when it did not update in the last minute' do
it 'schedules a RepositoryUpdateRemoteMirrorWorker to run now' do
expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_async).with(remote_mirror.id, Time.now)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 187283b284b..f09b4b67061 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1488,6 +1488,7 @@ describe Repository do
:size,
:commit_count,
:rendered_readme,
+ :readme_path,
:contribution_guide,
:changelog,
:license_blob,
@@ -1874,6 +1875,42 @@ describe Repository do
end
end
+ describe '#readme_path', :use_clean_rails_memory_store_caching do
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) }
+
+ it 'returns nil' do
+ expect(repository.readme_path).to be_nil
+ end
+ end
+
+ context 'with an existing repository' do
+ context 'when no README exists' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'returns nil' do
+ expect(repository.readme_path).to be_nil
+ end
+ end
+
+ context 'when a README exists' do
+ let(:project) { create(:project, :repository) }
+
+ it 'returns the README' do
+ expect(repository.readme_path).to eq("README.md")
+ end
+
+ it 'caches the response' do
+ expect(repository).to receive(:readme).and_call_original.once
+
+ 2.times do
+ expect(repository.readme_path).to eq("README.md")
+ end
+ end
+ end
+ end
+ end
+
describe '#expire_statistics_caches' do
it 'expires the caches' do
expect(repository).to receive(:expire_method_caches)
@@ -2042,9 +2079,10 @@ describe Repository do
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
- .with(%i(rendered_readme license_blob license_key license))
+ .with(%i(rendered_readme readme_path license_blob license_key license))
expect(repository).to receive(:rendered_readme)
+ expect(repository).to receive(:readme_path)
expect(repository).to receive(:license_blob)
expect(repository).to receive(:license_key)
expect(repository).to receive(:license)
diff --git a/spec/models/site_statistic_spec.rb b/spec/models/site_statistic_spec.rb
deleted file mode 100644
index 0e739900065..00000000000
--- a/spec/models/site_statistic_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'spec_helper'
-
-describe SiteStatistic do
- describe '.fetch' do
- context 'existing record' do
- it 'returns existing SiteStatistic model' do
- statistics = create(:site_statistics)
-
- expect(described_class.fetch).to be_a(described_class)
- expect(described_class.fetch).to eq(statistics)
- end
- end
-
- context 'non existing record' do
- it 'creates a new SiteStatistic model' do
- expect(described_class.first).to be_nil
- expect(described_class.fetch).to be_a(described_class)
- end
- end
- end
-
- describe '.track' do
- context 'with allowed attributes' do
- let(:statistics) { create(:site_statistics) }
-
- it 'increases the attribute counter' do
- expect { described_class.track('repositories_count') }.to change { statistics.reload.repositories_count }.by(1)
- end
-
- it 'doesnt increase the attribute counter when an exception happens during transaction' do
- expect do
- begin
- described_class.transaction do
- described_class.track('repositories_count')
-
- raise StandardError
- end
- rescue StandardError
- # no-op
- end
- end.not_to change { statistics.reload.repositories_count }
- end
- end
-
- context 'with not allowed attributes' do
- it 'returns error' do
- expect { described_class.track('something_else') }.to raise_error(ArgumentError).with_message(/Invalid attribute: \'something_else\' to \'track\' method/)
- end
- end
- end
-
- describe '.untrack' do
- context 'with allowed attributes' do
- let(:statistics) { create(:site_statistics) }
-
- it 'decreases the attribute counter' do
- expect { described_class.untrack('repositories_count') }.to change { statistics.reload.repositories_count }.by(-1)
- end
-
- it 'doesnt decrease the attribute counter when an exception happens during transaction' do
- expect do
- begin
- described_class.transaction do
- described_class.track('repositories_count')
-
- raise StandardError
- end
- rescue StandardError
- # no-op
- end
- end.not_to change { described_class.fetch.repositories_count }
- end
- end
-
- context 'with not allowed attributes' do
- it 'returns error' do
- expect { described_class.untrack('something_else') }.to raise_error(ArgumentError).with_message(/Invalid attribute: \'something_else\' to \'untrack\' method/)
- end
- end
- end
-end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 733c1c49f08..7bd6dccd0ad 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1137,12 +1137,38 @@ describe User do
expect(described_class.find_by_any_email(user.email.upcase, confirmed: true)).to eq user
end
- it 'finds by secondary email' do
- email = create(:email, email: 'foo@example.com')
- user = email.user
+ context 'finds by secondary email' do
+ let(:user) { email.user }
- expect(described_class.find_by_any_email(email.email)).to eq user
- expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user
+ context 'primary email confirmed' do
+ context 'secondary email confirmed' do
+ let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user
+ end
+ end
+
+ context 'secondary email not confirmed' do
+ let!(:email) { create(:email, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
+ end
+ end
+ end
+
+ context 'primary email not confirmed' do
+ let(:user) { create(:user, confirmed_at: nil) }
+ let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
+ end
+ end
end
it 'returns nil when nothing found' do
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index e8096358f7d..7e25c53e77c 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do
return @policies if @policies
noteable ||= issue
- note = create(:note, noteable: noteable, author: user, project: project)
+ note = if noteable.is_a?(Commit)
+ create(:note_on_commit, commit_id: noteable.id, author: user, project: project)
+ else
+ create(:note, noteable: noteable, author: user, project: project)
+ end
@policies = described_class.new(user, note)
end
+ shared_examples_for 'a discussion with a private noteable' do
+ let(:noteable) { issue }
+ let(:policy) { policies(noteable) }
+
+ context 'when the note author can no longer see the noteable' do
+ it 'can not edit nor read the note' do
+ expect(policy).to be_disallowed(:admin_note)
+ expect(policy).to be_disallowed(:resolve_note)
+ expect(policy).to be_disallowed(:read_note)
+ end
+ end
+
+ context 'when the note author can still see the noteable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can edit the note' do
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
+ end
+ end
+ end
+
+ context 'when the project is private' do
+ let(:project) { create(:project, :private, :repository) }
+
+ context 'when the noteable is a commit' do
+ it_behaves_like 'a discussion with a private noteable' do
+ let(:noteable) { project.repository.head_commit }
+ end
+ end
+ end
+
context 'when the project is public' do
context 'when the note author is not a project member' do
it 'can edit a note' do
@@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do
end
end
- context 'when the noteable is a snippet' do
+ context 'when the noteable is a project snippet' do
+ it 'can edit note' do
+ policies = policies(create(:project_snippet, :public, project: project))
+
+ expect(policies).to be_allowed(:admin_note)
+ expect(policies).to be_allowed(:resolve_note)
+ expect(policies).to be_allowed(:read_note)
+ end
+
+ context 'when it is private' do
+ it_behaves_like 'a discussion with a private noteable' do
+ let(:noteable) { create(:project_snippet, :private, project: project) }
+ end
+ end
+ end
+
+ context 'when the noteable is a personal snippet' do
it 'can edit note' do
- policies = policies(create(:project_snippet, project: project))
+ policies = policies(create(:personal_snippet, :public))
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
end
+
+ context 'when it is private' do
+ it 'can not edit nor read the note' do
+ policies = policies(create(:personal_snippet, :private))
+
+ expect(policies).to be_disallowed(:admin_note)
+ expect(policies).to be_disallowed(:resolve_note)
+ expect(policies).to be_disallowed(:read_note)
+ end
+ end
+ end
+
+ context 'when a discussion is confidential' do
+ before do
+ issue.update_attribute(:confidential, true)
+ end
+
+ it_behaves_like 'a discussion with a private noteable'
end
context 'when a discussion is locked' do
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index 270e12bf201..6154be5c425 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -25,7 +25,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with the wrong redirect_uri format' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: ''
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://', scopes: ''
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -33,6 +33,16 @@ describe API::Applications, :api do
expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
end
+ it 'does not allow creating an application with a forbidden URI format' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'javascript://alert()', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response).to be_a Hash
+ expect(json_response['message']['redirect_uri'][0]).to eq('is forbidden by the server.')
+ end
+
it 'does not allow creating an application without a name' do
expect do
post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
new file mode 100644
index 00000000000..355336ad7e2
--- /dev/null
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe 'getting an issue list for a project' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :repository, :public) }
+ let(:current_user) { create(:user) }
+ let(:issues_data) { graphql_data['project']['issues']['edges'] }
+ let!(:issues) do
+ create(:issue, project: project, discussion_locked: true)
+ create(:issue, project: project)
+ end
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('issues'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('issues', {}, fields)
+ )
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'includes a web_url' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data[0]['node']['webUrl']).to be_present
+ end
+
+ it 'includes discussion locked' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data[0]['node']['discussionLocked']).to eq false
+ expect(issues_data[1]['node']['discussionLocked']).to eq true
+ end
+
+ context 'when the user does not have access to the issue' do
+ it 'returns nil' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+ post_graphql(query)
+
+ expect(issues_data).to eq []
+ end
+ end
+end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index cca449e9e56..2c40e266f5f 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -206,6 +206,19 @@ describe API::Helpers do
expect { current_user }.to raise_error Gitlab::Auth::ExpiredError
end
+
+ context 'when impersonation is disabled' do
+ let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
+ end
+
+ it 'does not allow impersonation tokens' do
+ expect { current_user }.to raise_error Gitlab::Auth::ImpersonationDisabled
+ end
+ end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e4e0ca285e0..27bcde77860 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -359,6 +359,8 @@ describe API::MergeRequests do
expect(json_response['should_close_merge_request']).to be_falsy
expect(json_response['force_close_merge_request']).to be_falsy
expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size)
+ expect(json_response['merge_error']).to eq(merge_request.merge_error)
+ expect(json_response).not_to include('rebase_in_progress')
end
it 'exposes description and title html when render_html is true' do
@@ -369,6 +371,14 @@ describe API::MergeRequests do
expect(json_response).to include('title_html', 'description_html')
end
+ it 'exposes rebase_in_progress when include_rebase_in_progress is true' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_rebase_in_progress: true
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to include('rebase_in_progress')
+ end
+
context 'merge_request_metrics' do
before do
merge_request.metrics.update!(merged_by: user,
@@ -1181,6 +1191,26 @@ describe API::MergeRequests do
end
end
+ describe 'PUT :id/merge_requests/:merge_request_iid/rebase' do
+ it 'enqueues a rebase of the merge request against the target branch' do
+ Sidekiq::Testing.fake! do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user)
+ end
+
+ expect(response).to have_gitlab_http_status(202)
+ expect(RebaseWorker.jobs.size).to eq(1)
+ end
+
+ it 'returns 403 if the user cannot push to the branch' do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", guest)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
describe 'Time tracking' do
let(:issuable) { merge_request }
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index c8fa4754810..204702b8a5a 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -42,7 +42,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import for a namespace that does not exist' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
expect(::Projects::CreateService).not_to receive(:new)
post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
@@ -52,7 +52,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import if the user has no permission to the namespace' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post(api('/projects/import', create(:user)),
path: 'test-import3',
@@ -64,7 +64,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import if the user uploads no valid file' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post api('/projects/import', user), path: 'test-import3', file: './random/test'
@@ -119,7 +119,7 @@ describe API::ProjectImport do
let(:existing_project) { create(:project, namespace: user.namespace) }
it 'does not schedule an import' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
@@ -139,7 +139,7 @@ describe API::ProjectImport do
end
def stub_import(namespace)
- expect_any_instance_of(Project).to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).to receive(:schedule)
expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 909703a8d47..b36087b86a7 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -830,6 +830,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
end
+
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response.status).to eq(409)
+ end
+ end
end
context 'when no trace is given' do
@@ -1022,6 +1034,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ patch_the_trace
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response.status).to eq(409)
+ end
+ end
+
context 'when the job is canceled' do
before do
job.cancel
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
index 8f795bb561e..c348fc0efac 100644
--- a/spec/rubocop/cop/migration/add_reference_spec.rb
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -29,7 +29,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
call do
add_reference(:projects, :users)
- ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
def up
add_reference(:projects, :users, index: false)
- ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
@@ -50,5 +50,13 @@ describe RuboCop::Cop::Migration::AddReference do
end
RUBY
end
+
+ it 'does not register an offense when the index is unique' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: { unique: true } )
+ end
+ RUBY
+ end
end
end
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
new file mode 100644
index 00000000000..8e9cb65f3bc
--- /dev/null
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Ci::ArchiveTraceService, '#execute' do
+ subject { described_class.new.execute(job) }
+
+ context 'when job is finished' do
+ let(:job) { create(:ci_build, :success, :trace_live) }
+
+ it 'creates an archived trace' do
+ expect { subject }.not_to raise_error
+
+ expect(job.reload.job_artifacts_trace).to be_exist
+ end
+
+ context 'when trace is already archived' do
+ let!(:job) { create(:ci_build, :success, :trace_artifact) }
+
+ it 'ignores an exception' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'does not create an archived trace' do
+ expect { subject }.not_to change { Ci::JobArtifact.trace.count }
+ end
+ end
+ end
+
+ context 'when job is running' do
+ let(:job) { create(:ci_build, :running, :trace_live) }
+
+ it 'increments Prometheus counter, sends crash report to Sentry and ignore an error for continuing to archive' do
+ expect(Gitlab::Sentry)
+ .to receive(:track_exception)
+ .with(::Gitlab::Ci::Trace::ArchiveError,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
+ extra: { job_id: job.id } ).once
+
+ expect(Rails.logger)
+ .to receive(:error)
+ .with("Failed to archive trace. id: #{job.id} message: Job is not finished yet")
+ .and_call_original
+
+ expect(Gitlab::Metrics)
+ .to receive(:counter)
+ .with(:job_trace_archive_failed_total, "Counter of failed attempts of trace archiving")
+ .and_call_original
+
+ expect { subject }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
index eb0bdb61ee3..f3036fbcb0e 100644
--- a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
+++ b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
@@ -28,41 +28,7 @@ describe Clusters::Applications::CheckIngressIpAddressService do
allow(application.cluster).to receive(:kubeclient).and_return(kubeclient)
end
- describe '#execute' do
- context 'when the ingress ip address is available' do
- it 'updates the external_ip for the app' do
- subject
+ include_examples 'check ingress ip executions', :clusters_applications_ingress
- expect(application.external_ip).to eq('111.222.111.222')
- end
- end
-
- context 'when the ingress ip address is not available' do
- let(:ingress) { nil }
-
- it 'does not error' do
- subject
- end
- end
-
- context 'when the exclusive lease cannot be obtained' do
- it 'does not call kubeclient' do
- stub_exclusive_lease_taken(lease_key, timeout: 15.seconds.to_i)
-
- subject
-
- expect(kubeclient).not_to have_received(:get_service)
- end
- end
-
- context 'when there is already an external_ip' do
- let(:application) { create(:clusters_applications_ingress, :installed, external_ip: '001.111.002.111') }
-
- it 'does not call kubeclient' do
- subject
-
- expect(kubeclient).not_to have_received(:get_service)
- end
- end
- end
+ include_examples 'check ingress ip executions', :clusters_applications_knative
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 ea17f2bb423..45b8ce94815 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -8,14 +8,6 @@ describe Clusters::Applications::CheckInstallationProgressService do
let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN }
let(:errors) { nil }
- shared_examples 'a terminated installation' do
- it 'removes the installation POD' do
- expect(service).to receive(:remove_installation_pod).once
-
- service.execute
- end
- end
-
shared_examples 'a not yet terminated installation' do |a_phase|
let(:phase) { a_phase }
@@ -39,15 +31,13 @@ describe Clusters::Applications::CheckInstallationProgressService do
context 'when timeouted' do
let(:application) { create(:clusters_applications_helm, :timeouted) }
- it_behaves_like 'a terminated installation'
-
it 'make the application errored' do
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
service.execute
expect(application).to be_errored
- expect(application.status_reason).to match(/\btimed out\b/)
+ expect(application.status_reason).to eq("Installation timed out. Check pod logs for install-helm for more details.")
end
end
end
@@ -66,7 +56,11 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(service).to receive(:installation_phase).once.and_return(phase)
end
- it_behaves_like 'a terminated installation'
+ it 'removes the installation POD' do
+ expect(service).to receive(:remove_installation_pod).once
+
+ service.execute
+ end
it 'make the application installed' do
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
@@ -86,13 +80,11 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(service).to receive(:installation_phase).once.and_return(phase)
end
- it_behaves_like 'a terminated installation'
-
it 'make the application errored' do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq("Installation failed")
+ expect(application.status_reason).to eq("Installation failed. Check pod logs for install-helm for more details.")
end
end
@@ -113,6 +105,12 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(application).to be_errored
expect(application.status_reason).to eq('Kubernetes error: 401')
end
+
+ it 'should log error' do
+ expect(service.send(:logger)).to receive(:error)
+
+ service.execute
+ end
end
end
end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 2f801d019fe..018d9822d3e 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -33,8 +33,9 @@ describe Clusters::Applications::InstallService do
end
context 'when k8s cluster communication fails' do
+ let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
before do
- error = Kubeclient::HttpError.new(500, 'system failure', nil)
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
@@ -44,18 +45,81 @@ describe Clusters::Applications::InstallService do
expect(application).to be_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ service.execute
+ end
end
- context 'when application cannot be persisted' do
+ context 'a non kubernetes error happens' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
+ let(:error) { StandardError.new("something bad happened") }
+
+ before do
+ expect(application).to receive(:make_installing!).once.and_raise(error)
+ end
it 'make the application errored' do
- expect(application).to receive(:make_installing!).once.and_raise(ActiveRecord::RecordInvalid)
expect(helm_client).not_to receive(:install)
service.execute
expect(application).to be_errored
+ expect(application.status_reason).to eq("Can't start installation process.")
+ end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ service.execute
end
end
end
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index 5f3c8e82715..84c48d63c64 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -122,26 +122,47 @@ describe Files::MultiService do
let(:action) { 'move' }
let(:new_file_path) { 'files/ruby/new_popen.rb' }
+ let(:result) { subject.execute }
+ let(:blob) { repository.blob_at_branch(branch_name, new_file_path) }
+
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)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to match(original_file_path)
end
end
- context 'when original file have not been updated' do
+ context 'when original file has 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(result[:status]).to eq(:success)
expect(blob).to be_present
+ expect(blob.data).to eq(file_content)
+ end
+
+ context 'when content is nil' do
+ let(:file_content) { nil }
+
+ it 'moves the existing content untouched' do
+ original_content = repository.blob_at_branch(branch_name, original_file_path).data
+
+ expect(result[:status]).to eq(:success)
+ expect(blob).to be_present
+ expect(blob.data).to eq(original_content)
+ end
+ end
+
+ context 'when content is an empty string' do
+ let(:file_content) { '' }
+
+ it 'moves the file and empties it' do
+ expect(result[:status]).to eq(:success)
+ expect(blob).not_to be_nil
+ expect(blob.data).to eq('')
+ end
end
end
end
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index 141ccf7c4d8..da078dd36c6 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -47,7 +47,7 @@ describe Projects::CreateFromTemplateService do
end
it 'is not scheduled' do
- expect(project.import_scheduled?).to be(false)
+ expect(project.import_scheduled?).to be_nil
end
it 'repository is empty' do
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
new file mode 100644
index 00000000000..7e4958f177a
--- /dev/null
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -0,0 +1,92 @@
+shared_examples 'authenticates sessionless user' do |path, format, params|
+ params ||= {}
+
+ before do
+ stub_authentication_activity_metrics(debug: false)
+ end
+
+ let(:user) { create(:user) }
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let(:default_params) { { format: format }.merge(params.except(:public) || {}) }
+
+ context "when the 'personal_access_token' param is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ get path, default_params.merge(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(controller.current_user).to eq(user)
+ end
+
+ it 'does not log the user in if page is public', if: params[:public] do
+ get path, default_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(controller.current_user).to be_nil
+ end
+ end
+
+ context 'when the personal access token has no api scope', unless: params[:public] do
+ it 'does not log the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ personal_access_token.update(scopes: [:read_user])
+
+ get path, default_params.merge(private_token: personal_access_token.token)
+
+ expect(response).not_to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ @request.headers['PRIVATE-TOKEN'] = personal_access_token.token
+ get path, default_params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the 'feed_token' param is populated with the feed token", if: format == :rss do
+ it "logs the user in" do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ get path, default_params.merge(feed_token: user.feed_token)
+
+ expect(response).to have_gitlab_http_status 200
+ end
+ end
+
+ context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do
+ it "logs the user" do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ get path, default_params.merge(feed_token: 'token')
+
+ expect(response.status).not_to eq 200
+ end
+ end
+
+ it "doesn't log the user in otherwise", unless: params[:public] do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ get path, default_params.merge(private_token: 'token')
+
+ expect(response.status).not_to eq(200)
+ end
+end
diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb
index 3f7279a50e0..8d1637228d0 100644
--- a/spec/support/helpers/gpg_helpers.rb
+++ b/spec/support/helpers/gpg_helpers.rb
@@ -1,5 +1,9 @@
+# frozen_string_literal: true
+
module GpgHelpers
- SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'.freeze
+ SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'
+ SIGNED_AND_AUTHORED_SHA = '3c1d9a0266cb0c62d926f4a6c649beed561846f5'
+ DIFFERING_EMAIL_SHA = 'a17a9f66543673edf0a3d1c6b93bdda3fe600f32'
module User1
extend self
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index 4212be2cc88..ce1f9fce10d 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -49,11 +49,11 @@ module PrometheusHelpers
"https://prometheus.example.com/api/v1/series?#{query}"
end
- def stub_prometheus_request(url, body: {}, status: 200)
+ def stub_prometheus_request(url, body: {}, status: 200, headers: {})
WebMock.stub_request(:get, url)
.to_return({
status: status,
- headers: { 'Content-Type' => 'application/json' },
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
body: body.to_json
})
end
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
new file mode 100644
index 00000000000..1b1f67daac3
--- /dev/null
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -0,0 +1,17 @@
+shared_context 'invalid urls' do
+ let(:urls_with_CRLF) do
+ ["http://127.0.0.1:333/pa\rth",
+ "http://127.0.0.1:333/pa\nth",
+ "http://127.0a.0.1:333/pa\r\nth",
+ "http://127.0.0.1:333/path?param=foo\r\nbar",
+ "http://127.0.0.1:333/path?param=foo\rbar",
+ "http://127.0.0.1:333/path?param=foo\nbar",
+ "http://127.0.0.1:333/pa%0dth",
+ "http://127.0.0.1:333/pa%0ath",
+ "http://127.0a.0.1:333/pa%0d%0th",
+ "http://127.0.0.1:333/pa%0D%0Ath",
+ "http://127.0.0.1:333/path?param=foo%0Abar",
+ "http://127.0.0.1:333/path?param=foo%0Dbar",
+ "http://127.0.0.1:333/path?param=foo%0D%0Abar"]
+ end
+end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index 94e82b8ce90..377bd82b67e 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -272,16 +272,11 @@ shared_examples_for 'common trace features' do
include ExclusiveLeaseHelpers
before do
- stub_exclusive_lease_taken("trace:archive:#{trace.job.id}", timeout: 1.hour)
+ stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 1.minute)
end
it 'blocks concurrent archiving' do
- expect(Rails.logger).to receive(:error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
-
- subject
-
- build.reload
- expect(build.job_artifacts_trace).to be_nil
+ expect { subject }.to raise_error(::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
end
end
end
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
new file mode 100644
index 00000000000..14638a574a5
--- /dev/null
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -0,0 +1,33 @@
+shared_examples 'check ingress ip executions' do |app_name|
+ describe '#execute' do
+ let(:application) { create(app_name, :installed) }
+ let(:service) { described_class.new(application) }
+ let(:kubeclient) { double(::Kubeclient::Client, get_service: kube_service) }
+
+ context 'when the ingress ip address is available' do
+ it 'updates the external_ip for the app' do
+ subject
+
+ expect(application.external_ip).to eq('111.222.111.222')
+ end
+ end
+
+ context 'when the ingress ip address is not available' do
+ let(:ingress) { nil }
+
+ it 'does not error' do
+ subject
+ end
+ end
+
+ context 'when the exclusive lease cannot be obtained' do
+ it 'does not call kubeclient' do
+ stub_exclusive_lease_taken(lease_key, timeout: 15.seconds.to_i)
+
+ subject
+
+ expect(kubeclient).not_to have_received(:get_service)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/site_statistics_rake_spec.rb b/spec/tasks/gitlab/site_statistics_rake_spec.rb
deleted file mode 100644
index c43ce25a540..00000000000
--- a/spec/tasks/gitlab/site_statistics_rake_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-require 'rake_helper'
-
-describe 'rake gitlab:refresh_site_statistics' do
- before do
- Rake.application.rake_require 'tasks/gitlab/site_statistics'
-
- create(:project)
- SiteStatistic.fetch.update(repositories_count: 0)
- end
-
- let(:task) { 'gitlab:refresh_site_statistics' }
-
- it 'recalculates existing counters' do
- run_rake_task(task)
-
- expect(SiteStatistic.fetch.repositories_count).to eq(1)
- end
-
- it 'displays message listing counters' do
- expect { run_rake_task(task) }.to output(/Updating Site Statistics counters:.* Repositories\.\.\. OK!/m).to_stdout
- end
-end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index ab6100509a6..082d09d3f16 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UrlValidator do
@@ -6,6 +8,30 @@ describe UrlValidator do
include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
+ describe 'validations' do
+ include_context 'invalid urls'
+
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+
+ it 'returns error when url is nil' do
+ expect(validator.validate_each(badge, :link_url, nil)).to be_nil
+ expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ end
+
+ it 'returns error when url is empty' do
+ expect(validator.validate_each(badge, :link_url, '')).to be_nil
+ expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ end
+
+ it 'does not allow urls with CR or LF characters' do
+ aggregate_failures do
+ urls_with_CRLF.each do |url|
+ expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid'
+ end
+ end
+ end
+ end
+
context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) }
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
new file mode 100644
index 00000000000..2e19d0cec26
--- /dev/null
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'layouts/header/_new_dropdown' do
+ let(:user) { create(:user) }
+
+ context 'group-specific links' do
+ let(:group) { create(:group) }
+
+ before do
+ stub_current_user(user)
+
+ assign(:group, group)
+ end
+
+ context 'as a Group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'has a "New project" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New project',
+ href: new_project_path(namespace_id: group.id)
+ )
+ end
+
+ it 'has a "New subgroup" link', :nested_groups do
+ render
+
+ expect(rendered).to have_link(
+ 'New subgroup',
+ href: new_group_path(parent_id: group.id)
+ )
+ end
+ end
+ end
+
+ context 'project-specific links' do
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+
+ before do
+ assign(:project, project)
+ end
+
+ context 'as a Project owner' do
+ before do
+ stub_current_user(user)
+ end
+
+ it 'has a "New issue" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New issue',
+ href: new_project_issue_path(project)
+ )
+ end
+
+ it 'has a "New merge request" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New merge request',
+ href: project_new_merge_request_path(project)
+ )
+ end
+
+ it 'has a "New snippet" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New snippet',
+ href: new_project_snippet_path(project)
+ )
+ end
+ end
+
+ context 'as a Project guest' do
+ let(:guest) { create(:user) }
+
+ before do
+ stub_current_user(guest)
+ project.add_guest(guest)
+ end
+
+ it 'has no "New merge request" link' do
+ render
+
+ expect(rendered).not_to have_link('New merge request')
+ end
+
+ it 'has no "New snippet" link' do
+ render
+
+ expect(rendered).not_to have_link(
+ 'New snippet',
+ href: new_project_snippet_path(project)
+ )
+ end
+ end
+ end
+
+ context 'global links' do
+ before do
+ stub_current_user(user)
+ end
+
+ it 'has a "New project" link' do
+ render
+
+ expect(rendered).to have_link('New project', href: new_project_path)
+ end
+
+ it 'has a "New group" link' do
+ render
+
+ expect(rendered).to have_link('New group', href: new_group_path)
+ end
+
+ it 'has a "New snippet" link' do
+ render
+
+ expect(rendered).to have_link('New snippet', href: new_snippet_path)
+ end
+ end
+
+ def stub_current_user(current_user)
+ allow(view).to receive(:current_user).and_return(current_user)
+ end
+end
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index b768588c6e1..7244ad4f199 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -5,10 +5,11 @@ describe ArchiveTraceWorker do
subject { described_class.new.perform(job&.id) }
context 'when job is found' do
- let(:job) { create(:ci_build) }
+ let(:job) { create(:ci_build, :trace_live) }
it 'executes service' do
- expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!)
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .to receive(:execute).with(job)
subject
end
@@ -18,7 +19,8 @@ describe ArchiveTraceWorker do
let(:job) { nil }
it 'does not execute service' do
- expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!)
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .not_to receive(:execute)
subject
end
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 23f5dda298a..478fb7d2c0f 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -30,6 +30,13 @@ describe Ci::ArchiveTracesCronWorker do
it_behaves_like 'archives trace'
+ it 'executes service' do
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .to receive(:execute).with(build)
+
+ subject
+ end
+
context 'when a trace had already been archived' do
let!(:build) { create(:ci_build, :success, :trace_live, :trace_artifact) }
let!(:build2) { create(:ci_build, :success, :trace_live) }
@@ -46,11 +53,12 @@ describe Ci::ArchiveTracesCronWorker do
let!(:build) { create(:ci_build, :success, :trace_live) }
before do
+ allow(Gitlab::Sentry).to receive(:track_exception)
allow_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!).and_raise('Unexpected error')
end
it 'puts a log' do
- expect(Rails.logger).to receive(:error).with("Failed to archive stale live trace. id: #{build.id} message: Unexpected error")
+ expect(Rails.logger).to receive(:error).with("Failed to archive trace. id: #{build.id} message: Unexpected error")
subject
end
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index 241e8a2b6d3..d85a87f2cb0 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -58,14 +58,16 @@ describe Gitlab::GithubImport::StageMethods do
end
describe '#find_project' do
+ let(:import_state) { create(:import_state, project: project) }
+
it 'returns a Project for an existing ID' do
- project.update_column(:import_status, 'started')
+ import_state.update_column(:status, 'started')
expect(worker.find_project(project.id)).to eq(project)
end
it 'returns nil for a project that failed importing' do
- project.update_column(:import_status, 'failed')
+ import_state.update_column(:status, 'failed')
expect(worker.find_project(project.id)).to be_nil
end
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index b6c111df8b9..3699fd83a9a 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -28,13 +28,23 @@ describe ProjectImportOptions do
worker_class.sidekiq_retries_exhausted_block.call(job)
- expect(project.reload.import_error).to include("fork")
+ expect(project.import_state.reload.last_error).to include("fork")
end
it 'logs the appropriate error message for forked projects' do
worker_class.sidekiq_retries_exhausted_block.call(job)
- expect(project.reload.import_error).to include("import")
+ expect(project.import_state.reload.last_error).to include("import")
+ end
+
+ context 'when project does not have import_state' do
+ let(:project) { create(:project) }
+
+ it 'raises an error' do
+ expect do
+ worker_class.sidekiq_retries_exhausted_block.call(job)
+ end.to raise_error(NoMethodError)
+ end
end
end
end
diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
index 0f78c5cc644..fc7aafbc0c9 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -17,8 +17,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
context 'when there are remaining jobs' do
before do
allow(worker)
- .to receive(:find_project)
- .and_return(project)
+ .to receive(:find_import_state)
+ .and_return(import_state)
end
it 'reschedules itself' do
@@ -38,8 +38,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
context 'when there are no remaining jobs' do
before do
allow(worker)
- .to receive(:find_project)
- .and_return(project)
+ .to receive(:find_import_state)
+ .and_return(import_state)
allow(worker)
.to receive(:wait_for_jobs)
@@ -48,8 +48,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
end
it 'schedules the next stage' do
- expect(project)
- .to receive(:refresh_import_jid_expiration)
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::Stage::FinishImportWorker)
.to receive(:perform_async)
@@ -96,22 +96,18 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
end
end
- describe '#find_project' do
- it 'returns a Project' do
- project.update_column(:import_status, 'started')
+ describe '#find_import_state' do
+ it 'returns a ProjectImportState' do
+ import_state.update_column(:status, 'started')
- found = worker.find_project(project.id)
+ found = worker.find_import_state(project.id)
- expect(found).to be_an_instance_of(Project)
-
- # This test is there to make sure we only select the columns we care
- # about.
- # TODO: enable this assertion back again
- # expect(found.attributes).to include({ 'id' => nil, 'import_jid' => '123' })
+ expect(found).to be_an_instance_of(ProjectImportState)
+ expect(found.attributes.keys).to match_array(%w(id jid))
end
it 'returns nil if the project import is not running' do
- expect(worker.find_project(project.id)).to be_nil
+ expect(worker.find_import_state(project.id)).to be_nil
end
end
end
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index 25ada575a44..7ff133f1049 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
context 'when the job is running' do
it 'refreshes the import JID and reschedules itself' do
allow(worker)
- .to receive(:find_project)
+ .to receive(:find_import_state)
.with(project.id)
.and_return(project)
@@ -39,7 +39,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
.and_return(true)
expect(project)
- .to receive(:refresh_import_jid_expiration)
+ .to receive(:refresh_jid_expiration)
expect(worker.class)
.to receive(:perform_in_the_future)
@@ -52,7 +52,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
context 'when the job is no longer running' do
it 'returns' do
allow(worker)
- .to receive(:find_project)
+ .to receive(:find_import_state)
.with(project.id)
.and_return(project)
@@ -62,18 +62,18 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
.and_return(false)
expect(project)
- .not_to receive(:refresh_import_jid_expiration)
+ .not_to receive(:refresh_jid_expiration)
worker.perform(project.id, '123')
end
end
end
- describe '#find_project' do
- it 'returns a Project' do
+ describe '#find_import_state' do
+ it 'returns a ProjectImportState' do
project = create(:project, :import_started)
- expect(worker.find_project(project.id)).to be_an_instance_of(Project)
+ expect(worker.find_import_state(project.id)).to be_an_instance_of(ProjectImportState)
end
# it 'only selects the import JID field' do
@@ -84,14 +84,14 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
# .to eq({ 'id' => nil, 'import_jid' => '123abc' })
# end
- it 'returns nil for a project for which the import process failed' do
+ it 'returns nil for a import state for which the import process failed' do
project = create(:project, :import_failed)
- expect(worker.find_project(project.id)).to be_nil
+ expect(worker.find_import_state(project.id)).to be_nil
end
- it 'returns nil for a non-existing project' do
- expect(worker.find_project(-1)).to be_nil
+ it 'returns nil for a non-existing find_import_state' do
+ expect(worker.find_import_state(-1)).to be_nil
end
end
end
diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
index 8c80d660287..ad6154cc4a4 100644
--- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
describe '#import' do
@@ -18,7 +19,7 @@ describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
expect(importer).to receive(:execute)
end
- expect(project).to receive(:refresh_import_jid_expiration)
+ expect(import_state).to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::Stage::ImportPullRequestsWorker)
.to receive(:perform_async)
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 2fc91a3e80a..1fbb073a34a 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
describe '#import' do
@@ -19,8 +20,8 @@ describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
.to receive(:execute)
.and_return(waiter)
- expect(project)
- .to receive(:refresh_import_jid_expiration)
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::AdvanceStageWorker)
.to receive(:perform_async)
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index d07e40377d4..87ac4bc05c1 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -9,13 +9,13 @@ describe RepositoryImportWorker do
describe '#perform' do
let(:project) { create(:project, :import_scheduled) }
+ let(:import_state) { project.import_state }
context 'when worker was reset without cleanup' do
it 'imports the project successfully' do
jid = '12345678'
started_project = create(:project)
-
- create(:import_state, :started, project: started_project, jid: jid)
+ started_import_state = create(:import_state, :started, project: started_project, jid: jid)
allow(subject).to receive(:jid).and_return(jid)
@@ -23,12 +23,12 @@ describe RepositoryImportWorker do
.and_return({ status: :ok })
# Works around https://github.com/rspec/rspec-mocks/issues/910
- expect(Project).to receive(:find).with(project.id).and_return(project)
- expect(project.repository).to receive(:expire_emptiness_caches)
- expect(project.wiki.repository).to receive(:expire_emptiness_caches)
- expect(project).to receive(:import_finish)
+ expect(Project).to receive(:find).with(started_project.id).and_return(started_project)
+ expect(started_project.repository).to receive(:expire_emptiness_caches)
+ expect(started_project.wiki.repository).to receive(:expire_emptiness_caches)
+ expect(started_import_state).to receive(:finish)
- subject.perform(project.id)
+ subject.perform(started_project.id)
end
end
@@ -41,7 +41,7 @@ describe RepositoryImportWorker do
expect(Project).to receive(:find).with(project.id).and_return(project)
expect(project.repository).to receive(:expire_emptiness_caches)
expect(project.wiki.repository).to receive(:expire_emptiness_caches)
- expect(project).to receive(:import_finish)
+ expect(import_state).to receive(:finish)
subject.perform(project.id)
end
@@ -51,26 +51,27 @@ describe RepositoryImportWorker do
it 'hide the credentials that were used in the import URL' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
- project.update(import_jid: '123')
+ import_state.update(jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
- expect(project.reload.import_jid).not_to be_nil
+ expect(import_state.reload.jid).not_to be_nil
end
it 'updates the error on Import/Export' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
- project.update(import_jid: '123', import_type: 'gitlab_project')
+ project.update(import_type: 'gitlab_project')
+ import_state.update(jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
- expect(project.reload.import_error).not_to be_nil
+ expect(import_state.reload.last_error).not_to be_nil
end
end
@@ -90,8 +91,8 @@ describe RepositoryImportWorker do
.to receive(:async?)
.and_return(true)
- expect_any_instance_of(Project)
- .not_to receive(:import_finish)
+ expect_any_instance_of(ProjectImportState)
+ .not_to receive(:finish)
subject.perform(project.id)
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 557934346c9..e09b8e5b964 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -5,7 +5,7 @@ describe StuckCiJobsWorker do
let!(:runner) { create :ci_runner }
let!(:job) { create :ci_build, runner: runner }
- let(:trace_lease_key) { "trace:archive:#{job.id}" }
+ let(:trace_lease_key) { "trace:write:lock:#{job.id}" }
let(:trace_lease_uuid) { SecureRandom.uuid }
let(:worker_lease_key) { StuckCiJobsWorker::EXCLUSIVE_LEASE_KEY }
let(:worker_lease_uuid) { SecureRandom.uuid }
diff --git a/yarn.lock b/yarn.lock
index 63a8913fa3d..6d5ff62715b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -629,10 +629,10 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0-beta.3"
-"@gitlab/svgs@^1.38.0":
- version "1.38.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.38.0.tgz#e2f6e73379d60c7c63af4df8242a94c4671a1dfe"
- integrity sha512-Mzv6PxVbWEPvvMgXHaGxk8UE1Gard2gifca6loLgfLH7BtjXfESiZyJdQkkTSeBYp5MoqQa88Kw+vJYobwjsSw==
+"@gitlab/svgs@^1.40.0":
+ version "1.40.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.40.0.tgz#58d7545fde3a7af3c290b419d1de2405973e58c5"
+ integrity sha512-Y5QkaZH5N84qSNSGPxaj+NNlI4kthUNet7eRS1QCnaskwcvuWd/vF0xYCPd/tbRnK9MIhkKzhbxatUYDZVgXTQ==
"@gitlab/ui@^1.11.0":
version "1.11.0"
@@ -654,6 +654,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
+"@types/async@2.0.50":
+ version "2.0.50"
+ resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.50.tgz#117540e026d64e1846093abbd5adc7e27fda7bcb"
+ integrity sha512-VMhZMMQgV1zsR+lX/0IBfAk+8Eb7dPVMWiQGFAt3qjo5x7Ml6b77jUo0e1C3ToD+XRDXqtrfw+6AB0uUsPEr3Q==
+
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
@@ -678,12 +683,7 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
-"@types/node@*":
- version "10.5.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.2.tgz#f19f05314d5421fe37e74153254201a7bf00a707"
- integrity sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==
-
-"@types/node@^10.11.7":
+"@types/node@*", "@types/node@^10.11.7":
version "10.12.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.9.tgz#a07bfa74331471e1dc22a47eb72026843f7b95c8"
integrity sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==
@@ -698,6 +698,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
+"@types/zen-observable@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
+ integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
+
"@vue/component-compiler-utils@^2.0.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz#bbbb7ed38a9a8a7c93abe7ef2e54a90a04b631b4"
@@ -882,7 +887,7 @@ abbrev@1, abbrev@1.0.x:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU=
-accepts@~1.3.3, accepts@~1.3.4, accepts@~1.3.5:
+accepts@~1.3.4, accepts@~1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I=
@@ -996,6 +1001,103 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
+apollo-boost@^0.1.20:
+ version "0.1.20"
+ resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.1.20.tgz#cc3e418ebd2bea857656685d32a7a20443493363"
+ integrity sha512-n2MiEY5IGpD/cy0RH+pM9vbmobM/JZ5qz38XQAUA41FxxMPlLFQxf0IUMm0tijLOJvJJBub3pDt+Of4TVPBCqA==
+ dependencies:
+ apollo-cache "^1.1.20"
+ apollo-cache-inmemory "^1.3.9"
+ apollo-client "^2.4.5"
+ apollo-link "^1.0.6"
+ apollo-link-error "^1.0.3"
+ apollo-link-http "^1.3.1"
+ apollo-link-state "^0.4.0"
+ graphql-tag "^2.4.2"
+
+apollo-cache-inmemory@^1.3.9:
+ version "1.3.9"
+ resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.3.9.tgz#10738ba6a04faaeeb0da21bbcc1f7c0b5902910c"
+ integrity sha512-Q2k84p/OqIuMUyeWGc6XbVXXZu0erYOO+wTx9p+CnQUspnNvf7zmvFNgFnmudXzfuG1m1CSzePk6fC/M1ehOqQ==
+ dependencies:
+ apollo-cache "^1.1.20"
+ apollo-utilities "^1.0.25"
+ optimism "^0.6.6"
+
+apollo-cache@1.1.20, apollo-cache@^1.1.20:
+ version "1.1.20"
+ resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.20.tgz#6152cc4baf6a63e376efee79f75de4f5c84bf90e"
+ integrity sha512-+Du0/4kUSuf5PjPx0+pvgMGV12ezbHA8/hubYuqRQoy/4AWb4faa61CgJNI6cKz2mhDd9m94VTNKTX11NntwkQ==
+ dependencies:
+ apollo-utilities "^1.0.25"
+
+apollo-client@^2.4.5:
+ version "2.4.5"
+ resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.5.tgz#545beda1ef60814943b5622f0feabc9f29ee9822"
+ integrity sha512-nUm06EGa4TP/IY68OzmC3lTD32TqkjLOQdb69uYo+lHl8NnwebtrAw3qFtsQtTEz6ueBp/Z/HasNZng4jwafVQ==
+ dependencies:
+ "@types/zen-observable" "^0.8.0"
+ apollo-cache "1.1.20"
+ apollo-link "^1.0.0"
+ apollo-link-dedup "^1.0.0"
+ apollo-utilities "1.0.25"
+ symbol-observable "^1.0.2"
+ zen-observable "^0.8.0"
+ optionalDependencies:
+ "@types/async" "2.0.50"
+
+apollo-link-dedup@^1.0.0:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae"
+ integrity sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w==
+ dependencies:
+ apollo-link "^1.2.3"
+
+apollo-link-error@^1.0.3:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.1.tgz#69d7124d4dc11ce60f505c940f05d4f1aa0945fb"
+ integrity sha512-/yPcaQWcBdB94vpJ4FsiCJt1dAGGRm+6Tsj3wKwP+72taBH+UsGRQQZk7U/1cpZwl1yqhHZn+ZNhVOebpPcIlA==
+ dependencies:
+ apollo-link "^1.2.3"
+
+apollo-link-http-common@^0.2.5:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.5.tgz#d094beb7971523203359bf830bfbfa7b4e7c30ed"
+ integrity sha512-6FV1wr5AqAyJ64Em1dq5hhGgiyxZE383VJQmhIoDVc3MyNcFL92TkhxREOs4rnH2a9X2iJMko7nodHSGLC6d8w==
+ dependencies:
+ apollo-link "^1.2.3"
+
+apollo-link-http@^1.3.1:
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.5.tgz#7dbe851821771ad67fa29e3900c57f38cbd80da8"
+ integrity sha512-C5N6N/mRwmepvtzO27dgMEU3MMtRKSqcljBkYNZmWwH11BxkUQ5imBLPM3V4QJXNE7NFuAQAB5PeUd4ligivTQ==
+ dependencies:
+ apollo-link "^1.2.3"
+ apollo-link-http-common "^0.2.5"
+
+apollo-link-state@^0.4.0:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.2.tgz#ac00e9be9b0ca89eae0be6ba31fe904b80bbe2e8"
+ integrity sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==
+ dependencies:
+ apollo-utilities "^1.0.8"
+ graphql-anywhere "^4.1.0-alpha.0"
+
+apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d"
+ integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw==
+ dependencies:
+ apollo-utilities "^1.0.0"
+ zen-observable-ts "^0.8.10"
+
+apollo-utilities@1.0.25, apollo-utilities@^1.0.0, apollo-utilities@^1.0.25, apollo-utilities@^1.0.8:
+ version "1.0.25"
+ resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.25.tgz#899b00f5f990fb451675adf84cb3de82eb6372ea"
+ integrity sha512-AXvqkhni3Ir1ffm4SA1QzXn8k8I5BBl4PVKEyak734i4jFdp+xgfUyi2VCqF64TJlFTA/B73TRDUvO2D+tKtZg==
+ dependencies:
+ fast-json-stable-stringify "^2.0.0"
+
append-transform@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"
@@ -1038,11 +1140,6 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
-array-find-index@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
- integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
-
array-find@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
@@ -1139,9 +1236,9 @@ async@^2.0.0, async@^2.5.0, async@^2.6.1:
lodash "^4.17.10"
atob@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
- integrity sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+ integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autosize@^4.0.0:
version "4.0.0"
@@ -1408,10 +1505,10 @@ bootstrap-vue@^2.0.0-rc.11:
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
-bootstrap@4.1.1, bootstrap@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.1.tgz#3aec85000fa619085da8d2e4983dfd67cf2114cb"
- integrity sha512-SpiDSOcbg4J/PjVSt4ny5eY6j74VbVSjROY4Fb/WIUXBV9cnb5luyR4KnPvNoXuGnBK1T+nJIWqRsvU3yP8Mcg==
+bootstrap@4.1.3, bootstrap@^4.1.1:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
+ integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==
boxen@^1.2.1:
version "1.3.0"
@@ -1565,11 +1662,6 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
-bytes@2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
- integrity sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo=
-
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -1892,12 +1984,7 @@ combine-lists@^1.0.0:
dependencies:
lodash "^4.5.0"
-commander@2, commander@^2.18.0:
- version "2.18.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970"
- integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==
-
-commander@^2.19.0:
+commander@2, commander@^2.18.0, commander@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@@ -1937,12 +2024,12 @@ component-inherit@0.0.3:
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
-compressible@~2.0.10:
- version "2.0.11"
- resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a"
- integrity sha1-FnGKdd4oPtjmBAQWJaIGRYZ5fYo=
+compressible@~2.0.14:
+ version "2.0.15"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.15.tgz#857a9ab0a7e5a07d8d837ed43fe2defff64fe212"
+ integrity sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==
dependencies:
- mime-db ">= 1.29.0 < 2"
+ mime-db ">= 1.36.0 < 2"
compression-webpack-plugin@^2.0.0:
version "2.0.0"
@@ -1957,17 +2044,17 @@ compression-webpack-plugin@^2.0.0:
webpack-sources "^1.0.1"
compression@^1.5.2:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d"
- integrity sha1-AwyfGY8WQ6BX13anOOki2kNzAS0=
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
+ integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
dependencies:
- accepts "~1.3.3"
- bytes "2.5.0"
- compressible "~2.0.10"
- debug "2.6.8"
+ accepts "~1.3.5"
+ bytes "3.0.0"
+ compressible "~2.0.14"
+ debug "2.6.9"
on-headers "~1.0.1"
- safe-buffer "5.1.1"
- vary "~1.1.1"
+ safe-buffer "5.1.2"
+ vary "~1.1.2"
concat-map@0.0.1:
version "0.0.1"
@@ -2242,13 +2329,6 @@ cssesc@^0.1.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=
-currently-unhandled@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
- integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
- dependencies:
- array-find-index "^1.0.1"
-
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -2335,12 +2415,7 @@ d3-force@1.1.0:
d3-quadtree "1"
d3-timer "1"
-d3-format@1, d3-format@1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f"
- integrity sha512-U4zRVLDXW61bmqoo+OJ/V687e1T5nVd3TAKAJKgtpZ/P1JsMgyod0y9br+mlQOryTAACdiXI3wCjuERHFNp91w==
-
-d3-format@1.2.2:
+d3-format@1, d3-format@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==
@@ -2412,12 +2487,7 @@ d3-scale@1.0.7, d3-scale@^1.0.7:
d3-time "1"
d3-time-format "2"
-d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88"
- integrity sha512-xW2Pfcdzh1gOaoI+LGpPsLR2VpBQxuFoxvrvguK8ZmrJbPIVvfNG6pU6GNfK41D6Qz15sj61sbW/AFYuukwaLQ==
-
-d3-selection@1.3.0:
+d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==
@@ -2474,42 +2544,6 @@ d3-zoom@1.7.1:
d3-selection "1"
d3-transition "1"
-d3@4.12.2:
- version "4.12.2"
- resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.2.tgz#12f775564c6a9de229f63db03446e2cb7bb56c8f"
- integrity sha512-aKAlpgTmpuGeEpezB+GvPpX1x+gCMs/PHpuse6sCpkgw4Un3ZeqUobIc87eIy9adcl+wxPAnEyKyO5oulH3MOw==
- dependencies:
- d3-array "1.2.1"
- d3-axis "1.0.8"
- d3-brush "1.0.4"
- d3-chord "1.0.4"
- d3-collection "1.0.4"
- d3-color "1.0.3"
- d3-dispatch "1.0.3"
- d3-drag "1.2.1"
- d3-dsv "1.0.8"
- d3-ease "1.0.3"
- d3-force "1.1.0"
- d3-format "1.2.1"
- d3-geo "1.9.1"
- d3-hierarchy "1.1.5"
- d3-interpolate "1.1.6"
- d3-path "1.0.5"
- d3-polygon "1.0.3"
- d3-quadtree "1.0.3"
- d3-queue "3.0.7"
- d3-random "1.1.0"
- d3-request "1.0.6"
- d3-scale "1.0.7"
- d3-selection "1.2.0"
- d3-shape "1.2.0"
- d3-time "1.0.8"
- d3-time-format "2.1.1"
- d3-timer "1.0.7"
- d3-transition "1.1.1"
- d3-voronoi "1.1.2"
- d3-zoom "1.7.1"
-
d3@^4.13.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d"
@@ -2582,24 +2616,17 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2.6.8:
- version "2.6.8"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
- integrity sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=
- dependencies:
- ms "2.0.0"
-
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-debug@^3.1.0:
- version "3.2.5"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407"
- integrity sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==
+debug@^3.1.0, debug@^3.2.5:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
@@ -2639,10 +2666,10 @@ deep-equal@^1.0.1:
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
-deep-extend@~0.4.0:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
- integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
deep-is@~0.1.3:
version "0.1.3"
@@ -3361,12 +3388,12 @@ events@^1.0.0:
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
-eventsource@0.1.6:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
- integrity sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=
+eventsource@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
+ integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
dependencies:
- original ">=0.0.5"
+ original "^1.0.0"
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
version "1.0.3"
@@ -3492,9 +3519,9 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
is-extendable "^1.0.1"
extend@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
- integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
external-editor@^2.0.1:
version "2.2.0"
@@ -3555,7 +3582,7 @@ faye-websocket@^0.10.0:
dependencies:
websocket-driver ">=0.5.1"
-faye-websocket@~0.11.0:
+faye-websocket@~0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=
@@ -3992,6 +4019,25 @@ graphlibrary@^2.2.0:
dependencies:
lodash "^4.17.5"
+graphql-anywhere@^4.1.0-alpha.0:
+ version "4.1.22"
+ resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.22.tgz#1c831ba3c9e5664a0dd24d10d23a9e9512d92056"
+ integrity sha512-qm2/1cKM8nfotxDhm4J0r1znVlK0Yge/yEKt26EVVBgpIhvxjXYFALCGbr7cvfDlvzal1iSPpaYa+8YTtjsxQA==
+ dependencies:
+ apollo-utilities "^1.0.25"
+
+graphql-tag@^2.10.0, graphql-tag@^2.4.2:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae"
+ integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w==
+
+graphql@^14.0.2:
+ version "14.0.2"
+ resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.0.2.tgz#7dded337a4c3fd2d075692323384034b357f5650"
+ integrity sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==
+ dependencies:
+ iterall "^1.2.2"
+
gzip-size@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
@@ -4199,7 +4245,7 @@ http-deceiver@^1.2.7:
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
-http-errors@1.6.2, http-errors@~1.6.1, http-errors@~1.6.2:
+http-errors@1.6.2, http-errors@~1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=
@@ -4288,6 +4334,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
+immutable-tuple@^0.4.9:
+ version "0.4.9"
+ resolved "https://registry.yarnpkg.com/immutable-tuple/-/immutable-tuple-0.4.9.tgz#473ebdd6c169c461913a454bf87ef8f601a20ff0"
+ integrity sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA==
+
import-lazy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -4835,6 +4886,11 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
+iterall@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"
+ integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==
+
jasmine-core@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.0.tgz#bfbb56defcd30789adec5a3fbba8504233289c72"
@@ -5276,14 +5332,6 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
-loud-rejection@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
- integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
- dependencies:
- currently-unhandled "^0.4.1"
- signal-exit "^3.0.0"
-
lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
@@ -5446,17 +5494,17 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
-"mime-db@>= 1.29.0 < 2", mime-db@~1.33.0:
- version "1.33.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
- integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+"mime-db@>= 1.36.0 < 2", mime-db@~1.37.0:
+ version "1.37.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
+ integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
-mime-types@~2.1.15, mime-types@~2.1.18:
- version "2.1.18"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
- integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+mime-types@~2.1.17, mime-types@~2.1.18:
+ version "2.1.21"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
+ integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
dependencies:
- mime-db "~1.33.0"
+ mime-db "~1.37.0"
mime@1.4.1:
version "1.4.1"
@@ -5660,10 +5708,10 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-needle@^2.2.0:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d"
- integrity sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==
+needle@^2.2.1:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
+ integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==
dependencies:
debug "^2.1.2"
iconv-lite "^0.4.4"
@@ -5727,17 +5775,17 @@ node-forge@0.6.33:
vm-browserify "0.0.4"
node-pre-gyp@^0.10.0:
- version "0.10.0"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
- integrity sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
+ integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
- needle "^2.2.0"
+ needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
- rc "^1.1.7"
+ rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4"
@@ -5982,6 +6030,13 @@ opn@^5.1.0:
dependencies:
is-wsl "^1.1.0"
+optimism@^0.6.6:
+ version "0.6.8"
+ resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.8.tgz#0780b546da8cd0a72e5207e0c3706c990c8673a6"
+ integrity sha512-bN5n1KCxSqwBDnmgDnzMtQTHdL+uea2HYFx1smvtE+w2AMl0Uy31g0aXnP/Nt85OINnMJPRpJyfRQLTCqn5Weg==
+ dependencies:
+ immutable-tuple "^0.4.9"
+
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -6002,12 +6057,12 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
-original@>=0.0.5:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
- integrity sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=
+original@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
+ integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
dependencies:
- url-parse "1.0.x"
+ url-parse "^1.4.3"
os-browserify@^0.3.0:
version "0.3.0"
@@ -6180,7 +6235,7 @@ parseuri@0.0.5:
dependencies:
better-assert "~1.0.0"
-parseurl@~1.3.1, parseurl@~1.3.2:
+parseurl@~1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=
@@ -6576,15 +6631,10 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
-querystringify@0.0.x:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
- integrity sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=
-
-querystringify@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
- integrity sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=
+querystringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef"
+ integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
@@ -6633,12 +6683,12 @@ raw-loader@^0.5.1:
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
integrity sha1-DD0L6u2KAclm2Xh793goElKpeao=
-rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd"
- integrity sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=
+rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
- deep-extend "~0.4.0"
+ deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
@@ -6852,7 +6902,7 @@ require-uncached@^1.0.3:
caller-path "^0.1.0"
resolve-from "^1.0.0"
-requires-port@1.0.x, requires-port@1.x.x:
+requires-port@1.x.x, requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
@@ -6967,7 +7017,7 @@ safe-buffer@5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -7057,12 +7107,7 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1:
- version "5.5.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
- integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==
-
-semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
@@ -7092,17 +7137,17 @@ serialize-javascript@^1.4.0:
integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
serve-index@^1.7.2:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7"
- integrity sha1-0rKA/FYNYW7oG0i/D6gqvtJIXOc=
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
+ integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=
dependencies:
- accepts "~1.3.3"
+ accepts "~1.3.4"
batch "0.6.1"
- debug "2.6.8"
+ debug "2.6.9"
escape-html "~1.0.3"
- http-errors "~1.6.1"
- mime-types "~2.1.15"
- parseurl "~1.3.1"
+ http-errors "~1.6.2"
+ mime-types "~2.1.17"
+ parseurl "~1.3.2"
serve-static@1.13.2:
version "1.13.2"
@@ -7301,17 +7346,17 @@ socket.io@2.1.1:
socket.io-client "2.1.1"
socket.io-parser "~3.2.0"
-sockjs-client@1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83"
- integrity sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=
+sockjs-client@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
+ integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==
dependencies:
- debug "^2.6.6"
- eventsource "0.1.6"
- faye-websocket "~0.11.0"
- inherits "^2.0.1"
+ debug "^3.2.5"
+ eventsource "^1.0.7"
+ faye-websocket "~0.11.1"
+ inherits "^2.0.3"
json3 "^3.3.2"
- url-parse "^1.1.8"
+ url-parse "^1.4.3"
sockjs@0.3.19:
version "0.3.19"
@@ -7638,6 +7683,11 @@ svg4everybody@2.1.9:
resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
integrity sha1-W9n23vwTOFmgRGRtR0P6vCjbfi0=
+symbol-observable@^1.0.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
+ integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
+
table@^4.0.3:
version "4.0.3"
resolved "http://registry.npmjs.org/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"
@@ -7715,6 +7765,11 @@ three@^0.84.0:
resolved "https://registry.yarnpkg.com/three/-/three-0.84.0.tgz#95be85a55a0fa002aa625ed559130957dcffd918"
integrity sha1-lb6FpVoPoAKqYl7VWRMJV9z/2Rg=
+throttle-debounce@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.0.1.tgz#7307ddd6cd9acadb349132fbf6c18d78c88a5e62"
+ integrity sha512-Sr6jZBlWShsAaSXKyNXyNicOrJW/KtkDqIEwHt4wYwWA2wa/q67Luhqoujg48V8hTk60wB56tYrJJn6jc2R7VA==
+
through2@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -8022,11 +8077,6 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
-url-join@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
- integrity sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=
-
url-loader@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.1.tgz#4d1f3b4f90dde89f02c008e662d604d7511167c1"
@@ -8050,21 +8100,13 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
-url-parse@1.0.x:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
- integrity sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=
- dependencies:
- querystringify "0.0.x"
- requires-port "1.0.x"
-
-url-parse@^1.1.8:
- version "1.1.9"
- resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19"
- integrity sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=
+url-parse@^1.4.3:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
+ integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==
dependencies:
- querystringify "~1.0.0"
- requires-port "1.0.x"
+ querystringify "^2.0.0"
+ requires-port "^1.0.0"
url-search-params-polyfill@^5.0.0:
version "5.0.0"
@@ -8144,7 +8186,7 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
-vary@~1.1.1, vary@~1.1.2:
+vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
@@ -8166,6 +8208,14 @@ void-elements@^2.0.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+vue-apollo@^3.0.0-beta.25:
+ version "3.0.0-beta.25"
+ resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-beta.25.tgz#05a9a699b2ba6103639e9bd6c3bb88ca04c4b637"
+ integrity sha512-M7/l3h0NlFvaZ/s/wrtRiOt3xXMbaNNuteGaCY+U5D0ABrQqvCgy5mayIZHurQxbloluNkbCt18wRKAgJTAuKA==
+ dependencies:
+ chalk "^2.4.1"
+ throttle-debounce "^2.0.0"
+
vue-eslint-parser@^3.2.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-3.2.2.tgz#47c971ee4c39b0ee7d7f5e154cb621beb22f7a34"
@@ -8298,23 +8348,20 @@ webpack-cli@^3.1.0:
v8-compile-cache "^2.0.0"
yargs "^12.0.1"
-webpack-dev-middleware@3.2.0, webpack-dev-middleware@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz#a20ceef194873710052da678f3c6ee0aeed92552"
- integrity sha512-YJLMF/96TpKXaEQwaLEo+Z4NDK8aV133ROF6xp9pe3gQoS7sxfpXh4Rv9eC+8vCvWfmDjRQaMSlRPbO+9G6jgA==
+webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.2.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
+ integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==
dependencies:
- loud-rejection "^1.6.0"
memory-fs "~0.4.1"
mime "^2.3.1"
- path-is-absolute "^1.0.0"
range-parser "^1.0.3"
- url-join "^4.0.0"
webpack-log "^2.0.0"
-webpack-dev-server@^3.1.8:
- version "3.1.8"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.8.tgz#eb7a95945d1108170f902604fb3b939533d9daeb"
- integrity sha512-c+tcJtDqnPdxCAzEEZKdIPmg3i5i7cAHe+B+0xFNK0BlCc2HF/unYccbU7xTgfGc5xxhCztCQzFmsqim+KhI+A==
+webpack-dev-server@^3.1.10:
+ version "3.1.10"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz#507411bee727ee8d2fdffdc621b66a64ab3dea2b"
+ integrity sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==
dependencies:
ansi-html "0.0.7"
bonjour "^3.5.0"
@@ -8337,11 +8384,11 @@ webpack-dev-server@^3.1.8:
selfsigned "^1.9.1"
serve-index "^1.7.2"
sockjs "0.3.19"
- sockjs-client "1.1.5"
+ sockjs-client "1.3.0"
spdy "^3.4.1"
strip-ansi "^3.0.0"
supports-color "^5.1.0"
- webpack-dev-middleware "3.2.0"
+ webpack-dev-middleware "3.4.0"
webpack-log "^2.0.0"
yargs "12.0.2"
@@ -8584,3 +8631,15 @@ yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
+
+zen-observable-ts@^0.8.10:
+ version "0.8.10"
+ resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829"
+ integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ==
+ dependencies:
+ zen-observable "^0.8.0"
+
+zen-observable@^0.8.0:
+ version "0.8.11"
+ resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
+ integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==