summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock9
-rw-r--r--PROCESS.md22
-rw-r--r--README.md1
-rw-r--r--app/assets/javascripts/api.js3
-rw-r--r--app/assets/javascripts/breadcrumb.js28
-rw-r--r--app/assets/javascripts/commons/index.js1
-rw-r--r--app/assets/javascripts/commons/vue.js (renamed from app/assets/javascripts/vue_shared/common_vue.js)1
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js8
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js4
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js2
-rw-r--r--app/assets/javascripts/gl_dropdown.js14
-rw-r--r--app/assets/javascripts/group_name.js76
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js25
-rw-r--r--app/assets/javascripts/layout_nav.js15
-rw-r--r--app/assets/javascripts/main.js9
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue10
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue127
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue15
-rw-r--r--app/assets/javascripts/monitoring/components/graph/legend.vue83
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/graph_row.vue41
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_paths.vue40
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js4
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js70
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js12
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js80
-rw-r--r--app/assets/javascripts/new_sidebar.js2
-rw-r--r--app/assets/javascripts/notes.js8
-rw-r--r--app/assets/javascripts/project.js4
-rw-r--r--app/assets/javascripts/project_select.js42
-rw-r--r--app/assets/javascripts/projects_dropdown/components/app.vue157
-rw-r--r--app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue57
-rw-r--r--app/assets/javascripts/projects_dropdown/components/projects_list_item.vue96
-rw-r--r--app/assets/javascripts/projects_dropdown/components/projects_list_search.vue63
-rw-r--r--app/assets/javascripts/projects_dropdown/components/search.vue64
-rw-r--r--app/assets/javascripts/projects_dropdown/constants.js10
-rw-r--r--app/assets/javascripts/projects_dropdown/event_hub.js3
-rw-r--r--app/assets/javascripts/projects_dropdown/index.js68
-rw-r--r--app/assets/javascripts/projects_dropdown/service/projects_service.js132
-rw-r--r--app/assets/javascripts/projects_dropdown/store/projects_store.js33
-rw-r--r--app/assets/javascripts/right_sidebar.js2
-rw-r--r--app/assets/javascripts/sidebar_height_manager.js37
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/identicon.vue8
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss160
-rw-r--r--app/assets/stylesheets/framework/header.scss13
-rw-r--r--app/assets/stylesheets/framework/selects.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/new_nav.scss423
-rw-r--r--app/assets/stylesheets/new_sidebar.scss11
-rw-r--r--app/assets/stylesheets/pages/boards.scss1
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/environments.scss38
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss18
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/controllers/concerns/issuable_collections.rb28
-rw-r--r--app/controllers/projects/issues_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/issues_finder.rb1
-rw-r--r--app/finders/merge_requests_finder.rb1
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/breadcrumbs_helper.rb12
-rw-r--r--app/helpers/groups_helper.rb28
-rw-r--r--app/helpers/issuables_helper.rb13
-rw-r--r--app/helpers/nav_helper.rb23
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/page_layout_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb19
-rw-r--r--app/helpers/search_helper.rb11
-rw-r--r--app/helpers/wiki_helper.rb11
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/ci/trigger_request.rb4
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb13
-rw-r--r--app/models/concerns/issuable.rb11
-rw-r--r--app/models/concerns/resolvable_discussion.rb1
-rw-r--r--app/models/concerns/resolvable_note.rb28
-rw-r--r--app/models/gpg_key.rb23
-rw-r--r--app/models/gpg_signature.rb14
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/member.rb62
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/repository.rb28
-rw-r--r--app/models/user.rb4
-rw-r--r--app/presenters/ci/build_presenter.rb11
-rw-r--r--app/services/ci/create_trigger_request_service.rb19
-rw-r--r--app/services/discussions/update_diff_position_service.rb4
-rw-r--r--app/services/projects/update_pages_service.rb2
-rw-r--r--app/views/admin/applications/edit.html.haml2
-rw-r--r--app/views/admin/cohorts/index.html.haml1
-rw-r--r--app/views/admin/dashboard/index.html.haml1
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/hooks/edit.html.haml1
-rw-r--r--app/views/admin/jobs/index.html.haml1
-rw-r--r--app/views/admin/labels/edit.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml1
-rw-r--r--app/views/admin/services/edit.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/dashboard/_groups_head.html.haml6
-rw-r--r--app/views/dashboard/_projects_head.html.haml6
-rw-r--r--app/views/dashboard/_snippets_head.html.haml6
-rw-r--r--app/views/dashboard/issues.html.haml11
-rw-r--r--app/views/dashboard/merge_requests.html.haml7
-rw-r--r--app/views/dashboard/milestones/index.html.haml7
-rw-r--r--app/views/discussions/_headline.html.haml4
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/issues.html.haml4
-rw-r--r--app/views/groups/labels/index.html.haml4
-rw-r--r--app/views/groups/merge_requests.html.haml4
-rw-r--r--app/views/groups/milestones/index.html.haml4
-rw-r--r--app/views/groups/projects.html.haml1
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml3
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/groups/subgroups.html.haml1
-rw-r--r--app/views/layouts/_head.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml26
-rw-r--r--app/views/layouts/admin.html.haml7
-rw-r--r--app/views/layouts/application.html.haml5
-rw-r--r--app/views/layouts/group.html.haml7
-rw-r--r--app/views/layouts/header/_default.html.haml78
-rw-r--r--app/views/layouts/header/_new.html.haml84
-rw-r--r--app/views/layouts/header/_new_dropdown.haml8
-rw-r--r--app/views/layouts/nav/_admin.html.haml40
-rw-r--r--app/views/layouts/nav/_admin_settings.html.haml31
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml26
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml123
-rw-r--r--app/views/layouts/nav/_explore.html.haml30
-rw-r--r--app/views/layouts/nav/_group.html.haml31
-rw-r--r--app/views/layouts/nav/_new_dashboard.html.haml33
-rw-r--r--app/views/layouts/nav/_new_explore.html.haml19
-rw-r--r--app/views/layouts/nav/_profile.html.haml57
-rw-r--r--app/views/layouts/nav/_project.html.haml111
-rw-r--r--app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml11
-rw-r--r--app/views/layouts/nav/projects_dropdown/_show.html.haml15
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml (renamed from app/views/layouts/nav/_new_admin_sidebar.html.haml)0
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml (renamed from app/views/layouts/nav/_new_group_sidebar.html.haml)0
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml (renamed from app/views/layouts/nav/_new_profile_sidebar.html.haml)0
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml (renamed from app/views/layouts/nav/_new_project_sidebar.html.haml)0
-rw-r--r--app/views/layouts/profile.html.haml7
-rw-r--r--app/views/layouts/project.html.haml15
-rw-r--r--app/views/profiles/passwords/edit.html.haml1
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml1
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml5
-rw-r--r--app/views/projects/_flash_messages.html.haml3
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml4
-rw-r--r--app/views/projects/activity.html.haml4
-rw-r--r--app/views/projects/artifacts/browse.html.haml4
-rw-r--r--app/views/projects/boards/_show.html.haml4
-rw-r--r--app/views/projects/branches/index.html.haml3
-rw-r--r--app/views/projects/commit/_invalid_signature_badge.html.haml9
-rw-r--r--app/views/projects/commit/_other_user_signature_badge.html.haml6
-rw-r--r--app/views/projects/commit/_same_user_different_email_signature_badge.html.haml7
-rw-r--r--app/views/projects/commit/_signature.html.haml5
-rw-r--r--app/views/projects/commit/_signature_badge.html.haml20
-rw-r--r--app/views/projects/commit/_signature_badge_user.html.haml21
-rw-r--r--app/views/projects/commit/_unknown_key_signature_badge.html.haml1
-rw-r--r--app/views/projects/commit/_unverified_key_signature_badge.html.haml1
-rw-r--r--app/views/projects/commit/_unverified_signature_badge.html.haml6
-rw-r--r--app/views/projects/commit/_valid_signature_badge.html.haml32
-rw-r--r--app/views/projects/commit/_verified_signature_badge.html.haml7
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml3
-rw-r--r--app/views/projects/compare/index.html.haml3
-rw-r--r--app/views/projects/compare/show.html.haml4
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml1
-rw-r--r--app/views/projects/empty.html.haml1
-rw-r--r--app/views/projects/environments/index.html.haml4
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--app/views/projects/graphs/charts.html.haml2
-rw-r--r--app/views/projects/graphs/show.html.haml3
-rw-r--r--app/views/projects/issues/_issues.html.haml2
-rw-r--r--app/views/projects/issues/index.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml5
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml8
-rw-r--r--app/views/projects/jobs/index.html.haml3
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml7
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml6
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml2
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/edit.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml8
-rw-r--r--app/views/projects/pipeline_schedules/new.html.haml3
-rw-r--r--app/views/projects/pipelines/charts.html.haml3
-rw-r--r--app/views/projects/pipelines/show.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml15
-rw-r--r--app/views/projects/project_members/index.html.haml3
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/services/edit.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml6
-rw-r--r--app/views/projects/settings/integrations/show.html.haml3
-rw-r--r--app/views/projects/settings/repository/show.html.haml4
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml4
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml4
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml7
-rw-r--r--app/views/shared/_logo.svg2
-rw-r--r--app/views/shared/icons/_caret_down.svg1
-rw-r--r--app/views/shared/icons/_icon_resolve_discussion.svg1
-rwxr-xr-xapp/views/shared/icons/_icon_status_success.svg2
-rw-r--r--app/views/shared/icons/_mr_bold.svg3
-rw-r--r--app/views/shared/icons/_plus_square.svg1
-rw-r--r--app/views/shared/icons/_todo_done.svg1
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml3
-rw-r--r--app/views/shared/issuable/_close_reopen_report_toggle.html.haml22
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml4
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/workers/create_gpg_signature_worker.rb6
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
-rw-r--r--changelogs/unreleased/19650-remove-admin-section-from-search-results-if-user-doesnt-have-access.yml5
-rw-r--r--changelogs/unreleased/35010-projects-nav-dropdown.yml5
-rw-r--r--changelogs/unreleased/35010-remove-goto-project-from-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/36821-fix-new-nav-wrapping-caret-and-increasing-height.yml5
-rw-r--r--changelogs/unreleased/36859-update-gpg-docs-with-gpg2.yml5
-rw-r--r--changelogs/unreleased/36860-migrate-issues-author.yml5
-rw-r--r--changelogs/unreleased/36994-toggle-for-automatically-collapsing-outdated-diff-comments.yml5
-rw-r--r--changelogs/unreleased/37204-deprecate-git-user-manual-ssh-config.yml5
-rw-r--r--changelogs/unreleased/37331-button-MR-widget.yml5
-rw-r--r--changelogs/unreleased/37406-success-status-icon.yml5
-rw-r--r--changelogs/unreleased/additional-time-series-charts.yml5
-rw-r--r--changelogs/unreleased/api-gpg-key-management.yml5
-rw-r--r--changelogs/unreleased/api_branches_head.yml5
-rw-r--r--changelogs/unreleased/dont-remove-add-diff-btn-on-post.yml5
-rw-r--r--changelogs/unreleased/feature-dependency-status-badge.yml5
-rw-r--r--changelogs/unreleased/feature-gpg-verification-status.yml6
-rw-r--r--changelogs/unreleased/feature-sm-34518-extend-api-pipeline-schedule-variable-new.yml5
-rw-r--r--changelogs/unreleased/feature-sm-37239-implement-failure_reason-on-ci_builds.yml5
-rw-r--r--changelogs/unreleased/fix-import-export-performance.yml5
-rw-r--r--changelogs/unreleased/fuzzy-issue-search.yml5
-rw-r--r--changelogs/unreleased/issue-api-my-reaction.yml5
-rw-r--r--changelogs/unreleased/mr-index-page-performance.yml5
-rw-r--r--changelogs/unreleased/sh-bump-jira-gem.yml5
-rw-r--r--config/dependency_decisions.yml6
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/webpack.config.js2
-rw-r--r--db/migrate/20170817123339_add_verification_status_to_gpg_signatures.rb20
-rw-r--r--db/migrate/20170825104051_migrate_issues_to_ghost_user.rb36
-rw-r--r--db/migrate/20170825154015_resolve_outdated_diff_discussions.rb9
-rw-r--r--db/migrate/20170830125940_add_failure_reason_to_ci_builds.rb9
-rw-r--r--db/migrate/20170901071411_add_foreign_key_to_issue_author.rb14
-rw-r--r--db/migrate/20170905112933_add_resolved_by_push_to_notes.rb9
-rw-r--r--db/post_migrate/20170830084744_destroy_gpg_signatures.rb10
-rw-r--r--db/post_migrate/20170831195038_remove_valid_signature_from_gpg_signatures.rb11
-rw-r--r--db/schema.rb8
-rw-r--r--doc/README.md1
-rw-r--r--doc/administration/integration/koding.md6
-rw-r--r--doc/api/README.md15
-rw-r--r--doc/api/environments.md2
-rw-r--r--doc/api/issues.md95
-rw-r--r--doc/api/merge_requests.md61
-rw-r--r--doc/api/pipeline_schedules.md91
-rw-r--r--doc/api/project_snippets.md3
-rw-r--r--doc/api/projects.md239
-rw-r--r--doc/api/users.md224
-rw-r--r--doc/ci/environments.md4
-rw-r--r--doc/ci/runners/README.md19
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/fe_guide/vue.md291
-rw-r--r--doc/development/licensing.md3
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/ssh/README.md32
-rw-r--r--doc/user/discussions/img/automatically_resolve_outdated_discussions.pngbin0 -> 117604 bytes
-rw-r--r--doc/user/discussions/index.md18
-rw-r--r--doc/user/permissions.md45
-rw-r--r--doc/user/project/import/cvs.md68
-rw-r--r--doc/user/project/import/index.md1
-rw-r--r--doc/user/project/index.md2
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/confidential_issues_system_notes.pngbin2330 -> 4214 bytes
-rw-r--r--doc/user/project/koding.md5
-rw-r--r--doc/user/project/pipelines/settings.md28
-rw-r--r--doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.pngbin41193 -> 113801 bytes
-rw-r--r--doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.pngbin9542 -> 12924 bytes
-rw-r--r--doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.pngbin14029 -> 20652 bytes
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md13
-rw-r--r--doc/user/search/img/issue_search_by_term.pngbin0 -> 127492 bytes
-rw-r--r--doc/user/search/index.md14
-rw-r--r--features/steps/explore/projects.rb4
-rw-r--r--features/steps/project/redirects.rb2
-rw-r--r--features/support/gitaly.rb3
-rw-r--r--lib/api/branches.rb25
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/helpers/internal_helpers.rb9
-rw-r--r--lib/api/internal.rb11
-rw-r--r--lib/api/issues.rb1
-rw-r--r--lib/api/merge_requests.rb1
-rw-r--r--lib/api/pipeline_schedules.rb85
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/runner.rb4
-rw-r--r--lib/api/users.rb150
-rw-r--r--lib/api/v3/entities.rb1
-rw-r--r--lib/api/v3/projects.rb7
-rw-r--r--lib/api/v3/triggers.rb32
-rw-r--r--lib/gitlab/git/repository.rb61
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb14
-rw-r--r--lib/gitlab/gpg.rb2
-rw-r--r--lib/gitlab/gpg/commit.rb34
-rw-r--r--lib/gitlab/gpg/invalid_gpg_signature_updater.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb79
-rw-r--r--lib/gitlab/import_export/shared.rb2
-rw-r--r--lib/gitlab/issuables_count_for_state.rb50
-rw-r--r--lib/gitlab/sql/pattern.rb23
-rw-r--r--lib/system_check/app/git_user_default_ssh_config_check.rb69
-rw-r--r--lib/system_check/app/init_script_up_to_date_check.rb28
-rw-r--r--lib/system_check/base_check.rb19
-rw-r--r--lib/system_check/incoming_email/foreman_configured_check.rb23
-rw-r--r--lib/system_check/incoming_email/imap_authentication_check.rb45
-rw-r--r--lib/system_check/incoming_email/initd_configured_check.rb32
-rw-r--r--lib/system_check/incoming_email/mail_room_running_check.rb43
-rw-r--r--lib/system_check/simple_executor.rb4
-rw-r--r--lib/tasks/gettext.rake3
-rw-r--r--lib/tasks/gitlab/check.rake126
-rw-r--r--locale/bg/gitlab.po191
-rw-r--r--locale/de/gitlab.po895
-rw-r--r--locale/en/gitlab.po48
-rw-r--r--locale/eo/gitlab.po191
-rw-r--r--locale/es/gitlab.po191
-rw-r--r--locale/fr/gitlab.po191
-rw-r--r--locale/gitlab.pot172
-rw-r--r--locale/it/gitlab.po191
-rw-r--r--locale/ja/gitlab.po191
-rw-r--r--locale/ko/gitlab.po279
-rw-r--r--locale/pt_BR/gitlab.po191
-rw-r--r--locale/ru/gitlab.po207
-rw-r--r--locale/uk/gitlab.po191
-rw-r--r--locale/zh_CN/gitlab.po285
-rw-r--r--locale/zh_HK/gitlab.po285
-rw-r--r--locale/zh_TW/gitlab.po287
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb82
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/ci/pipeline_variables.rb (renamed from spec/factories/ci/pipeline_variable_variables.rb)0
-rw-r--r--spec/factories/ci/trigger_requests.rb9
-rw-r--r--spec/factories/gpg_signature.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb4
-rw-r--r--spec/features/boards/boards_spec.rb18
-rw-r--r--spec/features/commits_spec.rb101
-rw-r--r--spec/features/issues/form_spec.rb6
-rw-r--r--spec/features/issues/issue_detail_spec.rb14
-rw-r--r--spec/features/merge_requests/form_spec.rb7
-rw-r--r--spec/features/merge_requests/resolve_outdated_diff_discussions.rb78
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb10
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb4
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb14
-rw-r--r--spec/features/projects/jobs_spec.rb40
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb3
-rw-r--r--spec/features/projects_spec.rb43
-rw-r--r--spec/features/signed_commits_spec.rb179
-rw-r--r--spec/finders/issues_finder_spec.rb18
-rw-r--r--spec/finders/merge_requests_finder_spec.rb14
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json4
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule_variable.json8
-rw-r--r--spec/helpers/groups_helper_spec.rb3
-rw-r--r--spec/helpers/notes_helper_spec.rb20
-rw-r--r--spec/helpers/search_helper_spec.rb20
-rw-r--r--spec/javascripts/api_spec.js6
-rw-r--r--spec/javascripts/gl_dropdown_spec.js323
-rw-r--r--spec/javascripts/monitoring/graph/flag_spec.js4
-rw-r--r--spec/javascripts/monitoring/graph/legend_spec.js122
-rw-r--r--spec/javascripts/monitoring/graph_row_spec.js62
-rw-r--r--spec/javascripts/monitoring/graph_spec.js34
-rw-r--r--spec/javascripts/monitoring/mock_data.js7580
-rw-r--r--spec/javascripts/monitoring/monitoring_paths_spec.js34
-rw-r--r--spec/javascripts/monitoring/monitoring_store_spec.js4
-rw-r--r--spec/javascripts/monitoring/utils/multiple_time_series_spec.js21
-rw-r--r--spec/javascripts/project_title_spec.js59
-rw-r--r--spec/javascripts/projects_dropdown/components/app_spec.js348
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js72
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_item_spec.js65
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_search_spec.js84
-rw-r--r--spec/javascripts/projects_dropdown/components/search_spec.js101
-rw-r--r--spec/javascripts/projects_dropdown/mock_data.js96
-rw-r--r--spec/javascripts/projects_dropdown/service/projects_service_spec.js179
-rw-r--r--spec/javascripts/projects_dropdown/store/projects_store_spec.js41
-rw-r--r--spec/javascripts/vue_shared/components/identicon_spec.js28
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb38
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb232
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb43
-rw-r--r--spec/lib/gitlab/gpg_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb19
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml4
-rw-r--r--spec/lib/gitlab/issuables_count_for_state_spec.rb37
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb120
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb79
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb24
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb51
-rw-r--r--spec/models/ci/build_spec.rb28
-rw-r--r--spec/models/ci/trigger_request_spec.rb17
-rw-r--r--spec/models/commit_status_spec.rb21
-rw-r--r--spec/models/concerns/issuable_spec.rb42
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb16
-rw-r--r--spec/models/gpg_key_spec.rb38
-rw-r--r--spec/models/group_spec.rb19
-rw-r--r--spec/models/member_spec.rb9
-rw-r--r--spec/models/merge_request_spec.rb38
-rw-r--r--spec/models/project_spec.rb19
-rw-r--r--spec/models/user_spec.rb14
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb34
-rw-r--r--spec/requests/api/branches_spec.rb16
-rw-r--r--spec/requests/api/commit_statuses_spec.rb3
-rw-r--r--spec/requests/api/internal_spec.rb31
-rw-r--r--spec/requests/api/issues_spec.rb10
-rw-r--r--spec/requests/api/merge_requests_spec.rb12
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb160
-rw-r--r--spec/requests/api/projects_spec.rb58
-rw-r--r--spec/requests/api/runner_spec.rb56
-rw-r--r--spec/requests/api/users_spec.rb326
-rw-r--r--spec/requests/api/v3/projects_spec.rb1
-rw-r--r--spec/requests/api/v3/triggers_spec.rb5
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb52
-rw-r--r--spec/services/ci/retry_build_service_spec.rb2
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb62
-rw-r--r--spec/services/projects/update_pages_service_spec.rb1
-rw-r--r--spec/support/group_members_shared_example.rb27
-rw-r--r--spec/support/test_env.rb20
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb (renamed from spec/views/layouts/nav/_project.html.haml_spec.rb)6
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb16
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb9
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb22
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore2
-rw-r--r--vendor/gitignore/Haskell.gitignore1
-rw-r--r--vendor/gitignore/Prestashop.gitignore4
-rw-r--r--vendor/gitignore/Smalltalk.gitignore4
-rw-r--r--vendor/gitignore/Symfony.gitignore3
-rw-r--r--vendor/gitignore/VisualStudio.gitignore2
-rw-r--r--vendor/gitlab-ci-yml/Go.gitlab-ci.yml32
-rw-r--r--vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml43
-rw-r--r--vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml4
-rw-r--r--vendor/gitlab-ci-yml/PHP.gitlab-ci.yml3
-rw-r--r--vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml8
458 files changed, 16678 insertions, 5631 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aa7659d1b41..b76c8f00d77 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -43,6 +43,7 @@ stages:
# Predefined scopes
.dedicated-runner: &dedicated-runner
+ retry: 1
tags:
- gitlab-org
@@ -208,11 +209,10 @@ update-tests-metadata:
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- - rm -f rspec_flaky/${CI_PROJECT_NAME}/all_node_*.json
+ - rm -f rspec_flaky/${CI_PROJECT_NAME}/*_node_*.json
flaky-examples-check:
<<: *dedicated-runner
- <<: *except-docs
image: ruby:2.3-alpine
services: []
before_script: []
@@ -227,6 +227,7 @@ flaky-examples-check:
- branches
except:
- master
+ - /(^docs[\/-].*|.*-docs$)/
artifacts:
expire_in: 30d
paths:
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 93d4c1ef06f..0f1a7dfc7c4 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.36.0
+0.37.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 11d9efa3d5a..b3d91f9cfc0 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.8.0
+5.9.0
diff --git a/Gemfile b/Gemfile
index 61c941ae449..0341f2609ad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -181,7 +181,7 @@ gem 'connection_pool', '~> 2.0'
gem 'hipchat', '~> 1.5.0'
# JIRA integration
-gem 'jira-ruby', '~> 1.1.2'
+gem 'jira-ruby', '~> 1.4'
# Flowdock integration
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
@@ -397,7 +397,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.31.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.32.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index cba30e856ed..320d42b8974 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -275,7 +275,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.31.0)
+ gitaly-proto (0.32.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -404,8 +404,9 @@ GEM
cause
json
ipaddress (0.8.3)
- jira-ruby (1.1.2)
+ jira-ruby (1.4.1)
activesupport
+ multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
jquery-rails (4.1.1)
@@ -1020,7 +1021,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.31.0)
+ gitaly-proto (~> 0.32.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
@@ -1042,7 +1043,7 @@ DEPENDENCIES
html2text
httparty (~> 0.13.3)
influxdb (~> 0.2)
- jira-ruby (~> 1.1.2)
+ jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0)
json-schema (~> 2.6.2)
diff --git a/PROCESS.md b/PROCESS.md
index 538e4389e00..ed4e84dd0b6 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -199,26 +199,8 @@ available in the package repositories.
## Release retrospective and kickoff
-### Retrospective
-
-After each release, we have a retrospective call where we discuss what went well,
-what went wrong, and what we can improve for the next release. The
-[retrospective notes] are public and you are invited to comment on them.
-If you're interested, you can even join the
-[retrospective call][retro-kickoff-call], on the first working day after the
-22nd at 6pm CET / 9am PST.
-
-### Kickoff
-
-Before working on the next release, we have a
-kickoff call to explain what we expect to ship in the next release. The
-[kickoff notes] are public and you are invited to comment on them.
-If you're interested, you can even join the [kickoff call][retro-kickoff-call],
-on the first working day after the 7th at 6pm CET / 9am PST..
-
-[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
-[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
-[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
+- [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
+- [Kickoff](https://about.gitlab.com/handbook/engineering/workflow/#kickoff)
## Copy & paste responses
diff --git a/README.md b/README.md
index 9309922ae39..9ead6d51c5d 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines)
+[![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.svg)](https://gemnasium.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
[![Gitter](https://badges.gitter.im/gitlabhq/gitlabhq.svg)](https://gitter.im/gitlabhq/gitlabhq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 78cb3def879..8acddd6194c 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -5,7 +5,7 @@ const Api = {
groupPath: '/api/:version/groups/:id.json',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
- projectsPath: '/api/:version/projects.json?simple=true',
+ projectsPath: '/api/:version/projects.json',
labelsPath: '/:namespace_path/:project_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
@@ -58,6 +58,7 @@ const Api = {
const defaults = {
search: query,
per_page: 20,
+ simple: true,
};
if (gon.current_user_id) {
diff --git a/app/assets/javascripts/breadcrumb.js b/app/assets/javascripts/breadcrumb.js
new file mode 100644
index 00000000000..10fbcfe96cf
--- /dev/null
+++ b/app/assets/javascripts/breadcrumb.js
@@ -0,0 +1,28 @@
+export const addTooltipToEl = (el) => {
+ const textEl = el.querySelector('.js-breadcrumb-item-text');
+
+ if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
+ el.setAttribute('title', el.textContent);
+ el.setAttribute('data-container', 'body');
+ el.classList.add('has-tooltip');
+ }
+};
+
+export default () => {
+ const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
+
+ if (breadcrumbs) {
+ const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown'))
+ .map(el => el.querySelector('a'))
+ .filter(el => el);
+ const $expander = $('.js-breadcrumbs-collapsed-expander');
+
+ topLevelLinks.forEach(el => addTooltipToEl(el));
+
+ $expander.closest('.dropdown')
+ .on('show.bs.dropdown hide.bs.dropdown', (e) => {
+ $('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open')
+ .tooltip('hide');
+ });
+ }
+};
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 6db8b3afbef..768453b28f1 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -2,3 +2,4 @@ import 'underscore';
import './polyfills';
import './jquery';
import './bootstrap';
+import './vue';
diff --git a/app/assets/javascripts/vue_shared/common_vue.js b/app/assets/javascripts/commons/vue.js
index eb2a6071fda..8b62d78c043 100644
--- a/app/assets/javascripts/vue_shared/common_vue.js
+++ b/app/assets/javascripts/commons/vue.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import './vue_resource_interceptor';
if (process.env.NODE_ENV !== 'production') {
Vue.config.productionTip = false;
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index c37249c060a..06ce84d7599 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -21,11 +21,13 @@ const DiffNoteAvatars = Vue.extend({
},
template: `
<div class="diff-comment-avatar-holders"
+ :class="discussionClassName"
v-show="notesCount !== 0">
<div v-if="!isVisible">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image
v-for="note in notesSubset"
+ :key="note.id"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="clickedAvatar($event)"
:img-src="note.authorAvatar"
@@ -68,7 +70,8 @@ const DiffNoteAvatars = Vue.extend({
});
});
},
- destroyed() {
+ beforeDestroy() {
+ this.addNoCommentClass();
$(document).off('toggle.comments');
},
watch: {
@@ -85,6 +88,9 @@ const DiffNoteAvatars = Vue.extend({
},
},
computed: {
+ discussionClassName() {
+ return `js-diff-avatars-${this.discussionId}`;
+ },
notesSubset() {
let notes = [];
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 5decfc1dc01..0863c3406bd 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -32,6 +32,10 @@ $(() => {
const tmpApp = new tmp().$mount();
$(this).replaceWith(tmpApp.$el);
+ $(tmpApp.$el).one('remove.vue', () => {
+ tmpApp.$destroy();
+ tmpApp.$el.remove();
+ });
});
const $components = $(COMPONENT_SELECTOR).filter(function () {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 3dec4de06ec..0445dd4e6ea 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -41,7 +41,6 @@ import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
-import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit';
@@ -554,9 +553,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'root':
new UserCallout();
break;
- case 'groups':
- new GroupName();
- break;
case 'profiles':
new NotificationsForm();
new NotificationsDropdown();
@@ -564,7 +560,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects':
new Project();
new ProjectAvatar();
- new GroupName();
switch (path[1]) {
case 'compare':
new CompareAutocomplete();
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 1c5ca1d3cf9..23040cd9eb8 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -61,7 +61,7 @@ class DropdownHint extends gl.FilteredSearchDropdown {
.map(tokenKey => ({
icon: `fa-${tokenKey.icon}`,
hint: tokenKey.key,
- tag: `<${tokenKey.tag}>`,
+ tag: `:${tokenKey.tag}`,
type: tokenKey.type,
}));
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index d65bbc0d808..6f7671aa6fe 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -637,11 +637,15 @@ GitLabDropdown = (function() {
value = this.options.id ? this.options.id(data) : data.id;
fieldName = this.options.fieldName;
- if (value) { value = value.toString().replace(/'/g, '\\\''); }
-
- field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
- if (field.length) {
- selected = true;
+ if (value) {
+ value = value.toString().replace(/'/g, '\\\'');
+ field = this.dropdown.parent().find(`input[name='${fieldName}'][value='${value}']`);
+ if (field.length) {
+ selected = true;
+ }
+ } else {
+ field = this.dropdown.parent().find(`input[name='${fieldName}']`);
+ selected = !field.length;
}
}
// Set URL
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
deleted file mode 100644
index 3e483b69fd2..00000000000
--- a/app/assets/javascripts/group_name.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import Cookies from 'js-cookie';
-import _ from 'underscore';
-
-export default class GroupName {
- constructor() {
- this.titleContainer = document.querySelector('.js-title-container');
- this.title = this.titleContainer.querySelector('.title');
-
- if (this.title) {
- this.titleWidth = this.title.offsetWidth;
- this.groupTitle = this.titleContainer.querySelector('.group-title');
- this.groups = this.titleContainer.querySelectorAll('.group-path');
- this.toggle = null;
- this.isHidden = false;
- this.init();
- }
- }
-
- init() {
- if (this.groups.length > 0) {
- this.groups[this.groups.length - 1].classList.remove('hidable');
- this.toggleHandler();
- window.addEventListener('resize', _.debounce(this.toggleHandler.bind(this), 100));
- }
- this.render();
- }
-
- toggleHandler() {
- if (this.titleWidth > this.titleContainer.offsetWidth) {
- if (!this.toggle) this.createToggle();
- this.showToggle();
- } else if (this.toggle) {
- this.hideToggle();
- }
- }
-
- createToggle() {
- this.toggle = document.createElement('button');
- this.toggle.setAttribute('type', 'button');
- this.toggle.className = 'text-expander group-name-toggle';
- this.toggle.setAttribute('aria-label', 'Toggle full path');
- if (Cookies.get('new_nav') === 'true') {
- this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
- } else {
- this.toggle.innerHTML = '...';
- }
- this.toggle.addEventListener('click', this.toggleGroups.bind(this));
- if (Cookies.get('new_nav') === 'true') {
- this.title.insertBefore(this.toggle, this.groupTitle);
- } else {
- this.titleContainer.insertBefore(this.toggle, this.title);
- }
- this.toggleGroups();
- }
-
- showToggle() {
- this.title.classList.add('wrap');
- this.toggle.classList.remove('hidden');
- if (this.isHidden) this.groupTitle.classList.add('hidden');
- }
-
- hideToggle() {
- this.title.classList.remove('wrap');
- this.toggle.classList.add('hidden');
- if (this.isHidden) this.groupTitle.classList.remove('hidden');
- }
-
- toggleGroups() {
- this.isHidden = !this.isHidden;
- this.groupTitle.classList.toggle('hidden');
- }
-
- render() {
- this.title.classList.remove('initializing');
- }
-}
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index d314f3c4d43..0e8a0519928 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -5,7 +5,6 @@
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
-import SidebarHeightManager from './sidebar_height_manager';
const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content';
@@ -50,13 +49,6 @@ export default class IssuableBulkUpdateSidebar {
new SubscriptionSelect();
}
- getNavHeight() {
- const navbarHeight = $('.navbar-gitlab').outerHeight();
- const layoutNavHeight = $('.layout-nav').outerHeight();
- const subNavScroll = $('.sub-nav-scroll').outerHeight();
- return navbarHeight + layoutNavHeight + subNavScroll;
- }
-
setupBulkUpdateActions() {
IssuableBulkUpdateActions.setOriginalDropdownData();
}
@@ -84,23 +76,6 @@ export default class IssuableBulkUpdateSidebar {
this.toggleBulkEditButtonDisabled(enable);
this.toggleOtherFiltersDisabled(enable);
this.toggleCheckboxDisplay(enable);
-
- if (enable) {
- this.initAffix();
- SidebarHeightManager.init();
- }
- }
-
- initAffix() {
- if (!this.$sidebar.hasClass('affix-top')) {
- const offsetTop = $('.scrolling-tabs-container').outerHeight() + $('.sub-nav-scroll').outerHeight();
-
- this.$sidebar.affix({
- offset: {
- top: offsetTop,
- },
- });
- }
}
updateSelectedIssuableIds() {
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 5c1ba416a03..d064a2c0024 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -50,19 +50,10 @@ import initFlyOutNav from './fly_out_nav';
});
});
- function applyScrollNavClass() {
- const scrollOpacityHeight = 40;
- $('.navbar-border').css('opacity', Math.min($(window).scrollTop() / scrollOpacityHeight, 1));
- }
-
$(() => {
- if (Cookies.get('new_nav') === 'true') {
- const newNavSidebar = new NewNavSidebar();
- newNavSidebar.bindEvents();
-
- initFlyOutNav();
- }
+ const newNavSidebar = new NewNavSidebar();
+ newNavSidebar.bindEvents();
- $(window).on('scroll', _.throttle(applyScrollNavClass, 100));
+ initFlyOutNav();
});
}).call(window);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 69a6e131b59..0bc31a56684 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -132,6 +132,7 @@ import './project_new';
import './project_select';
import './project_show';
import './project_variables';
+import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
import './render_math';
@@ -143,6 +144,7 @@ import './smart_interval';
import './star';
import './subscription';
import './subscription_select';
+import initBreadcrumbs from './breadcrumb';
import './dispatcher';
@@ -180,6 +182,8 @@ $(function () {
var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize;
+ initBreadcrumbs();
+
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
@@ -249,7 +253,10 @@ $(function () {
// Initialize popovers
$body.popover({
selector: '[data-toggle="popover"]',
- trigger: 'focus'
+ trigger: 'focus',
+ // set the viewport to the main content, excluding the navigation bar, so
+ // the navigation can't overlap the popover
+ viewport: '.page-with-sidebar'
});
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 74244faa5d9..b596c4f383f 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -4,7 +4,7 @@
import statusCodes from '../../lib/utils/http_status';
import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue';
- import GraphRow from './graph_row.vue';
+ import Graph from './graph.vue';
import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub';
@@ -32,8 +32,8 @@
},
components: {
+ Graph,
GraphGroup,
- GraphRow,
EmptyState,
},
@@ -127,10 +127,10 @@
:key="index"
:name="groupData.group"
>
- <graph-row
- v-for="(row, index) in groupData.metrics"
+ <graph
+ v-for="(graphData, index) in groupData.metrics"
:key="index"
- :row-data="row"
+ :graph-data="graphData"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
/>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 6f6da9e1463..cde2ff7ca2a 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -3,11 +3,12 @@
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
+ import monitoringPaths from './monitoring_paths.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
- import { formatRelevantDigits } from '../../lib/utils/number_utils';
import { timeScaleFormat } from '../utils/date_time_formatters';
+ import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left;
@@ -18,10 +19,6 @@
type: Object,
required: true,
},
- classType: {
- type: String,
- required: true,
- },
updateAspectRatio: {
type: Boolean,
required: true,
@@ -36,32 +33,29 @@
data() {
return {
+ baseGraphHeight: 450,
+ baseGraphWidth: 600,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
- xScale: {},
- yScale: {},
margin: {},
- data: [],
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
- area: '',
- line: '',
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
},
- currentYCoordinate: 0,
+ currentDataIndex: 0,
currentXCoordinate: 0,
currentFlagPosition: 0,
- metricUsage: '',
showFlag: false,
showDeployInfo: true,
+ timeSeries: [],
};
},
@@ -69,16 +63,17 @@
GraphLegend,
GraphFlag,
GraphDeployment,
+ monitoringPaths,
},
computed: {
outterViewBox() {
- return `0 0 ${this.graphWidth} ${this.graphHeight}`;
+ return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
},
innerViewBox() {
- if ((this.graphWidth - 150) > 0) {
- return `0 0 ${this.graphWidth - 150} ${this.graphHeight}`;
+ if ((this.baseGraphWidth - 150) > 0) {
+ return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
}
return '0 0 0 0';
},
@@ -89,7 +84,7 @@
paddingBottomRootSvg() {
return {
- paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
+ paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
};
},
},
@@ -104,17 +99,16 @@
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
- this.data = query.result[0].values;
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
- if (this.data !== undefined) {
- this.renderAxesPaths();
- this.formatDeployments();
- }
+ this.baseGraphHeight = this.graphHeight;
+ this.baseGraphWidth = this.graphWidth;
+ this.renderAxesPaths();
+ this.formatDeployments();
},
handleMouseOverGraph(e) {
@@ -123,16 +117,17 @@
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x = point.x += 7;
- const timeValueOverlay = this.xScale.invert(point.x);
- const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
- const d0 = this.data[overlayIndex - 1];
- const d1 = this.data[overlayIndex];
+ const firstTimeSeries = this.timeSeries[0];
+ const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
+ const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
+ const d0 = firstTimeSeries.values[overlayIndex - 1];
+ const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
this.currentData = evalTime ? d1 : d0;
- this.currentXCoordinate = Math.floor(this.xScale(this.currentData.time));
+ this.currentDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
+ this.currentXCoordinate = Math.floor(firstTimeSeries.timeSeriesScaleX(this.currentData.time));
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
- this.currentYCoordinate = this.yScale(this.currentData.value);
if (this.currentXCoordinate > (this.graphWidth - 200)) {
this.currentFlagPosition = this.currentXCoordinate - 103;
@@ -145,17 +140,25 @@
} else {
this.showFlag = true;
}
-
- this.metricUsage = `${formatRelevantDigits(this.currentData.value)} ${this.unitOfDisplay}`;
},
renderAxesPaths() {
+ this.timeSeries = createTimeSeries(this.graphData.queries[0].result,
+ this.graphWidth,
+ this.graphHeight,
+ this.graphHeightOffset);
+
+ if (this.timeSeries.length > 3) {
+ this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
+ }
+
const axisXScale = d3.time.scale()
.range([0, this.graphWidth]);
- this.yScale = d3.scale.linear()
+ const axisYScale = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
- axisXScale.domain(d3.extent(this.data, d => d.time));
- this.yScale.domain([0, d3.max(this.data.map(d => d.value))]);
+
+ axisXScale.domain(d3.extent(this.timeSeries[0].values, d => d.time));
+ axisYScale.domain([0, d3.max(this.timeSeries[0].values.map(d => d.value))]);
const xAxis = d3.svg.axis()
.scale(axisXScale)
@@ -164,7 +167,7 @@
.orient('bottom');
const yAxis = d3.svg.axis()
- .scale(this.yScale)
+ .scale(axisYScale)
.ticks(measurements.yTicks)
.orient('left');
@@ -180,25 +183,6 @@
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
-
- this.xScale = d3.time.scale()
- .range([0, this.graphWidth - 70]);
-
- this.xScale.domain(d3.extent(this.data, d => d.time));
-
- const areaFunction = d3.svg.area()
- .x(d => this.xScale(d.time))
- .y0(this.graphHeight - this.graphHeightOffset)
- .y1(d => this.yScale(d.value))
- .interpolate('linear');
-
- const lineFunction = d3.svg.line()
- .x(d => this.xScale(d.time))
- .y(d => this.yScale(d.value));
-
- this.line = lineFunction(this.data);
-
- this.area = areaFunction(this.data);
},
},
@@ -219,12 +203,11 @@
},
};
</script>
+
<template>
- <div
- :class="classType">
- <h5
- class="text-center graph-title">
- {{graphData.title}}
+ <div class="prometheus-graph">
+ <h5 class="text-center graph-title">
+ {{graphData.title}}
</h5>
<div
class="prometheus-svg-container"
@@ -245,30 +228,25 @@
:graph-height="graphHeight"
:margin="margin"
:measurements="measurements"
- :area-color-rgb="areaColorRgb"
:legend-title="legendTitle"
:y-axis-label="yAxisLabel"
- :metric-usage="metricUsage"
+ :time-series="timeSeries"
+ :unit-of-display="unitOfDisplay"
+ :current-data-index="currentDataIndex"
/>
<svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
- <path
- class="metric-area"
- :d="area"
- :fill="areaColorRgb"
- transform="translate(-5, 20)">
- </path>
- <path
- class="metric-line"
- :d="line"
- :stroke="lineColorRgb"
- fill="none"
- stroke-width="2"
- transform="translate(-5, 20)">
- </path>
- <graph-deployment
+ <monitoring-paths
+ v-for="(path, index) in timeSeries"
+ :key="index"
+ :generated-line-path="path.linePath"
+ :generated-area-path="path.areaPath"
+ :line-color="path.lineColor"
+ :area-color="path.areaColor"
+ />
+ <monitoring-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
@@ -277,7 +255,6 @@
<graph-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
- :current-y-coordinate="currentYCoordinate"
:current-data="currentData"
:current-flag-position="currentFlagPosition"
:graph-height="graphHeight"
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index c4d4647d240..a98e3d06c18 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -7,10 +7,6 @@
type: Number,
required: true,
},
- currentYCoordinate: {
- type: Number,
- required: true,
- },
currentFlagPosition: {
type: Number,
required: true,
@@ -60,16 +56,7 @@
:y2="calculatedHeight"
transform="translate(-5, 20)">
</line>
- <circle
- class="circle-metric"
- :fill="circleColorRgb"
- stroke="#000"
- :cx="currentXCoordinate"
- :cy="currentYCoordinate"
- r="5"
- transform="translate(-5, 20)">
- </circle>
- <svg
+ <svg
class="rect-text-metric"
:x="currentFlagPosition"
y="0">
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index d08f9cbffd4..a43dad8e601 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -1,4 +1,6 @@
<script>
+ import { formatRelevantDigits } from '../../../lib/utils/number_utils';
+
export default {
props: {
graphWidth: {
@@ -17,10 +19,6 @@
type: Object,
required: true,
},
- areaColorRgb: {
- type: String,
- required: true,
- },
legendTitle: {
type: String,
required: true,
@@ -29,15 +27,25 @@
type: String,
required: true,
},
- metricUsage: {
+ timeSeries: {
+ type: Array,
+ required: true,
+ },
+ unitOfDisplay: {
type: String,
required: true,
},
+ currentDataIndex: {
+ type: Number,
+ required: true,
+ },
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
+ seriesXPosition: 0,
+ metricUsageXPosition: 0,
};
},
computed: {
@@ -63,10 +71,28 @@
yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
},
+
+ },
+ methods: {
+ translateLegendGroup(index) {
+ return `translate(0, ${12 * (index)})`;
+ },
+
+ formatMetricUsage(series) {
+ return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`;
+ },
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
+ this.metricUsageXPosition = 0;
+ this.seriesXPosition = 0;
+ if (this.$refs.legendTitleSvg != null) {
+ this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
+ }
+ if (this.$refs.seriesTitleSvg != null) {
+ this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
+ }
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
@@ -121,24 +147,33 @@
dy=".35em">
Time
</text>
- <rect
- :fill="areaColorRgb"
- :width="measurements.legends.width"
- :height="measurements.legends.height"
- x="20"
- :y="graphHeight - measurements.legendOffset">
- </rect>
- <text
- class="text-metric-title"
- x="50"
- :y="graphHeight - 25">
- {{legendTitle}}
- </text>
- <text
- class="text-metric-usage"
- x="50"
- :y="graphHeight - 10">
- {{metricUsage}}
- </text>
+ <g class="legend-group"
+ v-for="(series, index) in timeSeries"
+ :key="index"
+ :transform="translateLegendGroup(index)">
+ <rect
+ :fill="series.areaColor"
+ :width="measurements.legends.width"
+ :height="measurements.legends.height"
+ x="20"
+ :y="graphHeight - measurements.legendOffset">
+ </rect>
+ <text
+ v-if="timeSeries.length > 1"
+ class="legend-metric-title"
+ ref="legendTitleSvg"
+ x="38"
+ :y="graphHeight - 30">
+ {{legendTitle}} Series {{index + 1}} {{formatMetricUsage(series)}}
+ </text>
+ <text
+ v-else
+ class="legend-metric-title"
+ ref="legendTitleSvg"
+ x="38"
+ :y="graphHeight - 30">
+ {{legendTitle}} {{formatMetricUsage(series)}}
+ </text>
+ </g>
</g>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index 32c90fda8cc..958f537d31b 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -14,7 +14,7 @@ export default {
<div class="panel-heading">
<h4>{{name}}</h4>
</div>
- <div class="panel-body">
+ <div class="panel-body prometheus-graph-group">
<slot />
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph_row.vue b/app/assets/javascripts/monitoring/components/graph_row.vue
deleted file mode 100644
index bdb9149c3b4..00000000000
--- a/app/assets/javascripts/monitoring/components/graph_row.vue
+++ /dev/null
@@ -1,41 +0,0 @@
-<script>
- import Graph from './graph.vue';
-
- export default {
- props: {
- rowData: {
- type: Array,
- required: true,
- },
- updateAspectRatio: {
- type: Boolean,
- required: true,
- },
- deploymentData: {
- type: Array,
- required: true,
- },
- },
- components: {
- Graph,
- },
- computed: {
- bootstrapClass() {
- return this.rowData.length >= 2 ? 'col-md-6' : 'col-md-12';
- },
- },
- };
-</script>
-
-<template>
- <div class="prometheus-row row">
- <graph
- v-for="(graphData, index) in rowData"
- :graph-data="graphData"
- :class-type="bootstrapClass"
- :key="index"
- :update-aspect-ratio="updateAspectRatio"
- :deployment-data="deploymentData"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_paths.vue b/app/assets/javascripts/monitoring/components/monitoring_paths.vue
new file mode 100644
index 00000000000..043f1bf66bb
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/monitoring_paths.vue
@@ -0,0 +1,40 @@
+<script>
+ export default {
+ props: {
+ generatedLinePath: {
+ type: String,
+ required: true,
+ },
+ generatedAreaPath: {
+ type: String,
+ required: true,
+ },
+ lineColor: {
+ type: String,
+ required: true,
+ },
+ areaColor: {
+ type: String,
+ required: true,
+ },
+ },
+ };
+</script>
+<template>
+ <g>
+ <path
+ class="metric-area"
+ :d="generatedAreaPath"
+ :fill="areaColor"
+ transform="translate(-5, 20)">
+ </path>
+ <path
+ class="metric-line"
+ :d="generatedLinePath"
+ :stroke="lineColor"
+ fill="none"
+ stroke-width="1"
+ transform="translate(-5, 20)">
+ </path>
+ </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 8e62fa63f13..345a0b37a76 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -21,9 +21,9 @@ const mixins = {
formatDeployments() {
this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
- const xPos = Math.floor(this.xScale(time));
+ const xPos = Math.floor(this.timeSeries[0].timeSeriesScaleX(time));
- time.setSeconds(this.data[0].time.getSeconds());
+ time.setSeconds(this.timeSeries[0].values[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 737c964f12e..7592af5878e 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -1,46 +1,36 @@
import _ from 'underscore';
-class MonitoringStore {
+function sortMetrics(metrics) {
+ return _.chain(metrics).sortBy('weight').sortBy('title').value();
+}
+
+function normalizeMetrics(metrics) {
+ return metrics.map(metric => ({
+ ...metric,
+ queries: metric.queries.map(query => ({
+ ...query,
+ result: query.result.map(result => ({
+ ...result,
+ values: result.values.map(([timestamp, value]) => ({
+ time: new Date(timestamp * 1000),
+ value,
+ })),
+ })),
+ })),
+ }));
+}
+
+export default class MonitoringStore {
constructor() {
this.groups = [];
this.deploymentData = [];
}
- // eslint-disable-next-line class-methods-use-this
- createArrayRows(metrics = []) {
- const currentMetrics = metrics;
- const availableMetrics = [];
- let metricsRow = [];
- let index = 1;
- Object.keys(currentMetrics).forEach((key) => {
- const metricValues = currentMetrics[key].queries[0].result[0].values;
- if (metricValues != null) {
- const literalMetrics = metricValues.map(metric => ({
- time: new Date(metric[0] * 1000),
- value: metric[1],
- }));
- currentMetrics[key].queries[0].result[0].values = literalMetrics;
- metricsRow.push(currentMetrics[key]);
- if (index % 2 === 0) {
- availableMetrics.push(metricsRow);
- metricsRow = [];
- }
- index = index += 1;
- }
- });
- if (metricsRow.length > 0) {
- availableMetrics.push(metricsRow);
- }
- return availableMetrics;
- }
-
storeMetrics(groups = []) {
- this.groups = groups.map((group) => {
- const currentGroup = group;
- currentGroup.metrics = _.chain(group.metrics).sortBy('weight').sortBy('title').value();
- currentGroup.metrics = this.createArrayRows(currentGroup.metrics);
- return currentGroup;
- });
+ this.groups = groups.map(group => ({
+ ...group,
+ metrics: normalizeMetrics(sortMetrics(group.metrics)),
+ }));
}
storeDeploymentData(deploymentData = []) {
@@ -48,14 +38,6 @@ class MonitoringStore {
}
getMetricsCount() {
- let metricsCount = 0;
- this.groups.forEach((group) => {
- group.metrics.forEach((metric) => {
- metricsCount = metricsCount += metric.length;
- });
- });
- return metricsCount;
+ return this.groups.reduce((count, group) => count + group.metrics.length, 0);
}
}
-
-export default MonitoringStore;
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
index 62cd19c86e1..ee3c45efacc 100644
--- a/app/assets/javascripts/monitoring/utils/measurements.js
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -7,15 +7,15 @@ export default {
left: 40,
},
legends: {
- width: 15,
- height: 25,
+ width: 10,
+ height: 3,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
- legendOffset: 35,
+ legendOffset: 33,
},
large: { // This covers both md and lg screen sizes
margin: {
@@ -25,15 +25,15 @@ export default {
left: 80,
},
legends: {
- width: 20,
- height: 30,
+ width: 15,
+ height: 3,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
- legendOffset: 38,
+ legendOffset: 36,
},
xTicks: 8,
yTicks: 3,
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
new file mode 100644
index 00000000000..05d551e917c
--- /dev/null
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -0,0 +1,80 @@
+import d3 from 'd3';
+import _ from 'underscore';
+
+export default function createTimeSeries(seriesData, graphWidth, graphHeight, graphHeightOffset) {
+ const maxValues = seriesData.map((timeSeries, index) => {
+ const maxValue = d3.max(timeSeries.values.map(d => d.value));
+ return {
+ maxValue,
+ index,
+ };
+ });
+
+ const maxValueFromSeries = _.max(maxValues, val => val.maxValue);
+
+ let timeSeriesNumber = 1;
+ let lineColor = '#1f78d1';
+ let areaColor = '#8fbce8';
+ return seriesData.map((timeSeries) => {
+ const timeSeriesScaleX = d3.time.scale()
+ .range([0, graphWidth - 70]);
+
+ const timeSeriesScaleY = d3.scale.linear()
+ .range([graphHeight - graphHeightOffset, 0]);
+
+ timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
+ timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
+
+ const lineFunction = d3.svg.line()
+ .x(d => timeSeriesScaleX(d.time))
+ .y(d => timeSeriesScaleY(d.value));
+
+ const areaFunction = d3.svg.area()
+ .x(d => timeSeriesScaleX(d.time))
+ .y0(graphHeight - graphHeightOffset)
+ .y1(d => timeSeriesScaleY(d.value))
+ .interpolate('linear');
+
+ switch (timeSeriesNumber) {
+ case 1:
+ lineColor = '#1f78d1';
+ areaColor = '#8fbce8';
+ break;
+ case 2:
+ lineColor = '#fc9403';
+ areaColor = '#feca81';
+ break;
+ case 3:
+ lineColor = '#db3b21';
+ areaColor = '#ed9d90';
+ break;
+ case 4:
+ lineColor = '#1aaa55';
+ areaColor = '#8dd5aa';
+ break;
+ case 5:
+ lineColor = '#6666c4';
+ areaColor = '#d1d1f0';
+ break;
+ default:
+ lineColor = '#1f78d1';
+ areaColor = '#8fbce8';
+ break;
+ }
+
+ if (timeSeriesNumber <= 5) {
+ timeSeriesNumber = timeSeriesNumber += 1;
+ } else {
+ timeSeriesNumber = 1;
+ }
+
+ return {
+ linePath: lineFunction(timeSeries.values),
+ areaPath: areaFunction(timeSeries.values),
+ timeSeriesScaleX,
+ values: timeSeries.values,
+ lineColor,
+ areaColor,
+ };
+ });
+}
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
index 05e3f33f5ed..709a5d33b9f 100644
--- a/app/assets/javascripts/new_sidebar.js
+++ b/app/assets/javascripts/new_sidebar.js
@@ -63,7 +63,7 @@ export default class NewNavSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') {
- const collapse = Cookies.get('sidebar_collapsed') === 'true';
+ const collapse = this.$sidebar.hasClass('sidebar-icons-only');
this.toggleCollapsedSidebar(collapse);
}
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b38a6abc8d1..a09270d6d24 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -464,7 +464,6 @@ export default class Notes {
}
renderDiscussionAvatar(diffAvatarContainer, noteEntity) {
- var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
if (!avatarHolder.length) {
@@ -475,10 +474,6 @@ export default class Notes {
gl.diffNotesCompileComponents();
}
-
- if (commentButton.length) {
- commentButton.remove();
- }
}
/**
@@ -767,6 +762,7 @@ export default class Notes {
var $note, $notes;
$note = $(el);
$notes = $note.closest('.discussion-notes');
+ const discussionId = $('.notes', $notes).data('discussion-id');
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteElId]) {
@@ -783,6 +779,8 @@ export default class Notes {
// "Discussions" tab
$notes.closest('.timeline-entry').remove();
+ $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
+
// The notes tr can contain multiple lists of notes, like on the parallel diff
if (notesTr.find('.discussion-notes').length > 1) {
$notes.remove();
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index d7e3ab42f00..fe6602259e2 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -53,10 +53,6 @@ import Cookies from 'js-cookie';
return _this.changeProject($(e.currentTarget).val());
};
})(this));
- return $('.js-projects-dropdown-toggle').on('click', function(e) {
- e.preventDefault();
- return $('.js-projects-dropdown').select2('open');
- });
};
Project.prototype.changeProject = function(url) {
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 1b4ed6be90a..fb01390f91c 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -5,48 +5,6 @@ import ProjectSelectComboButton from './project_select_combo_button';
(function() {
this.ProjectSelect = (function() {
function ProjectSelect() {
- $('.js-projects-dropdown-toggle').each(function(i, dropdown) {
- var $dropdown;
- $dropdown = $(dropdown);
- return $dropdown.glDropdown({
- filterable: true,
- filterRemote: true,
- search: {
- fields: ['name_with_namespace']
- },
- data: function(term, callback) {
- var finalCallback, projectsCallback;
- var orderBy = $dropdown.data('order-by');
- finalCallback = function(projects) {
- return callback(projects);
- };
- if (this.includeGroups) {
- projectsCallback = function(projects) {
- var groupsCallback;
- groupsCallback = function(groups) {
- var data;
- data = groups.concat(projects);
- return finalCallback(data);
- };
- return Api.groups(term, {}, groupsCallback);
- };
- } else {
- projectsCallback = finalCallback;
- }
- if (this.groupId) {
- return Api.groupProjects(this.groupId, term, projectsCallback);
- } else {
- return Api.projects(term, { order_by: orderBy }, projectsCallback);
- }
- },
- url: function(project) {
- return project.web_url;
- },
- text: function(project) {
- return project.name_with_namespace;
- }
- });
- });
$('.ajax-project-select').each(function(i, select) {
var placeholder;
this.groupId = $(select).data('group-id');
diff --git a/app/assets/javascripts/projects_dropdown/components/app.vue b/app/assets/javascripts/projects_dropdown/components/app.vue
new file mode 100644
index 00000000000..7606605be32
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/components/app.vue
@@ -0,0 +1,157 @@
+<script>
+import bs from '../../breakpoints';
+import eventHub from '../event_hub';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+
+import projectsListFrequent from './projects_list_frequent.vue';
+import projectsListSearch from './projects_list_search.vue';
+
+import search from './search.vue';
+
+export default {
+ components: {
+ search,
+ loadingIcon,
+ projectsListFrequent,
+ projectsListSearch,
+ },
+ props: {
+ currentProject: {
+ type: Object,
+ required: true,
+ },
+ store: {
+ type: Object,
+ required: true,
+ },
+ service: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isLoadingProjects: false,
+ isFrequentsListVisible: false,
+ isSearchListVisible: false,
+ isLocalStorageFailed: false,
+ isSearchFailed: false,
+ searchQuery: '',
+ };
+ },
+ computed: {
+ frequentProjects() {
+ return this.store.getFrequentProjects();
+ },
+ searchProjects() {
+ return this.store.getSearchedProjects();
+ },
+ },
+ methods: {
+ toggleFrequentProjectsList(state) {
+ this.isLoadingProjects = !state;
+ this.isSearchListVisible = !state;
+ this.isFrequentsListVisible = state;
+ },
+ toggleSearchProjectsList(state) {
+ this.isLoadingProjects = !state;
+ this.isFrequentsListVisible = !state;
+ this.isSearchListVisible = state;
+ },
+ toggleLoader(state) {
+ this.isFrequentsListVisible = !state;
+ this.isSearchListVisible = !state;
+ this.isLoadingProjects = state;
+ },
+ fetchFrequentProjects() {
+ const screenSize = bs.getBreakpointSize();
+ if (this.searchQuery && (screenSize !== 'sm' && screenSize !== 'xs')) {
+ this.toggleSearchProjectsList(true);
+ } else {
+ this.toggleLoader(true);
+ this.isLocalStorageFailed = false;
+ const projects = this.service.getFrequentProjects();
+ if (projects) {
+ this.toggleFrequentProjectsList(true);
+ this.store.setFrequentProjects(projects);
+ } else {
+ this.isLocalStorageFailed = true;
+ this.toggleFrequentProjectsList(true);
+ this.store.setFrequentProjects([]);
+ }
+ }
+ },
+ fetchSearchedProjects(searchQuery) {
+ this.searchQuery = searchQuery;
+ this.toggleLoader(true);
+ this.service.getSearchedProjects(this.searchQuery)
+ .then(res => res.json())
+ .then((results) => {
+ this.toggleSearchProjectsList(true);
+ this.store.setSearchedProjects(results);
+ })
+ .catch(() => {
+ this.isSearchFailed = true;
+ this.toggleSearchProjectsList(true);
+ });
+ },
+ logCurrentProjectAccess() {
+ this.service.logProjectAccess(this.currentProject);
+ },
+ handleSearchClear() {
+ this.searchQuery = '';
+ this.toggleFrequentProjectsList(true);
+ this.store.clearSearchedProjects();
+ },
+ handleSearchFailure() {
+ this.isSearchFailed = true;
+ this.toggleSearchProjectsList(true);
+ },
+ },
+ created() {
+ if (this.currentProject.id) {
+ this.logCurrentProjectAccess();
+ }
+
+ eventHub.$on('dropdownOpen', this.fetchFrequentProjects);
+ eventHub.$on('searchProjects', this.fetchSearchedProjects);
+ eventHub.$on('searchCleared', this.handleSearchClear);
+ eventHub.$on('searchFailed', this.handleSearchFailure);
+ },
+ beforeDestroy() {
+ eventHub.$off('dropdownOpen', this.fetchFrequentProjects);
+ eventHub.$off('searchProjects', this.fetchSearchedProjects);
+ eventHub.$off('searchCleared', this.handleSearchClear);
+ eventHub.$off('searchFailed', this.handleSearchFailure);
+ },
+};
+</script>
+
+<template>
+ <div>
+ <search/>
+ <loading-icon
+ class="loading-animation prepend-top-20"
+ size="2"
+ v-if="isLoadingProjects"
+ :label="s__('ProjectsDropdown|Loading projects')"
+ />
+ <div
+ class="section-header"
+ v-if="isFrequentsListVisible"
+ >
+ {{ s__('ProjectsDropdown|Frequently visited') }}
+ </div>
+ <projects-list-frequent
+ v-if="isFrequentsListVisible"
+ :local-storage-failed="isLocalStorageFailed"
+ :projects="frequentProjects"
+ />
+ <projects-list-search
+ v-if="isSearchListVisible"
+ :search-failed="isSearchFailed"
+ :matcher="searchQuery"
+ :projects="searchProjects"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue
new file mode 100644
index 00000000000..093554cd0bc
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue
@@ -0,0 +1,57 @@
+<script>
+import { s__ } from '../../locale';
+import projectsListItem from './projects_list_item.vue';
+
+export default {
+ components: {
+ projectsListItem,
+ },
+ props: {
+ projects: {
+ type: Array,
+ required: true,
+ },
+ localStorageFailed: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ isListEmpty() {
+ return this.projects.length === 0;
+ },
+ listEmptyMessage() {
+ return this.localStorageFailed ?
+ s__('ProjectsDropdown|This feature requires browser localStorage support') :
+ s__('ProjectsDropdown|Projects you visit often will appear here');
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="projects-list-frequent-container"
+ >
+ <ul
+ class="list-unstyled"
+ >
+ <li
+ class="section-empty"
+ v-if="isListEmpty"
+ >
+ {{listEmptyMessage}}
+ </li>
+ <projects-list-item
+ v-else
+ v-for="(project, index) in projects"
+ :key="index"
+ :project-id="project.id"
+ :project-name="project.name"
+ :namespace="project.namespace"
+ :web-url="project.webUrl"
+ :avatar-url="project.avatarUrl"
+ />
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue
new file mode 100644
index 00000000000..fe5179de206
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue
@@ -0,0 +1,96 @@
+<script>
+import identicon from '../../vue_shared/components/identicon.vue';
+
+export default {
+ components: {
+ identicon,
+ },
+ props: {
+ matcher: {
+ type: String,
+ required: false,
+ },
+ projectId: {
+ type: Number,
+ required: true,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ namespace: {
+ type: String,
+ required: true,
+ },
+ webUrl: {
+ type: String,
+ required: true,
+ },
+ avatarUrl: {
+ required: true,
+ validator(value) {
+ return value === null || typeof value === 'string';
+ },
+ },
+ },
+ computed: {
+ hasAvatar() {
+ return this.avatarUrl !== null;
+ },
+ highlightedProjectName() {
+ if (this.matcher) {
+ const matcherRegEx = new RegExp(this.matcher, 'gi');
+ const matches = this.projectName.match(matcherRegEx);
+
+ if (matches && matches.length > 0) {
+ return this.projectName.replace(matches[0], `<b>${matches[0]}</b>`);
+ }
+ }
+ return this.projectName;
+ },
+ },
+};
+</script>
+
+<template>
+ <li
+ class="projects-list-item-container"
+ >
+ <a
+ class="clearfix"
+ :href="webUrl"
+ >
+ <div
+ class="project-item-avatar-container"
+ >
+ <img
+ v-if="hasAvatar"
+ class="avatar s32"
+ :src="avatarUrl"
+ />
+ <identicon
+ v-else
+ size-class="s32"
+ :entity-id=projectId
+ :entity-name="projectName"
+ />
+ </div>
+ <div
+ class="project-item-metadata-container"
+ >
+ <div
+ class="project-title"
+ :title="projectName"
+ v-html="highlightedProjectName"
+ >
+ </div>
+ <div
+ class="project-namespace"
+ :title="namespace"
+ >
+ {{namespace}}
+ </div>
+ </div>
+ </a>
+ </li>
+</template>
diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue
new file mode 100644
index 00000000000..fa5efef2919
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue
@@ -0,0 +1,63 @@
+<script>
+import { s__ } from '../../locale';
+import projectsListItem from './projects_list_item.vue';
+
+export default {
+ components: {
+ projectsListItem,
+ },
+ props: {
+ matcher: {
+ type: String,
+ required: true,
+ },
+ projects: {
+ type: Array,
+ required: true,
+ },
+ searchFailed: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ isListEmpty() {
+ return this.projects.length === 0;
+ },
+ listEmptyMessage() {
+ return this.searchFailed ?
+ s__('ProjectsDropdown|Something went wrong on our end.') :
+ s__('ProjectsDropdown|No projects matched your query');
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="projects-list-search-container"
+ >
+ <ul
+ class="list-unstyled"
+ >
+ <li
+ v-if="isListEmpty"
+ :class="{ 'section-failure': searchFailed }"
+ class="section-empty"
+ >
+ {{ listEmptyMessage }}
+ </li>
+ <projects-list-item
+ v-else
+ v-for="(project, index) in projects"
+ :key="index"
+ :project-id="project.id"
+ :project-name="project.name"
+ :namespace="project.namespace"
+ :web-url="project.webUrl"
+ :avatar-url="project.avatarUrl"
+ :matcher="matcher"
+ />
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects_dropdown/components/search.vue b/app/assets/javascripts/projects_dropdown/components/search.vue
new file mode 100644
index 00000000000..b71997234e5
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/components/search.vue
@@ -0,0 +1,64 @@
+<script>
+import _ from 'underscore';
+import eventHub from '../event_hub';
+
+export default {
+ data() {
+ return {
+ searchQuery: '',
+ };
+ },
+ watch: {
+ searchQuery() {
+ this.handleInput();
+ },
+ },
+ methods: {
+ setFocus() {
+ this.$refs.search.focus();
+ },
+ emitSearchEvents() {
+ if (this.searchQuery) {
+ eventHub.$emit('searchProjects', this.searchQuery);
+ } else {
+ eventHub.$emit('searchCleared');
+ }
+ },
+ /**
+ * Callback function within _.debounce is intentionally
+ * kept as ES5 `function() {}` instead of ES6 `() => {}`
+ * as it otherwise messes up function context
+ * and component reference is no longer accessible via `this`
+ */
+ // eslint-disable-next-line func-names
+ handleInput: _.debounce(function () {
+ this.emitSearchEvents();
+ }, 500),
+ },
+ mounted() {
+ eventHub.$on('dropdownOpen', this.setFocus);
+ },
+ beforeDestroy() {
+ eventHub.$off('dropdownOpen', this.setFocus);
+ },
+};
+</script>
+
+<template>
+ <div
+ class="search-input-container hidden-xs"
+ >
+ <input
+ type="search"
+ class="form-control"
+ ref="search"
+ v-model="searchQuery"
+ :placeholder="s__('ProjectsDropdown|Search projects')"
+ />
+ <i
+ v-if="!searchQuery"
+ class="search-icon fa fa-fw fa-search"
+ aria-hidden="true"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects_dropdown/constants.js b/app/assets/javascripts/projects_dropdown/constants.js
new file mode 100644
index 00000000000..8937097184c
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/constants.js
@@ -0,0 +1,10 @@
+export const FREQUENT_PROJECTS = {
+ MAX_COUNT: 20,
+ LIST_COUNT_DESKTOP: 5,
+ LIST_COUNT_MOBILE: 3,
+ ELIGIBLE_FREQUENCY: 3,
+};
+
+export const HOUR_IN_MS = 3600000;
+
+export const STORAGE_KEY = 'frequent-projects';
diff --git a/app/assets/javascripts/projects_dropdown/event_hub.js b/app/assets/javascripts/projects_dropdown/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/projects_dropdown/index.js b/app/assets/javascripts/projects_dropdown/index.js
new file mode 100644
index 00000000000..2660da3c558
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/index.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+
+import Translate from '../vue_shared/translate';
+import eventHub from './event_hub';
+import ProjectsService from './service/projects_service';
+import ProjectsStore from './store/projects_store';
+
+import projectsDropdownApp from './components/app.vue';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+ const el = document.getElementById('js-projects-dropdown');
+ const navEl = document.getElementById('nav-projects-dropdown');
+
+ // Don't do anything if element doesn't exist (No projects dropdown)
+ // This is for when the user accesses GitLab without logging in
+ if (!el || !navEl) {
+ return;
+ }
+
+ $(navEl).on('show.bs.dropdown', (e) => {
+ const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
+ dropdownEl.one('transitionend', () => {
+ eventHub.$emit('dropdownOpen');
+ });
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ components: {
+ projectsDropdownApp,
+ },
+ data() {
+ const dataset = this.$options.el.dataset;
+ const store = new ProjectsStore();
+ const service = new ProjectsService(dataset.userName);
+
+ const project = {
+ id: Number(dataset.projectId),
+ name: dataset.projectName,
+ namespace: dataset.projectNamespace,
+ webUrl: dataset.projectWebUrl,
+ avatarUrl: dataset.projectAvatarUrl || null,
+ lastAccessedOn: Date.now(),
+ };
+
+ return {
+ store,
+ service,
+ state: store.state,
+ currentUserName: dataset.userName,
+ currentProject: project,
+ };
+ },
+ render(createElement) {
+ return createElement('projects-dropdown-app', {
+ props: {
+ currentUserName: this.currentUserName,
+ currentProject: this.currentProject,
+ store: this.store,
+ service: this.service,
+ },
+ });
+ },
+ });
+});
diff --git a/app/assets/javascripts/projects_dropdown/service/projects_service.js b/app/assets/javascripts/projects_dropdown/service/projects_service.js
new file mode 100644
index 00000000000..fad956b4c26
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/service/projects_service.js
@@ -0,0 +1,132 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+import bp from '../../breakpoints';
+import Api from '../../api';
+import AccessorUtilities from '../../lib/utils/accessor';
+
+import { FREQUENT_PROJECTS, HOUR_IN_MS, STORAGE_KEY } from '../constants';
+
+Vue.use(VueResource);
+
+export default class ProjectsService {
+ constructor(currentUserName) {
+ this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+ this.currentUserName = currentUserName;
+ this.storageKey = `${this.currentUserName}/${STORAGE_KEY}`;
+ this.projectsPath = Vue.resource(Api.buildUrl(Api.projectsPath));
+ }
+
+ getSearchedProjects(searchQuery) {
+ return this.projectsPath.get({
+ simple: false,
+ per_page: 20,
+ membership: !!gon.current_user_id,
+ order_by: 'last_activity_at',
+ search: searchQuery,
+ });
+ }
+
+ getFrequentProjects() {
+ if (this.isLocalStorageAvailable) {
+ return this.getTopFrequentProjects();
+ }
+ return null;
+ }
+
+ logProjectAccess(project) {
+ let matchFound = false;
+ let storedFrequentProjects;
+
+ if (this.isLocalStorageAvailable) {
+ const storedRawProjects = localStorage.getItem(this.storageKey);
+
+ // Check if there's any frequent projects list set
+ if (!storedRawProjects) {
+ // No frequent projects list set, set one up.
+ storedFrequentProjects = [];
+ storedFrequentProjects.push({ ...project, frequency: 1 });
+ } else {
+ // Check if project is already present in frequents list
+ // When found, update metadata of it.
+ storedFrequentProjects = JSON.parse(storedRawProjects).map((projectItem) => {
+ if (projectItem.id === project.id) {
+ matchFound = true;
+ const diff = Math.abs(project.lastAccessedOn - projectItem.lastAccessedOn) / HOUR_IN_MS;
+ const updatedProject = {
+ ...project,
+ frequency: projectItem.frequency,
+ lastAccessedOn: projectItem.lastAccessedOn,
+ };
+
+ // Check if duration since last access of this project
+ // is over an hour
+ if (diff > 1) {
+ return {
+ ...updatedProject,
+ frequency: updatedProject.frequency + 1,
+ lastAccessedOn: Date.now(),
+ };
+ }
+
+ return {
+ ...updatedProject,
+ };
+ }
+
+ return projectItem;
+ });
+
+ // Check whether currently logged project is present in frequents list
+ if (!matchFound) {
+ // We always keep size of frequents collection to 20 projects
+ // out of which only 5 projects with
+ // highest value of `frequency` and most recent `lastAccessedOn`
+ // are shown in projects dropdown
+ if (storedFrequentProjects.length === FREQUENT_PROJECTS.MAX_COUNT) {
+ storedFrequentProjects.shift(); // Remove an item from head of array
+ }
+
+ storedFrequentProjects.push({ ...project, frequency: 1 });
+ }
+ }
+
+ localStorage.setItem(this.storageKey, JSON.stringify(storedFrequentProjects));
+ }
+ }
+
+ getTopFrequentProjects() {
+ const storedFrequentProjects = JSON.parse(localStorage.getItem(this.storageKey));
+ let frequentProjectsCount = FREQUENT_PROJECTS.LIST_COUNT_DESKTOP;
+
+ if (!storedFrequentProjects) {
+ return [];
+ }
+
+ if (bp.getBreakpointSize() === 'sm' ||
+ bp.getBreakpointSize() === 'xs') {
+ frequentProjectsCount = FREQUENT_PROJECTS.LIST_COUNT_MOBILE;
+ }
+
+ const frequentProjects = storedFrequentProjects
+ .filter(project => project.frequency >= FREQUENT_PROJECTS.ELIGIBLE_FREQUENCY);
+
+ // Sort all frequent projects in decending order of frequency
+ // and then by lastAccessedOn with recent most first
+ frequentProjects.sort((projectA, projectB) => {
+ if (projectA.frequency < projectB.frequency) {
+ return 1;
+ } else if (projectA.frequency > projectB.frequency) {
+ return -1;
+ } else if (projectA.lastAccessedOn < projectB.lastAccessedOn) {
+ return 1;
+ } else if (projectA.lastAccessedOn > projectB.lastAccessedOn) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ return _.first(frequentProjects, frequentProjectsCount);
+ }
+}
diff --git a/app/assets/javascripts/projects_dropdown/store/projects_store.js b/app/assets/javascripts/projects_dropdown/store/projects_store.js
new file mode 100644
index 00000000000..ffefbe693f4
--- /dev/null
+++ b/app/assets/javascripts/projects_dropdown/store/projects_store.js
@@ -0,0 +1,33 @@
+export default class ProjectsStore {
+ constructor() {
+ this.state = {};
+ this.state.frequentProjects = [];
+ this.state.searchedProjects = [];
+ }
+
+ setFrequentProjects(rawProjects) {
+ this.state.frequentProjects = rawProjects;
+ }
+
+ getFrequentProjects() {
+ return this.state.frequentProjects;
+ }
+
+ setSearchedProjects(rawProjects) {
+ this.state.searchedProjects = rawProjects.map(rawProject => ({
+ id: rawProject.id,
+ name: rawProject.name,
+ namespace: rawProject.name_with_namespace,
+ webUrl: rawProject.web_url,
+ avatarUrl: rawProject.avatar_url,
+ }));
+ }
+
+ getSearchedProjects() {
+ return this.state.searchedProjects;
+ }
+
+ clearSearchedProjects() {
+ this.state.searchedProjects = [];
+ }
+}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 4c87d46c96e..a4eae135403 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -2,7 +2,6 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
-import SidebarHeightManager from './sidebar_height_manager';
(function() {
this.Sidebar = (function() {
@@ -23,7 +22,6 @@ import SidebarHeightManager from './sidebar_height_manager';
};
Sidebar.prototype.addEventListeners = function() {
- SidebarHeightManager.init();
const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js
deleted file mode 100644
index 2752fe2b911..00000000000
--- a/app/assets/javascripts/sidebar_height_manager.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import _ from 'underscore';
-import Cookies from 'js-cookie';
-
-export default {
- init() {
- if (!this.initialized) {
- if (Cookies.get('new_nav') === 'true' && $('.js-issuable-sidebar').length) return;
-
- this.$window = $(window);
- this.$rightSidebar = $('.js-right-sidebar');
- this.$navHeight = $('.navbar-gitlab').outerHeight() +
- $('.layout-nav').outerHeight() +
- $('.sub-nav-scroll').outerHeight();
-
- const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
- const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
-
- this.$window.on('scroll', throttledSetSidebarHeight);
- this.$window.on('resize', debouncedSetSidebarHeight);
- this.initialized = true;
- }
- },
-
- setSidebarHeight() {
- const currentScrollDepth = window.pageYOffset || 0;
- const diff = this.$navHeight - currentScrollDepth;
-
- if (diff > 0) {
- const newSidebarHeight = window.innerHeight - diff;
- this.$rightSidebar.outerHeight(newSidebarHeight);
- this.sidebarHeightIsCustom = true;
- } else if (this.sidebarHeightIsCustom) {
- this.$rightSidebar.outerHeight('100%');
- this.sidebarHeightIsCustom = false;
- }
- },
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index c05a76a3b4a..aaca42e3ebc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -75,18 +75,20 @@ export default {
class="btn btn-small inline">
Check out branch
</a>
- <span class="dropdown inline prepend-left-10">
+ <span class="dropdown prepend-left-10">
<a
- class="btn btn-xs dropdown-toggle"
+ class="btn btn-small inline dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
role="button">
<i
class="fa fa-download"
- aria-hidden="true" />
+ aria-hidden="true">
+ </i>
<i
class="fa fa-caret-down"
- aria-hidden="true" />
+ aria-hidden="true">
+ </i>
</a>
<ul class="dropdown-menu dropdown-menu-align-right">
<li>
diff --git a/app/assets/javascripts/vue_shared/components/identicon.vue b/app/assets/javascripts/vue_shared/components/identicon.vue
index 0edd820743f..7cf2e029cf6 100644
--- a/app/assets/javascripts/vue_shared/components/identicon.vue
+++ b/app/assets/javascripts/vue_shared/components/identicon.vue
@@ -9,6 +9,11 @@ export default {
type: String,
required: true,
},
+ sizeClass: {
+ type: String,
+ required: false,
+ default: 's40',
+ },
},
computed: {
/**
@@ -38,7 +43,8 @@ export default {
<template>
<div
- class="avatar s40 identicon"
+ class="avatar identicon"
+ :class="sizeClass"
:style="identiconStyles">
{{identiconTitle}}
</div>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 68a51c5a461..a85051642dd 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -21,6 +21,7 @@
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
.append-bottom-0 { margin-bottom: 0; }
+.append-bottom-5 { margin-bottom: 5px; }
.append-bottom-10 { margin-bottom: 10px; }
.append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index fad991f2c49..5f397f08936 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -737,6 +737,8 @@
@mixin new-style-dropdown($selector: '') {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
+ margin-bottom: 24px;
+
li {
display: block;
padding: 0 1px;
@@ -764,11 +766,12 @@
box-shadow: none;
padding: 8px 16px;
text-align: left;
+ white-space: normal;
width: 100%;
// make sure the text color is not overriden
&.text-danger {
- @extend .text-danger;
+ color: $brand-danger;
}
&.is-focused,
@@ -777,6 +780,11 @@
&:focus {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
+
+ // make sure the text color is not overriden
+ &.text-danger {
+ color: $brand-danger;
+ }
}
&.is-active {
@@ -821,4 +829,154 @@
}
}
+@include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + ');
+
+header.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu {
+ padding: 0;
+
+ @media (max-width: $screen-xs-max) {
+ display: table;
+ left: -50px;
+ min-width: 300px;
+ }
+}
+
+.projects-dropdown-container {
+ display: flex;
+ flex-direction: row;
+ width: 500px;
+ height: 334px;
+
+ .project-dropdown-sidebar,
+ .project-dropdown-content {
+ padding: 8px 0;
+ }
+
+ .loading-animation {
+ color: $almost-black;
+ }
+
+ .project-dropdown-sidebar {
+ width: 30%;
+ border-right: 1px solid $border-color;
+ }
+
+ .project-dropdown-content {
+ position: relative;
+ width: 70%;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ flex-direction: column;
+ width: 100%;
+ height: auto;
+ flex: 1;
+
+ .project-dropdown-sidebar,
+ .project-dropdown-content {
+ width: 100%;
+ }
+
+ .project-dropdown-sidebar {
+ border-bottom: 1px solid $border-color;
+ border-right: 0;
+ }
+ }
+}
+
+.projects-dropdown-container {
+ .projects-list-frequent-container,
+ .projects-list-search-container, {
+ padding: 8px 0;
+ overflow-y: auto;
+ }
+
+ .section-header,
+ .projects-list-frequent-container li.section-empty,
+ .projects-list-search-container li.section-empty {
+ padding: 0 15px;
+ }
+
+ .section-header,
+ .projects-list-frequent-container li.section-empty,
+ .projects-list-search-container li.section-empty {
+ color: $gl-text-color-secondary;
+ font-size: $gl-font-size;
+ }
+
+ .projects-list-frequent-container,
+ .projects-list-search-container {
+ li.section-empty.section-failure {
+ color: $callout-danger-color;
+ }
+ }
+
+ .search-input-container {
+ position: relative;
+ padding: 4px $gl-padding;
+
+ .search-icon {
+ position: absolute;
+ top: 13px;
+ right: 25px;
+ color: $md-area-border;
+ }
+ }
+
+ .section-header {
+ font-weight: 700;
+ margin-top: 8px;
+ }
+
+ .projects-list-search-container {
+ height: 284px;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .projects-list-frequent-container {
+ width: auto;
+ height: auto;
+ padding-bottom: 0;
+ }
+ }
+}
+
+.projects-list-item-container {
+ .project-item-avatar-container
+ .project-item-metadata-container {
+ float: left;
+ }
+
+ .project-title,
+ .project-namespace {
+ max-width: 250px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &:hover {
+ .project-item-avatar-container .avatar {
+ border-color: $md-area-border;
+ }
+ }
+
+ .project-title {
+ font-size: $gl-font-size;
+ font-weight: 400;
+ line-height: 16px;
+ }
+
+ .project-namespace {
+ margin-top: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ color: $gl-text-color-secondary;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .project-item-metadata-container {
+ float: none;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 35bd97980e2..b00a2d053e2 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -105,12 +105,11 @@ header {
top: -3px;
font-size: 10px;
}
+ }
+ .user-counter {
svg {
- position: relative;
- top: 2px;
- height: 17px;
- // hack to get SVG to line up with FA icons
+ height: 16px;
width: 23px;
fill: currentColor;
}
@@ -325,12 +324,12 @@ header {
li {
.badge {
position: inherit;
- top: -8px;
font-weight: $gl-font-weight-normal;
- margin-left: -11px;
+ margin-left: -6px;
font-size: 11px;
color: $white-light;
- padding: 1px 5px 2px;
+ padding: 0 5px;
+ line-height: 12px;
border-radius: 7px;
box-shadow: 0 1px 0 rgba($gl-header-color, .2);
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index a39927eb0df..6c14e8b97e0 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -267,14 +267,26 @@
// TODO: change global style
.ajax-project-dropdown,
+.ajax-users-dropdown,
+body[data-page="projects:edit"] #select2-drop,
body[data-page="projects:new"] #select2-drop,
+body[data-page="projects:merge_requests:edit"] #select2-drop,
body[data-page="projects:blob:new"] #select2-drop,
body[data-page="profiles:show"] #select2-drop,
+body[data-page="admin:groups:show"] #select2-drop,
+body[data-page="projects:issues:show"] #select2-drop,
body[data-page="projects:blob:edit"] #select2-drop {
&.select2-drop {
+ border: 1px solid $dropdown-border-color;
+ border-radius: $border-radius-base;
color: $gl-text-color;
}
+ &.select2-drop-above {
+ border-top: none;
+ margin-top: -4px;
+ }
+
.select2-results {
.select2-no-results,
.select2-searching,
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 01fffa717e9..88b08998dfd 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -177,13 +177,14 @@ $row-hover: $blue-25;
$row-hover-border: $blue-100;
$progress-color: #c0392b;
$header-height: 50px;
+$new-navbar-height: 40px;
$fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$limited-layout-width-sm: 790px;
$container-text-max-width: 540px;
$gl-avatar-size: 40px;
$error-exclamation-point: $red-500;
-$border-radius-default: 3px;
+$border-radius-default: 4px;
$settings-icon-size: 18px;
$provider-btn-not-active-color: $blue-500;
$link-underline-blue: $blue-500;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index b711bd12c73..e4b52ab480d 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -1,16 +1,23 @@
@import "framework/variables";
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
+@import "framework/mixins";
+
+.content-wrapper.page-with-new-nav {
+ margin-top: $new-navbar-height;
+}
header.navbar-gitlab-new {
color: $white-light;
background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0;
+ min-height: $new-navbar-height;
.header-content {
display: -webkit-flex;
display: flex;
padding-left: 0;
+ min-height: $new-navbar-height;
.title-container {
display: -webkit-flex;
@@ -38,20 +45,13 @@ header.navbar-gitlab-new {
display: -webkit-flex;
display: flex;
align-items: center;
- padding-right: $gl-padding;
- padding-left: $gl-padding;
- margin-left: -$gl-padding;
-
- @media (min-width: $screen-sm-min) {
- padding-right: $gl-padding;
- padding-left: $gl-padding;
- }
+ padding: 2px 8px;
+ margin: 5px 2px 5px -8px;
+ border-radius: $border-radius-default;
svg {
- margin-top: -3px;
-
@media (min-width: $screen-sm-min) {
- margin-right: 10px;
+ margin-right: 8px;
}
}
@@ -60,7 +60,7 @@ header.navbar-gitlab-new {
svg {
width: 55px;
- height: 15px;
+ height: 14px;
margin: 0;
fill: $white-light;
}
@@ -68,9 +68,7 @@ header.navbar-gitlab-new {
&:hover,
&:focus {
- .logo-text svg {
- fill: $tanuki-yellow;
- }
+ background-color: rgba($indigo-200, .2);
}
}
}
@@ -90,6 +88,20 @@ header.navbar-gitlab-new {
right: 0;
}
}
+
+ &.menu-expanded {
+ @media (max-width: $screen-xs-max) {
+ .title-container,
+ .header-logo, {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .dropdown-bold-header {
+ color: $gl-text-color-secondary;
+ font-size: 12px;
}
.navbar-collapse {
@@ -98,14 +110,10 @@ header.navbar-gitlab-new {
box-shadow: 0;
@media (max-width: $screen-xs-max) {
- margin-left: -$gl-padding;
+ margin-left: -8px;
margin-right: -10px;
}
- .dropdown-bold-header {
- color: initial;
- }
-
.nav {
> li:not(.hidden-xs) a {
@media (max-width: $screen-xs-max) {
@@ -119,7 +127,7 @@ header.navbar-gitlab-new {
.container-fluid {
.navbar-toggle {
min-width: 45px;
- padding: 6px $gl-padding;
+ padding: 4px $gl-padding;
margin-right: -7px;
font-size: 14px;
text-align: center;
@@ -156,31 +164,90 @@ header.navbar-gitlab-new {
}
> a {
- background: none;
will-change: color;
+ margin: 4px 2px;
+ padding: 6px 8px;
+ color: $indigo-200;
+ height: 32px;
+
+ @media (max-width: $screen-xs-max) {
+ padding: 0;
+ }
+
+ svg {
+ fill: $indigo-200;
+ }
&.header-user-dropdown-toggle {
+ margin-left: 2px;
+
.header-user-avatar {
border-color: $indigo-200;
+ margin-right: 0;
}
}
+ }
- &:hover,
- &:focus {
- color: $white-light;
- opacity: 1;
+ .header-new-dropdown-toggle {
+ margin-right: 0;
+ }
- > svg {
- fill: $white-light;
- }
+ > a:hover,
+ > a:focus {
+ text-decoration: none;
+ outline: 0;
+ opacity: 1;
+ color: $white-light;
+
+ @media (min-width: $screen-sm-min) {
+ background-color: rgba($indigo-200, .2);
+ }
- &.header-user-dropdown-toggle {
- .header-user-avatar {
- border-color: $white-light;
- }
+ svg {
+ fill: currentColor;
+ }
+
+ &.header-user-dropdown-toggle {
+ .header-user-avatar {
+ border-color: $white-light;
}
}
}
+
+ .impersonated-user,
+ .impersonated-user:hover {
+ margin-right: 1px;
+ background-color: $white-light;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+
+ svg {
+ fill: $indigo-900;
+ }
+ }
+
+ .impersonation-btn,
+ .impersonation-btn:hover {
+ background-color: $white-light;
+ margin-left: 0;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+
+ i {
+ color: $orange-500;
+ font-size: 20px;
+ }
+ }
+
+ &.active > a,
+ &.dropdown.open > a {
+ color: $indigo-900;
+ background-color: $white-light;
+
+ svg {
+ fill: currentColor;
+ }
+ }
}
}
}
@@ -188,45 +255,76 @@ header.navbar-gitlab-new {
.navbar-sub-nav {
display: -webkit-flex;
display: flex;
- margin-bottom: 0;
+ margin: 0 0 0 6px;
color: $indigo-200;
- > li {
- > a:hover,
- > a:focus {
- box-shadow: inset 0 -3px 0 rgba($indigo-200, .4);
- text-decoration: none;
- outline: 0;
- color: $white-light;
- }
+ .dropdown-chevron {
+ position: relative;
+ top: -1px;
+ font-size: 10px;
+ }
+}
- &.active > a {
- box-shadow: inset 0 -3px 0 $indigo-500;
- color: $white-light;
- font-weight: $gl-font-weight-bold;
- }
+.navbar-gitlab-new {
+ .navbar-sub-nav,
+ .navbar-nav {
+ > li {
+ > a:hover,
+ > a:focus {
+ text-decoration: none;
+ outline: 0;
+ color: $white-light;
+ background-color: rgba($indigo-200, .2);
- > a {
- display: block;
- padding: 16px 10px;
- font-size: 13px;
- color: currentColor;
- box-shadow: inset 0 0 0 transparent;
- will-change: box-shadow;
- transition: box-shadow 0.15s;
+ svg {
+ fill: currentColor;
+ }
+ }
- @media (min-width: $screen-sm-min) {
- padding: 15px $gl-padding;
- font-size: 14px;
+ &.active > a,
+ &.dropdown.open > a {
+ color: $indigo-900;
+ background-color: $white-light;
+
+ svg {
+ fill: currentColor;
+ }
+ }
+
+ > a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 8px;
+ margin: 4px 2px;
+ font-size: 12px;
+ color: currentColor;
+ border-radius: $border-radius-default;
+ height: 32px;
+ font-weight: $gl-font-weight-bold;
+
+ svg {
+ fill: currentColor;
+ }
+ }
+
+ &.line-separator {
+ border-left: 1px solid rgba($indigo-200, .2);
+ margin: 8px;
}
}
}
+}
- .dropdown-chevron {
- position: relative;
- top: -1px;
- font-size: 10px;
- }
+.admin-icon i {
+ font-size: 18px;
+}
+
+.caret-down {
+ height: 11px;
+ width: 11px;
+ margin-left: 4px;
+ fill: currentColor;
}
.header-user .dropdown-menu-nav,
@@ -235,10 +333,14 @@ header.navbar-gitlab-new {
}
.search {
+ margin: 4px 8px 0;
+
form {
+ height: 32px;
border: 0;
+ border-radius: $border-radius-default;
background-color: rgba($indigo-200, .2);
- transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, background-color ease-in-out 0.15s;
+ transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
background-color: rgba($indigo-200, .3);
@@ -247,31 +349,50 @@ header.navbar-gitlab-new {
}
&.search-active form {
- background-color: rgba($indigo-200, .3);
+ background-color: $white-light;
box-shadow: none;
+
+ .search-input {
+ color: $gl-text-color;
+ transition: color ease-in-out 0.15s;
+ }
+
+ .search-input::placeholder {
+ color: $gl-text-color-tertiary;
+ }
+
+ .search-input-wrap {
+ .search-icon,
+ .clear-icon {
+ color: $gl-text-color-tertiary;
+ transition: color ease-in-out 0.15s;
+ }
+ }
}
.search-input {
color: $white-light;
background: none;
+ transition: color ease-in-out 0.15s;
}
.search-input::placeholder {
color: rgba($indigo-200, .8);
+ transition: color ease-in-out 0.15s;
}
.location-badge {
font-size: 12px;
color: $indigo-100;
background-color: rgba($indigo-200, .1);
- transition: color 0.15s;
will-change: color;
margin: -4px 4px -4px -4px;
line-height: 25px;
padding: 4px 8px;
border-radius: 2px 0 0 2px;
border-right: 1px solid $indigo-800;
- height: 34px;
+ height: 32px;
+ transition: border-color ease-in-out 0.15s;
}
.search-input-wrap {
@@ -283,8 +404,9 @@ header.navbar-gitlab-new {
&.search-active {
.location-badge {
- color: $white-light;
- background-color: rgba($indigo-200, .2);
+ color: $gl-text-color;
+ background-color: $nav-badge-bg;
+ border-color: $border-color;
}
.search-input-wrap {
@@ -301,109 +423,38 @@ header.navbar-gitlab-new {
.breadcrumbs {
display: flex;
- min-height: 61px;
+ min-height: 48px;
color: $gl-text-color;
- border-bottom: 1px solid $border-color;
-
- .dropdown-toggle-caret {
- position: relative;
- top: -1px;
- padding: 0 5px;
- color: $gl-text-color-secondary;
- font-size: 10px;
- line-height: 1;
- background: none;
- border: 0;
-
- &:focus {
- outline: 0;
- }
- }
-
- // TODO: fallback to global style
- .dropdown-menu {
- .divider {
- margin: 6px 0;
- }
-
- li {
- padding: 0 1px;
-
- a {
- border-radius: 0;
- padding: 8px 16px;
-
- &.is-focused,
- &:hover,
- &:active,
- &:focus {
- background-color: $gray-darker;
- }
- }
- }
- }
}
.breadcrumbs-container {
+ display: -webkit-flex;
display: flex;
width: 100%;
position: relative;
+ padding-top: $gl-padding;
+ padding-bottom: $gl-padding;
align-items: center;
-
- .dropdown-menu-projects {
- margin-top: -$gl-padding;
- margin-left: $gl-padding;
- }
+ border-bottom: 1px solid $border-color;
}
.breadcrumbs-links {
+ -webkit-flex: 1;
flex: 1;
min-width: 0;
align-self: center;
- color: $gl-text-color-quaternary;
-
- a {
- color: $gl-text-color-secondary;
-
- &:not(:first-child),
- &.group-path {
- margin-left: 4px;
- }
-
- &:not(:last-of-type),
- &.group-path {
- margin-right: 3px;
- }
- }
-
- .title {
- display: inline-block;
-
- > a {
- &:last-of-type:not(:first-child) {
- font-weight: $gl-font-weight-bold;
- }
- }
- }
+ color: $gl-text-color-secondary;
.avatar-tile {
- margin-right: 5px;
+ margin-right: 4px;
border: 1px solid $border-color;
border-radius: 50%;
vertical-align: sub;
-
- &.identicon {
- float: left;
- width: 16px;
- height: 16px;
- margin-top: 2px;
- font-size: 10px;
- }
}
.text-expander {
- margin-left: 4px;
- margin-right: 4px;
+ margin-left: 0;
+ margin-right: 2px;
> i {
position: relative;
@@ -412,37 +463,52 @@ header.navbar-gitlab-new {
}
}
-.breadcrumbs-extra {
+.breadcrumbs-list {
+ display: -webkit-flex;
display: flex;
- flex: 0 0 auto;
- margin-left: auto;
-}
-
-.breadcrumbs-sub-title {
- margin: 2px 0;
- font-size: 16px;
- font-weight: $gl-font-weight-normal;
- line-height: 1;
-
- ul {
- margin: 0;
- }
+ flex-wrap: wrap;
+ margin-bottom: 0;
+ line-height: 16px;
- li {
- display: inline-block;
+ > li {
+ display: flex;
+ align-items: center;
+ position: relative;
&:not(:last-child) {
- &::after {
- content: "/";
- margin: 0 2px 0 5px;
- color: rgba($black, .65);
- }
+ margin-right: 20px;
}
- &:last-child a {
- font-weight: $gl-font-weight-bold;
+ > a {
+ font-size: 12px;
+ color: currentColor;
}
}
+}
+
+.breadcrumb-item-text {
+ @include str-truncated(128px);
+}
+
+.breadcrumbs-list-angle {
+ position: absolute;
+ right: -12px;
+ top: 50%;
+ color: $gl-text-color-tertiary;
+ transform: translateY(-50%);
+}
+
+.breadcrumbs-extra {
+ display: flex;
+ flex: 0 0 auto;
+ margin-left: auto;
+}
+
+.breadcrumbs-sub-title {
+ margin: 0;
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 1;
a {
color: $gl-text-color;
@@ -458,3 +524,14 @@ header.navbar-gitlab-new {
}
}
}
+
+.btn-sign-in {
+ margin-top: 3px;
+ background-color: $indigo-100;
+ color: $indigo-900;
+ font-weight: $gl-font-weight-bold;
+
+ &:hover {
+ background-color: $white-light;
+ }
+}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index f624b130e19..55e0343d0dc 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -26,7 +26,7 @@ $new-sidebar-collapsed-width: 50px;
// Override position: absolute
.right-sidebar {
position: fixed;
- height: calc(100% - #{$header-height});
+ height: calc(100% - #{$new-navbar-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
@@ -45,7 +45,6 @@ $new-sidebar-collapsed-width: 50px;
margin-right: 2px;
a {
- border-bottom: 1px solid $border-color;
font-weight: $gl-font-weight-bold;
display: flex;
align-items: center;
@@ -93,7 +92,7 @@ $new-sidebar-collapsed-width: 50px;
z-index: 400;
width: $new-sidebar-width;
transition: left $sidebar-transition-duration;
- top: $header-height;
+ top: $new-navbar-height;
bottom: 0;
left: 0;
background-color: $gray-normal;
@@ -189,7 +188,7 @@ $new-sidebar-collapsed-width: 50px;
}
.with-performance-bar .nav-sidebar {
- top: $header-height + $performance-bar-height;
+ top: $new-navbar-height + $performance-bar-height;
}
.sidebar-sub-level-items {
@@ -453,7 +452,7 @@ $new-sidebar-collapsed-width: 50px;
// Make issue boards full-height now that sub-nav is gone
.boards-list {
- height: calc(100vh - #{$header-height});
+ height: calc(100vh - #{$new-navbar-height});
@media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
@@ -464,7 +463,7 @@ $new-sidebar-collapsed-width: 50px;
}
.with-performance-bar .boards-list {
- height: calc(100vh - #{$header-height} - #{$performance-bar-height});
+ height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height});
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 0f3074076ce..314dd2d1a21 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -440,6 +440,7 @@
&.right-sidebar {
top: 0;
bottom: 0;
+ height: 100%;
}
.issuable-sidebar-header {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index c051d37aad6..587a202d6dd 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -141,17 +141,17 @@
display: inline-block;
background: $white-light;
color: $gl-text-color-secondary;
- padding: 0 5px;
+ padding: 0 4px;
cursor: pointer;
border: 1px solid $border-gray-dark;
border-radius: $border-radius-default;
margin-left: 5px;
- font-size: $gl-font-size;
+ font-size: 12px;
line-height: $gl-font-size;
outline: none;
&.open {
- background: $gray-light;
+ background-color: darken($gray-light, 10%);
box-shadow: inset 0 0 2px rgba($black, 0.2);
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index e7c830cbc69..9362d80d4e6 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -169,7 +169,7 @@
}
.metric-area {
- opacity: 0.8;
+ opacity: 0.25;
}
.prometheus-graph-overlay {
@@ -227,6 +227,26 @@
margin-top: 20px;
}
+.prometheus-graph-group {
+ display: flex;
+ flex-wrap: wrap;
+ padding: $gl-padding / 2;
+}
+
+.prometheus-graph {
+ flex: 1 0 auto;
+ min-width: 450px;
+ padding: $gl-padding / 2;
+
+ h5 {
+ font-size: 16px;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ min-width: 100%;
+ }
+}
+
.prometheus-svg-container {
position: relative;
height: 0;
@@ -251,8 +271,14 @@
font-weight: $gl-font-weight-bold;
}
- .label-axis-text,
- .text-metric-usage {
+ .label-axis-text {
+ fill: $black;
+ font-weight: $gl-font-weight-normal;
+ font-size: 10px;
+ }
+
+ .text-metric-usage,
+ .legend-metric-title {
fill: $black;
font-weight: $gl-font-weight-normal;
font-size: 12px;
@@ -291,9 +317,3 @@
}
}
}
-
-.prometheus-row {
- h5 {
- font-size: 16px;
- }
-}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6523376ccc3..9f2cb979518 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -617,6 +617,8 @@
}
.issuable-actions {
+ @include new-style-dropdown;
+
padding-top: 10px;
@media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0213e7aa9d9..e8ca5cedaee 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -143,8 +143,12 @@ ul.related-merge-requests > li {
}
}
-.issue-form .select2-container {
- width: 250px !important;
+.issue-form {
+ @include new-style-dropdown;
+
+ .select2-container {
+ width: 250px !important;
+ }
}
.issues-footer {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 8932cff22a8..5d7c85b16ef 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -23,6 +23,8 @@
.new-note,
.note-edit-form {
.note-form-actions {
+ @include new-style-dropdown;
+
position: relative;
margin: $gl-padding 0 0;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 19caefa1961..dd600a27545 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -800,8 +800,10 @@ pre.light-well {
}
}
-.new_protected_branch,
+.new-protected-branch,
.new-protected-tag {
+ @include new-style-dropdown;
+
label {
margin-top: 6px;
font-weight: $gl-font-weight-normal;
@@ -821,19 +823,9 @@ pre.light-well {
.protected-branches-list,
.protected-tags-list {
- margin-bottom: 30px;
-
- a {
- color: $gl-text-color;
-
- &:hover {
- color: $gl-link-color;
- }
+ @include new-style-dropdown;
- &.is-active {
- font-weight: $gl-font-weight-bold;
- }
- }
+ margin-bottom: 30px;
.settings-message {
margin: 0;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 8d73246223d..615020ca856 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -190,6 +190,8 @@ input[type="checkbox"]:hover {
}
.search-holder {
+ @include new-style-dropdown;
+
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index a34a82b7ba6..23909bd2d39 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -36,6 +36,34 @@ module IssuableCollections
@merge_requests_finder ||= issuable_finder_for(MergeRequestsFinder)
end
+ def redirect_out_of_range(relation, total_pages)
+ return false if total_pages.zero?
+
+ out_of_range = relation.current_page > total_pages
+
+ if out_of_range
+ redirect_to(url_for(params.merge(page: total_pages, only_path: true)))
+ end
+
+ out_of_range
+ end
+
+ def issues_page_count(relation)
+ page_count_for_relation(relation, issues_finder.row_count)
+ end
+
+ def merge_requests_page_count(relation)
+ page_count_for_relation(relation, merge_requests_finder.row_count)
+ end
+
+ def page_count_for_relation(relation, row_count)
+ limit = relation.limit_value.to_f
+
+ return 1 if limit.zero?
+
+ (row_count.to_f / limit).ceil
+ end
+
def issuable_finder_for(finder_class)
finder_class.new(current_user, filter_params)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 0d4266f0899..dc9e6f71152 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -27,10 +27,9 @@ class Projects::IssuesController < Projects::ApplicationController
@issues = issues_collection
@issues = @issues.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issues, @collection_type)
+ @total_pages = issues_page_count(@issues)
- if @issues.out_of_range? && @issues.total_pages != 0
- return redirect_to url_for(params.merge(page: @issues.total_pages, only_path: true))
- end
+ return if redirect_out_of_range(@issues, @total_pages)
if params[:label_name].present?
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index e3fa3736808..5095d7fd445 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -18,10 +18,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(merge_request_diff: :merge_request)
@issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
+ @total_pages = merge_requests_page_count(@merge_requests)
- if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
- return redirect_to url_for(params.merge(page: @merge_requests.total_pages, only_path: true))
- end
+ return if redirect_out_of_range(@merge_requests, @total_pages)
if params[:label_name].present?
labels_params = { project_id: @project.id, title: params[:label_name] }
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ed17b3b4689..b13034d3333 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -323,6 +323,7 @@ class ProjectsController < Projects::ApplicationController
:build_allow_git_fetch,
:build_coverage_regex,
:build_timeout_in_minutes,
+ :resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
:description,
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index c8dd2275730..9848497f258 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -61,6 +61,10 @@ class IssuableFinder
execute.find_by(*params)
end
+ def row_count
+ Gitlab::IssuablesCountForState.new(self).for_state_or_opened(params[:state])
+ end
+
# We often get counts for each state by running a query per state, and
# counting those results. This is typically slower than running one query
# (even if that query is slower than any of the individual state queries) and
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index aa9cef6b08c..d2275139c42 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -14,6 +14,7 @@
# search: string
# label_name: string
# sort: string
+# my_reaction_emoji: string
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 771da3d441d..d0687d28c21 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -16,6 +16,7 @@
# label_name: string
# sort: string
# non_archived: boolean
+# my_reaction_emoji: string
#
class MergeRequestsFinder < IssuableFinder
def klass
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 017df8f6794..8d02d5de5c3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -302,10 +302,6 @@ module ApplicationHelper
end
end
- def show_new_nav?
- true
- end
-
def collapsed_sidebar?
cookies["sidebar_collapsed"] == "true"
end
diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb
index abe8edd6a8c..ee1b7ed083e 100644
--- a/app/helpers/breadcrumbs_helper.rb
+++ b/app/helpers/breadcrumbs_helper.rb
@@ -22,4 +22,16 @@ module BreadcrumbsHelper
@breadcrumb_title = title
end
+
+ def breadcrumb_list_item(link)
+ content_tag "li" do
+ link + icon("angle-right", class: "breadcrumbs-list-angle")
+ end
+ end
+
+ def add_to_breadcrumb_dropdown(link, location: :before)
+ @breadcrumb_dropdown_links ||= {}
+ @breadcrumb_dropdown_links[location] ||= []
+ @breadcrumb_dropdown_links[location] << link
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index dd159d12aa0..eab1feb8a1f 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -15,18 +15,20 @@ module GroupsHelper
@has_group_title = true
full_title = ''
- group.ancestors.reverse.each do |parent|
- full_title += group_title_link(parent, hidable: true)
-
- full_title += '<span class="hidable"> / </span>'.html_safe
+ group.ancestors.reverse.each_with_index do |parent, index|
+ if index > 0
+ add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before)
+ else
+ full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
+ end
end
- full_title += group_title_link(group)
- full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
+ full_title += render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups")
- content_tag :span, class: 'group-title' do
- full_title.html_safe
- end
+ full_title += breadcrumb_list_item group_title_link(group)
+ full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
+
+ full_title.html_safe
end
def projects_lfs_status(group)
@@ -65,11 +67,11 @@ module GroupsHelper
private
- def group_title_link(group, hidable: false)
- link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
+ def group_title_link(group, hidable: false, show_avatar: false)
+ link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do
output =
- if show_new_nav? && !Rails.env.test?
- image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
+ if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
+ image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15)
else
""
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 681615dbf3e..2cf2e7dddb0 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -126,12 +126,8 @@ module IssuablesHelper
end
def issuable_meta(issuable, project, text)
- output = content_tag(:strong, class: "identifier") do
- concat("#{text} ")
- concat(to_url_reference(issuable))
- end
-
- output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
+ output = ""
+ output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
@@ -141,7 +137,7 @@ module IssuablesHelper
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
- output
+ output.html_safe
end
def issuable_todo(issuable)
@@ -240,7 +236,8 @@ module IssuablesHelper
def issuables_count_for_state(issuable_type, state)
finder = public_send("#{issuable_type}_finder") # rubocop:disable GitlabSecurity/PublicSend
- finder.count_by_state[state]
+
+ Gitlab::IssuablesCountForState.new(finder)[state]
end
def close_issuable_url(issuable)
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index b63b3b70903..a23a43c9f43 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,8 +1,8 @@
module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
- class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
- class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
+ class_name << 'page-with-new-sidebar' if defined?(@left_sidebar) && @left_sidebar
+ class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
class_name
end
@@ -30,24 +30,15 @@ module NavHelper
end
end
- def nav_header_class
- class_names = []
- class_names << 'with-horizontal-nav' if defined?(nav) && nav
-
- class_names
+ def nav_control_class
+ "nav-control" if current_user
end
- def layout_nav_class
- return [] if show_new_nav?
-
+ def user_dropdown_class
class_names = []
- class_names << 'page-with-layout-nav' if defined?(nav) && nav
- class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
+ class_names << 'header-user-dropdown-toggle'
+ class_names << 'impersonated-user' if session[:impersonator_id]
class_names
end
-
- def nav_control_class
- "nav-control" if current_user
- end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 8c5e258f519..083ace65b09 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -146,4 +146,8 @@ module NotesHelper
autocomplete: autocomplete
}
end
+
+ def discussion_resolved_intro(discussion)
+ discussion.resolved_by_push? ? 'Automatically resolved' : 'Resolved'
+ end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index b30b2eb1d03..5946c475835 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,7 +4,7 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
- if show_new_nav? && titles.any? && !defined?(@breadcrumb_title)
+ if titles.any? && !defined?(@breadcrumb_title)
@breadcrumb_title = @page_title.last
end
@@ -80,7 +80,9 @@ module PageLayoutHelper
@header_title = title
@header_title_url = title_url
else
- @header_title_url ? link_to(@header_title, @header_title_url) : @header_title
+ return @header_title unless @header_title_url
+
+ breadcrumb_list_item(link_to(@header_title, @header_title_url))
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0bf94fd30db..86665ea2aec 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -54,31 +54,28 @@ module ProjectsHelper
def project_title(project)
namespace_link =
if project.group
- group_title(project.group)
+ group_title(project.group, nil, nil)
else
owner = project.namespace.owner
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+ project_link = link_to project_path(project) do
output =
- if show_new_nav? && !Rails.env.test?
- project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
+ if project.avatar_url && !Rails.env.test?
+ project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15)
else
""
end
- output << simple_sanitize(project.name)
+ output << content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")
output.html_safe
end
- if current_user
- project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
- icon("chevron-down")
- end
- end
+ namespace_link = breadcrumb_list_item(namespace_link) unless project.group
+ project_link = breadcrumb_list_item project_link
- "#{namespace_link} / #{project_link}".html_safe
+ "#{namespace_link} #{project_link}".html_safe
end
def remove_project_message(project)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index ae0e0aa3cf9..98e824a8c65 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -10,6 +10,7 @@ module SearchHelper
search_pattern = Regexp.new(Regexp.escape(term), "i")
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
+ generic_results.concat(default_autocomplete_admin) if current_user.admin?
generic_results.select! { |result| result[:label] =~ search_pattern }
[
@@ -41,8 +42,14 @@ module SearchHelper
[
{ category: "Settings", label: "User settings", url: profile_path },
{ category: "Settings", label: "SSH Keys", url: profile_keys_path },
- { category: "Settings", label: "Dashboard", url: root_path },
- { category: "Settings", label: "Admin Section", url: admin_root_path }
+ { category: "Settings", label: "Dashboard", url: root_path }
+ ]
+ end
+
+ # Autocomplete results for settings pages, for admins
+ def default_autocomplete_admin
+ [
+ { category: "Settings", label: "Admin Section", url: admin_root_path }
]
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 99212a3438f..815fab9e061 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -10,4 +10,15 @@ module WikiHelper
.map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }
.join(' / ')
end
+
+ def wiki_breadcrumb_dropdown_links(page_slug)
+ page_slug_split = page_slug.split('/')
+ page_slug_split.pop(1)
+ current_slug = ""
+ page_slug_split
+ .map do |dir_or_page|
+ current_slug = "#{current_slug}#{dir_or_page}/"
+ add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
+ end
+ end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ba3156154ac..28c16d4037f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -451,6 +451,10 @@ module Ci
trace
end
+ def serializable_hash(options = {})
+ super(options).merge(when: read_attribute(:when))
+ end
+
private
def update_artifacts_size
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index c58ce5c3717..2c860598281 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -6,6 +6,10 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id
has_many :builds
+ # We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables.
+ # Ci::TriggerRequest doesn't save variables anymore.
+ validates :variables, absence: true
+
serialize :variables # rubocop:disable Cop/ActiveRecordSerialize
def user_variables
diff --git a/app/models/commit.rb b/app/models/commit.rb
index c943365016f..ba3845df867 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -405,6 +405,6 @@ class Commit
end
def gpg_commit
- @gpg_commit ||= Gitlab::Gpg::Commit.for_commit(self)
+ @gpg_commit ||= Gitlab::Gpg::Commit.new(self)
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 842c6e5cb50..f3888528940 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -38,6 +38,14 @@ class CommitStatus < ActiveRecord::Base
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
+ enum failure_reason: {
+ unknown_failure: nil,
+ script_failure: 1,
+ api_failure: 2,
+ stuck_or_timeout_failure: 3,
+ runner_system_failure: 4
+ }
+
state_machine :status do
event :process do
transition [:skipped, :manual] => :created
@@ -79,6 +87,11 @@ class CommitStatus < ActiveRecord::Base
commit_status.finished_at = Time.now
end
+ before_transition any => :failed do |commit_status, transition|
+ failure_reason = transition.args.first
+ commit_status.failure_reason = failure_reason
+ end
+
after_transition do |commit_status, transition|
next if transition.loopback?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 3731b7c8577..681c3241dbb 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,6 +6,7 @@
#
module Issuable
extend ActiveSupport::Concern
+ include Gitlab::SQL::Pattern
include CacheMarkdownField
include Participable
include Mentionable
@@ -122,7 +123,9 @@ module Issuable
#
# Returns an ActiveRecord::Relation.
def search(query)
- where(arel_table[:title].matches("%#{query}%"))
+ title = to_fuzzy_arel(:title, query)
+
+ where(title)
end
# Searches for records with a matching title or description.
@@ -133,10 +136,10 @@ module Issuable
#
# Returns an ActiveRecord::Relation.
def full_search(query)
- t = arel_table
- pattern = "%#{query}%"
+ title = to_fuzzy_arel(:title, query)
+ description = to_fuzzy_arel(:description, query)
- where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
+ where(title&.or(description))
end
def sort(method, excluded_labels: [])
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index dd979e7bb17..f006a271327 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -24,6 +24,7 @@ module ResolvableDiscussion
delegate :resolved_at,
:resolved_by,
+ :resolved_by_push?,
to: :last_resolved_note,
allow_nil: true
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 05eb6f86704..668c5a079e3 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -51,22 +51,34 @@ module ResolvableNote
end
# If you update this method remember to also update `.resolve!`
- def resolve!(current_user)
- return unless resolvable?
- return if resolved?
+ def resolve_without_save(current_user, resolved_by_push: false)
+ return false unless resolvable?
+ return false if resolved?
self.resolved_at = Time.now
self.resolved_by = current_user
- save!
+ self.resolved_by_push = resolved_by_push
+
+ true
end
# If you update this method remember to also update `.unresolve!`
- def unresolve!
- return unless resolvable?
- return unless resolved?
+ def unresolve_without_save
+ return false unless resolvable?
+ return false unless resolved?
self.resolved_at = nil
self.resolved_by = nil
- save!
+
+ true
+ end
+
+ def resolve!(current_user, resolved_by_push: false)
+ resolve_without_save(current_user, resolved_by_push: resolved_by_push) &&
+ save!
+ end
+
+ def unresolve!
+ unresolve_without_save && save!
end
end
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 3df60ddc950..1633acd4fa9 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -56,7 +56,7 @@ class GpgKey < ActiveRecord::Base
def verified_user_infos
user_infos.select do |user_info|
- user_info[:email] == user.email
+ user.verified_email?(user_info[:email])
end
end
@@ -64,13 +64,17 @@ class GpgKey < ActiveRecord::Base
user_infos.map do |user_info|
[
user_info[:email],
- user_info[:email] == user.email
+ user.verified_email?(user_info[:email])
]
end.to_h
end
def verified?
- emails_with_verified_status.any? { |_email, verified| verified }
+ emails_with_verified_status.values.any?
+ end
+
+ def verified_and_belongs_to_email?(email)
+ emails_with_verified_status.fetch(email, false)
end
def update_invalid_gpg_signatures
@@ -78,11 +82,14 @@ class GpgKey < ActiveRecord::Base
end
def revoke
- GpgSignature.where(gpg_key: self, valid_signature: true).update_all(
- gpg_key_id: nil,
- valid_signature: false,
- updated_at: Time.zone.now
- )
+ GpgSignature
+ .where(gpg_key: self)
+ .where.not(verification_status: GpgSignature.verification_statuses[:unknown_key])
+ .update_all(
+ gpg_key_id: nil,
+ verification_status: GpgSignature.verification_statuses[:unknown_key],
+ updated_at: Time.zone.now
+ )
destroy
end
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 50fb35c77ec..454c90d5fc4 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -1,9 +1,21 @@
class GpgSignature < ActiveRecord::Base
include ShaAttribute
+ include IgnorableColumn
+
+ ignore_column :valid_signature
sha_attribute :commit_sha
sha_attribute :gpg_key_primary_keyid
+ enum verification_status: {
+ unverified: 0,
+ verified: 1,
+ same_user_different_email: 2,
+ other_user: 3,
+ unverified_key: 4,
+ unknown_key: 5
+ }
+
belongs_to :project
belongs_to :gpg_key
@@ -20,6 +32,6 @@ class GpgSignature < ActiveRecord::Base
end
def gpg_commit
- Gitlab::Gpg::Commit.new(project, commit_sha)
+ Gitlab::Gpg::Commit.new(commit)
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 190b27cf66b..e746e4a12c9 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -16,6 +16,7 @@ class Group < Namespace
source: :user
has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
+ has_many :members_and_requesters, as: :source, class_name: 'GroupMember'
has_many :milestones
has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/member.rb b/app/models/member.rb
index ee2cb13697b..cbbd58f2eaf 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -126,20 +126,11 @@ class Member < ActiveRecord::Base
find_by(invite_token: invite_token)
end
- def add_user(source, user, access_level, current_user: nil, expires_at: nil)
- user = retrieve_user(user)
+ def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil)
+ # `user` can be either a User object, User ID or an email to be invited
+ member = retrieve_member(source, user, existing_members)
access_level = retrieve_access_level(access_level)
- # `user` can be either a User object or an email to be invited
- member =
- if user.is_a?(User)
- source.members.find_by(user_id: user.id) ||
- source.requesters.find_by(user_id: user.id) ||
- source.members.build(user_id: user.id)
- else
- source.members.build(invite_email: user)
- end
-
return member unless can_update_member?(current_user, member)
member.attributes = {
@@ -165,17 +156,15 @@ class Member < ActiveRecord::Base
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
return [] unless users.present?
- # Collect all user ids into separate array
- # so we can use single sql query to get user objects
- user_ids = users.select { |user| user =~ /\A\d+\Z/ }
- users = users - user_ids + User.where(id: user_ids)
+ emails, users, existing_members = parse_users_list(source, users)
self.transaction do
- users.map do |user|
+ (emails + users).map! do |user|
add_user(
source,
user,
access_level,
+ existing_members: existing_members,
current_user: current_user,
expires_at: expires_at
)
@@ -189,6 +178,31 @@ class Member < ActiveRecord::Base
private
+ def parse_users_list(source, list)
+ emails, user_ids, users = [], [], []
+ existing_members = {}
+
+ list.each do |item|
+ case item
+ when User
+ users << item
+ when Integer
+ user_ids << item
+ when /\A\d+\Z/
+ user_ids << item.to_i
+ when Devise.email_regexp
+ emails << item
+ end
+ end
+
+ if user_ids.present?
+ users.concat(User.where(id: user_ids))
+ existing_members = source.members_and_requesters.where(user_id: user_ids).index_by(&:user_id)
+ end
+
+ [emails, users, existing_members]
+ end
+
# This method is used to find users that have been entered into the "Add members" field.
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
def retrieve_user(user)
@@ -197,6 +211,20 @@ class Member < ActiveRecord::Base
User.find_by(id: user) || User.find_by(email: user) || user
end
+ def retrieve_member(source, user, existing_members)
+ user = retrieve_user(user)
+
+ if user.is_a?(User)
+ if existing_members
+ existing_members[user.id] || source.members.build(user_id: user.id)
+ else
+ source.members_and_requesters.find_or_initialize_by(user_id: user.id)
+ end
+ else
+ source.members.build(invite_email: user)
+ end
+ end
+
def retrieve_access_level(access_level)
access_levels.fetch(access_level) { access_level.to_i }
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 724fb4ccef1..b82f49d7073 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -918,6 +918,12 @@ class MergeRequest < ActiveRecord::Base
active_diff_discussions.each do |discussion|
service.execute(discussion)
end
+
+ if project.resolve_outdated_diff_discussions?
+ MergeRequests::ResolvedDiscussionNotificationService
+ .new(project, current_user)
+ .execute(self)
+ end
end
def keep_around_commit
diff --git a/app/models/project.rb b/app/models/project.rb
index 051c4c8e2ec..fdd516ec2ae 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
+ default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
@@ -144,6 +145,7 @@ class Project < ActiveRecord::Base
has_many :requesters, -> { where.not(requested_at: nil) },
as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :members_and_requesters, as: :source, class_name: 'ProjectMember'
has_many :deploy_keys_projects
has_many :deploy_keys, through: :deploy_keys_projects
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 05f2f851162..035f85a0b46 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -166,32 +166,25 @@ class Repository
end
def add_branch(user, branch_name, ref)
- newrev = commit(ref).try(:sha)
-
- return false unless newrev
-
- Gitlab::Git::OperationService.new(user, raw_repository).add_branch(branch_name, newrev)
+ branch = raw_repository.add_branch(branch_name, committer: user, target: ref)
after_create_branch
- find_branch(branch_name)
+
+ branch
+ rescue Gitlab::Git::Repository::InvalidRef
+ false
end
def add_tag(user, tag_name, target, message = nil)
- newrev = commit(target).try(:id)
- options = { message: message, tagger: user_to_committer(user) } if message
-
- return false unless newrev
-
- Gitlab::Git::OperationService.new(user, raw_repository).add_tag(tag_name, newrev, options)
-
- find_tag(tag_name)
+ raw_repository.add_tag(tag_name, committer: user, target: target, message: message)
+ rescue Gitlab::Git::Repository::InvalidRef
+ false
end
def rm_branch(user, branch_name)
before_remove_branch
- branch = find_branch(branch_name)
- Gitlab::Git::OperationService.new(user, raw_repository).rm_branch(branch)
+ raw_repository.rm_branch(branch_name, committer: user)
after_remove_branch
true
@@ -199,9 +192,8 @@ class Repository
def rm_tag(user, tag_name)
before_remove_tag
- tag = find_tag(tag_name)
- Gitlab::Git::OperationService.new(user, raw_repository).rm_tag(tag)
+ raw_repository.rm_tag(tag_name, committer: user)
after_remove_tag
true
diff --git a/app/models/user.rb b/app/models/user.rb
index 9d48c82e861..c5b5f09722f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1041,6 +1041,10 @@ class User < ActiveRecord::Base
ensure_rss_token!
end
+ def verified_email?(email)
+ self.email == email
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
index c495c3f39bb..255475e1fe6 100644
--- a/app/presenters/ci/build_presenter.rb
+++ b/app/presenters/ci/build_presenter.rb
@@ -17,5 +17,16 @@ module Ci
"Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
+
+ def trigger_variables
+ return [] unless trigger_request
+
+ @trigger_variables ||=
+ if pipeline.variables.any?
+ pipeline.variables.map(&:to_runner_variable)
+ else
+ trigger_request.user_variables
+ end
+ end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
deleted file mode 100644
index b2aa457bbd5..00000000000
--- a/app/services/ci/create_trigger_request_service.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# This class is deprecated because we're closing Ci::TriggerRequest.
-# New class is PipelineTriggerService (app/services/ci/pipeline_trigger_service.rb)
-# which is integrated with Ci::PipelineVariable instaed of Ci::TriggerRequest.
-# We remove this class after we removed v1 and v3 API. This class is still being
-# referred by such legacy code.
-module Ci
- module CreateTriggerRequestService
- Result = Struct.new(:trigger_request, :pipeline)
-
- def self.execute(project, trigger, ref, variables = nil)
- trigger_request = trigger.trigger_requests.create(variables: variables)
-
- pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref)
- .execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
-
- Result.new(trigger_request, pipeline)
- end
- end
-end
diff --git a/app/services/discussions/update_diff_position_service.rb b/app/services/discussions/update_diff_position_service.rb
index 1ef8d9edbe1..746f209e20f 100644
--- a/app/services/discussions/update_diff_position_service.rb
+++ b/app/services/discussions/update_diff_position_service.rb
@@ -10,6 +10,10 @@ module Discussions
discussion.notes.each do |note|
if outdated
note.change_position = position
+
+ if project.resolve_outdated_diff_discussions?
+ note.resolve_without_save(current_user, resolved_by_push: true)
+ end
else
note.position = position
note.change_position = nil
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index f6b83a2f621..d34903c9989 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -53,7 +53,7 @@ module Projects
log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest?
@status.description = message
- @status.drop
+ @status.drop(:script_failure)
super
end
diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml
index 13b583e6072..13c408914bb 100644
--- a/app/views/admin/applications/edit.html.haml
+++ b/app/views/admin/applications/edit.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Applications", admin_applications_path
+- breadcrumb_title @application.name
- page_title "Edit", @application.name, "Applications"
%h3.page-title Edit application
diff --git a/app/views/admin/cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml
index be8644c0ca6..bff53da1d9a 100644
--- a/app/views/admin/cohorts/index.html.haml
+++ b/app/views/admin/cohorts/index.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Cohorts"
- @no_container = true
= render "admin/dashboard/head"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 8e94e68bc11..069f8f89e0b 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Dashboard"
= render "admin/dashboard/head"
%div{ class: container_class }
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 2aadc071c75..3e02f7b1e16 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Groups", admin_groups_path
+- breadcrumb_title @group.name
- page_title @group.name, "Groups"
%h3.page-title
Group: #{@group.full_name}
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 665e8c7e74f..efb15ccc8df 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -1,3 +1,4 @@
+- add_to_breadcrumbs "System Hooks", admin_hooks_path
- page_title 'Edit System Hook'
%h3.page-title
Edit System Hook
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 09be17f07be..aa6e9db3900 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Jobs"
- @no_container = true
= render "admin/dashboard/head"
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 309aedceded..96f0d404ac4 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Labels", admin_labels_path
+- breadcrumb_title "Edit Label"
- page_title "Edit", @label.name, "Labels"
%h3.page-title
Edit Label
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 7b1b15cfeb8..ab4165c0bf2 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Projects", admin_projects_path
+- breadcrumb_title @project.name_with_namespace
- page_title @project.name_with_namespace, "Projects"
%h3.page-title
Project: #{@project.name_with_namespace}
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 126550ee10e..6793ce557c4 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Runners"
- @no_container = true
= render "admin/dashboard/head"
diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml
index 53d970e33c1..512176649e6 100644
--- a/app/views/admin/services/edit.html.haml
+++ b/app/views/admin/services/edit.html.haml
@@ -1,2 +1,4 @@
+- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
+- breadcrumb_title @service.title
- page_title @service.title, "Service Templates"
= render 'form'
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index b556ff056c0..98ff592eb64 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Users", admin_users_path
+- breadcrumb_title @user.name
- page_title @user.name, "Users"
= render 'admin/users/head'
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 5a379eae8f4..11bf3f5d323 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,4 +1,4 @@
-- if show_new_nav? && current_user.can_create_group?
+- if current_user.can_create_group?
- content_for :breadcrumbs_extra do
= link_to "New group", new_group_path, class: "btn btn-new"
@@ -10,8 +10,8 @@
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups
- .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
+ .nav-controls.nav-controls-new-nav
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
- = link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
+ = link_to "New group", new_group_path, class: "btn btn-new visible-xs"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 1f9a5b401b6..e2a1914ada2 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,7 +1,7 @@
= content_for :flash_message do
= render 'shared/project_limit'
-- if show_new_nav? && current_user.can_create_project?
+- if current_user.can_create_project?
- content_for :breadcrumbs_extra do
= link_to "New project", new_project_path, class: 'btn btn-new'
@@ -19,8 +19,8 @@
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore projects
- .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
+ .nav-controls.nav-controls-new-nav
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
- = link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
+ = link_to "New project", new_project_path, class: "btn btn-new visible-xs"
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index fd5389106bb..14c18678ab1 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,4 +1,4 @@
-- if show_new_nav? && current_user
+- if current_user
- content_for :breadcrumbs_extra do
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
@@ -10,7 +10,3 @@
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets
-
- - if current_user
- .nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) }
- = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 9ac44674b73..ad0e205a79f 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,15 +4,14 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
-- if show_new_nav?
- - content_for :breadcrumbs_extra do
- = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
- = icon('rss')
- = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
+- content_for :breadcrumbs_extra do
+ = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
+ = icon('rss')
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 960e1e55f36..ccc74f7cf3d 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,13 +2,12 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
-- if show_new_nav?
- - content_for :breadcrumbs_extra do
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
+- content_for :breadcrumbs_extra do
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index cb8bf57cba1..9fffdded1a0 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -2,14 +2,13 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
-- if show_new_nav?
- - content_for :breadcrumbs_extra do
- = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
+- content_for :breadcrumbs_extra do
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones
diff --git a/app/views/discussions/_headline.html.haml b/app/views/discussions/_headline.html.haml
index 25e90924413..b865eb815f0 100644
--- a/app/views/discussions/_headline.html.haml
+++ b/app/views/discussions/_headline.html.haml
@@ -1,9 +1,11 @@
- if discussion.resolved?
.discussion-headline-light.js-discussion-headline
- Resolved
+ = discussion_resolved_intro(discussion)
- if discussion.resolved_by
by
= link_to_member(@project, discussion.resolved_by, avatar: false)
+ - if discussion.resolved_by_push?
+ with a push
= time_ago_with_tooltip(discussion.resolved_at, placement: "bottom")
- elsif discussion.updated?
.discussion-headline-light.js-discussion-headline
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 9ebb3894c55..839f23e69fd 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "General Settings"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 837ef385dd5..13a4b4c90c9 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -8,7 +8,7 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
-- if show_new_nav? && group_issues_exists
+- if group_issues_exists
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
= icon('rss')
@@ -19,7 +19,7 @@
- if group_issues_exists
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn' do
= icon('rss')
%span.icon-label
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 50179a47797..9e59a09d459 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,5 +1,5 @@
- page_title 'Labels'
-- if show_new_nav? && can?(current_user, :admin_label, @group)
+- if can?(current_user, :admin_label, @group)
- content_for :breadcrumbs_extra do
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
@@ -10,7 +10,7 @@
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
- if can?(current_user, :admin_label, @group)
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 184df6f5406..0344770e0dd 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -4,7 +4,7 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
-- if show_new_nav? && current_user
+- if current_user
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
@@ -14,7 +14,7 @@
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- if current_user
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
= render 'shared/issuable/search_bar', type: :merge_requests
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 66c6cc9e279..6e7a1af243d 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Milestones"
-- if show_new_nav? && can?(current_user, :admin_milestones, @group)
+- if can?(current_user, :admin_milestones, @group)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
@@ -8,7 +8,7 @@
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
- if can?(current_user, :admin_milestones, @group)
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 7a2e688a114..7f3f2f707f7 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Projects"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index bf36baf48ab..9f9ae01e7c5 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -1,4 +1,5 @@
-- page_title "Pipelines"
+- breadcrumb_title "CI / CD Settings"
+- page_title "CI / CD"
= render "groups/settings_head"
= render 'ci/variables/index'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index e07f61c94e4..f4f76887422 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- breadcrumb_title "Group"
+- breadcrumb_title "Details"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
diff --git a/app/views/groups/subgroups.html.haml b/app/views/groups/subgroups.html.haml
index 8f0724c0677..7abc84412c6 100644
--- a/app/views/groups/subgroups.html.haml
+++ b/app/views/groups/subgroups.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Details"
- @no_container = true
= render 'head'
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 3babdae3968..34e85fef6d9 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -32,9 +32,9 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- - if show_new_nav?
- = stylesheet_link_tag "new_nav", media: "all"
- = stylesheet_link_tag "new_sidebar", media: "all"
+ // TODO: Combine these 2 stylesheets into application.scss
+ = stylesheet_link_tag "new_nav", media: "all"
+ = stylesheet_link_tag "new_sidebar", media: "all"
= Gon::Base.render_data
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c4f8cd71395..1fd301d6850 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,26 +1,14 @@
.page-with-sidebar{ class: page_with_sidebar_class }
- - if show_new_nav?
- - if defined?(nav) && nav
- = render "layouts/nav/#{nav}"
- - else
- - if defined?(nav) && nav
- .layout-nav
- .container-fluid
- = render "layouts/nav/#{nav}"
- - if content_for?(:sub_nav)
- = yield :sub_nav
- .content-wrapper{ class: layout_nav_class }
- - if show_new_nav?
- .mobile-overlay
+ - if defined?(nav) && nav
+ = render "layouts/nav/sidebar/#{nav}"
+ .content-wrapper.page-with-new-nav
+ .mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- - if show_new_nav?
- - if content_for?(:new_global_flash)
- = yield :new_global_flash
- - unless @hide_breadcrumbs
- = render "layouts/nav/breadcrumbs"
- = render "layouts/flash"
= yield :flash_message
+ - unless @hide_breadcrumbs
+ = render "layouts/nav/breadcrumbs"
+ = render "layouts/flash"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index ae9eee215e0..8595157a997 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,9 +1,6 @@
- page_title "Admin Area"
- header_title "Admin Area", admin_root_path
-- if show_new_nav?
- - nav "new_admin_sidebar"
- - @new_sidebar = true
-- else
- - nav "admin"
+- nav "admin"
+- @left_sidebar = true
= render template: "layouts/application"
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index b53f382fa3d..65ac8aaa59b 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -4,10 +4,7 @@
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar'
- - if show_new_nav?
- = render "layouts/header/new"
- - else
- = render "layouts/header/default", title: header_title
+ = render "layouts/header/default"
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 35abfa0e80c..08bd6fc311e 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,10 +1,7 @@
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
-- if show_new_nav?
- - nav "new_group_sidebar"
- - @new_sidebar = true
-- else
- - nav "group"
+- nav "group"
+- @left_sidebar = true
= render template: "layouts/application"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 1d875f81041..d8fc371497d 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,68 +1,50 @@
-%header.navbar.navbar-gitlab{ class: nav_header_class }
- .navbar-border
+%header.navbar.navbar-gitlab.navbar-gitlab-new
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
- .dropdown.global-dropdown
- %button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.sr-only Toggle navigation
- = icon('bars')
- .dropdown-menu-nav.global-dropdown-menu
- - if current_user
- = render 'layouts/nav/dashboard'
- - else
- = render 'layouts/nav/explore'
+ .title-container
+ %h1.title
+ = link_to root_path, title: 'Dashboard', id: 'logo' do
+ = brand_header_logo
+ %span.logo-text.hidden-xs
+ = render 'shared/logo_type.svg'
- .header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
- = brand_header_logo
-
- .title-container.js-title-container
- %h1.title{ class: ('initializing' if @has_group_title) }= title
+ - if current_user
+ = render "layouts/nav/dashboard"
+ - else
+ = render "layouts/nav/explore"
.navbar-collapse.collapse
%ul.nav.navbar-nav
+ - if current_user
+ = render 'layouts/header/new_dropdown'
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
- - if session[:impersonator_id]
- %li.impersonation
- = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
- = icon('user-secret fw')
- - if current_user.admin?
- %li
- = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('wrench fw')
- = render 'layouts/header/new_dropdown'
- - if Gitlab::Sherlock.enabled?
- %li
- = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('tachometer fw')
- %li
- = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ %li.user-counter
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
- %li
- = link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ %li.user-counter
+ = link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count)
- %li
+ %li.user-counter
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('check-circle fw')
+ = custom_icon('todo_done')
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
- = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
- = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
- = icon('caret-down')
+ = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
+ = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
+ = custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
@@ -74,18 +56,24 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
+ - if current_user
+ %li
+ = link_to "Help", help_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
+ - if session[:impersonator_id]
+ %li.impersonation
+ = link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = icon('user-secret')
- else
%li
%div
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
+ = link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
- %button.navbar-toggle{ type: 'button' }
+ %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
- = icon('ellipsis-v')
-
- = yield :header_content
+ = icon('ellipsis-v', class: 'js-navbar-toggle-right')
+ = icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
deleted file mode 100644
index c84d7053cd6..00000000000
--- a/app/views/layouts/header/_new.html.haml
+++ /dev/null
@@ -1,84 +0,0 @@
-%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class }
- %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
- .container-fluid
- .header-content
- .title-container
- %h1.title
- = link_to root_path, title: 'Dashboard', id: 'logo' do
- = brand_header_logo
- %span.logo-text.hidden-xs
- = render 'shared/logo_type.svg'
-
- - if current_user
- = render "layouts/nav/new_dashboard"
- - else
- = render "layouts/nav/new_explore"
-
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- = render 'layouts/search' unless current_controller?(:search)
- %li.visible-sm-inline-block.visible-xs-inline-block
- = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('search')
- - if current_user
- - if session[:impersonator_id]
- %li.impersonation
- = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
- = icon('user-secret fw')
- - if current_user.admin?
- %li
- = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('wrench fw')
- = render 'layouts/header/new_dropdown'
- - if Gitlab::Sherlock.enabled?
- %li
- = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('tachometer fw')
- %li
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = custom_icon('issues')
- - issues_count = assigned_issuables_count(:issues)
- %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
- = number_with_delimiter(issues_count)
- %li
- = link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = custom_icon('mr_bold')
- - merge_requests_count = assigned_issuables_count(:merge_requests)
- %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
- = number_with_delimiter(merge_requests_count)
- %li
- = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('check-circle fw')
- %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
- = todos_count_format(todos_pending_count)
- %li.header-user.dropdown
- = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
- = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
- = icon('chevron-down')
- .dropdown-menu-nav.dropdown-menu-align-right
- %ul
- %li.current-user
- .user-name.bold
- = current_user.name
- @#{current_user.username}
- %li.divider
- %li
- = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
- %li
- = link_to "Settings", profile_path
- %li.divider
- %li
- = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
- - else
- %li
- %div
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
-
- %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
- %span.sr-only Toggle navigation
- = icon('ellipsis-v', class: 'js-navbar-toggle-right')
- = icon('times', class: 'js-navbar-toggle-left')
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 9da739b0974..63d1c077ecd 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,11 +1,7 @@
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
- - if show_new_nav?
- = icon('plus')
- = icon('chevron-down')
- - else
- = icon('plus fw')
- = icon('caret-down')
+ = custom_icon('plus_square')
+ = custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
- if @group&.persisted?
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
deleted file mode 100644
index 6df0adfd742..00000000000
--- a/app/views/layouts/nav/_admin.html.haml
+++ /dev/null
@@ -1,40 +0,0 @@
-= render 'layouts/nav/admin_settings'
-.scrolling-tabs-container{ class: nav_control_class }
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
- %ul.nav-links.scrolling-tabs
- = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
- %span
- Overview
- = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
- = link_to admin_conversational_development_index_path, title: 'Monitoring' do
- %span
- Monitoring
- = nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Messages' do
- %span
- Messages
- = nav_link(controller: [:hooks, :hook_logs]) do
- = link_to admin_hooks_path, title: 'Hooks' do
- %span
- System Hooks
-
- = nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications' do
- %span
- Applications
-
- = nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse Reports" do
- %span
- Abuse Reports
- %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
-
- - if akismet_enabled?
- = nav_link(controller: :spam_logs) do
- = link_to admin_spam_logs_path, title: "Spam Logs" do
- %span
- Spam Logs
diff --git a/app/views/layouts/nav/_admin_settings.html.haml b/app/views/layouts/nav/_admin_settings.html.haml
deleted file mode 100644
index 9de0e12a826..00000000000
--- a/app/views/layouts/nav/_admin_settings.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-.controls
- .dropdown.admin-settings-dropdown
- %a.dropdown-new.btn.btn-default{ href: '#', 'data-toggle' => 'dropdown' }
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- = nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
- %span
- Deploy Keys
-
- = nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates' do
- %span
- Service Templates
-
- = nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels' do
- %span
- Labels
-
- = nav_link(controller: :appearances) do
- = link_to admin_appearances_path, title: 'Appearances' do
- %span
- Appearance
-
- %li.divider
- = nav_link(controller: :application_settings) do
- = link_to admin_application_settings_path, title: 'Settings' do
- %span
- Settings
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index 653452871a0..feffd7707dc 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -1,28 +1,22 @@
-- breadcrumb_link = breadcrumb_title_link
- container = @no_breadcrumb_container ? 'container-fluid' : container_class
- hide_top_links = @hide_top_links || false
-%nav.breadcrumbs{ role: "navigation" }
+%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container{ class: [container, @content_class] }
- - if defined?(@new_sidebar)
+ - if defined?(@left_sidebar)
= button_tag class: 'toggle-mobile-nav', type: 'button' do
%span.sr-only Open sidebar
= icon ('bars')
.breadcrumbs-links.js-title-container
- - unless hide_top_links
- .title
- = link_to "GitLab", root_path
- \/
- - if content_for?(:header_title_before)
- = yield :header_title_before
- \/
+ %ul.list-unstyled.breadcrumbs-list.js-breadcrumbs-list
+ - unless hide_top_links
= header_title
- %h2.breadcrumbs-sub-title
- %ul.list-unstyled
- - if @breadcrumbs_extra_links
- - @breadcrumbs_extra_links.each do |extra|
- %li= link_to extra[:text], extra[:link]
- %li= link_to @breadcrumb_title, breadcrumb_link
+ - if @breadcrumbs_extra_links
+ - @breadcrumbs_extra_links.each do |extra|
+ = breadcrumb_list_item link_to(extra[:text], extra[:link])
+ = render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after
+ %li
+ %h2.breadcrumbs-sub-title= @breadcrumb_title
- if content_for?(:breadcrumbs_extra)
.breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
= yield :header_content
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index be7d27df2a0..8a39c4d775f 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,67 +1,62 @@
-%ul
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
- = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- P
- %span
- Projects
- = nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- A
- %span
- Activity
- - if koding_enabled?
- = nav_link(controller: :koding) do
- = link_to koding_path, title: 'Koding' do
- %span
- Koding
- = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+%ul.list-unstyled.navbar-sub-nav
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown" }) do
+ %a{ href: "#", data: { toggle: "dropdown" } }
+ Projects
+ = custom_icon('caret_down')
+ .dropdown-menu.projects-dropdown-menu
+ = render "layouts/nav/projects_dropdown/show"
+
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs" }) do
= link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- G
- %span
- Groups
- = nav_link(controller: 'dashboard/milestones') do
+ Groups
+
+ = nav_link(path: 'dashboard#activity', html_options: { class: "visible-lg" }) do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+ Activity
+
+ = nav_link(controller: 'dashboard/milestones', html_options: { class: "visible-lg" }) do
= link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- L
- %span
- Milestones
- = nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- I
- %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:issues))
- %span
- Issues
- = nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- M
- %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:merge_requests))
- %span
- Merge Requests
- = nav_link(controller: 'dashboard/snippets') do
+ Milestones
+
+ = nav_link(controller: 'dashboard/snippets', html_options: { class: "visible-lg" }) do
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- S
- %span
- Snippets
- %li.divider
- %li
- = link_to "Help", help_path, title: 'About GitLab CE', class: 'about-gitlab'
+ Snippets
+
+ %li.dropdown.hidden-lg
+ %a{ href: "#", data: { toggle: "dropdown" } }
+ More
+ = custom_icon('caret_down')
+ .dropdown-menu
+ %ul
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "visible-xs" }) do
+ = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
+ Groups
+
+ = nav_link(path: 'dashboard#activity') do
+ = link_to activity_dashboard_path, title: 'Activity' do
+ Activity
+
+ = nav_link(controller: 'dashboard/milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
+ Milestones
+
+ = nav_link(controller: 'dashboard/snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
+ Snippets
+
+ -# Shortcut to Dashboard > Projects
+ %li.hidden
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ Projects
+
+ - if current_user.admin? || Gitlab::Sherlock.enabled?
+ %li.line-separator.hidden-xs
+ - if current_user.admin?
+ = nav_link(controller: 'admin/dashboard') do
+ = link_to admin_root_path, class: 'admin-icon', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('wrench fw')
+ - if Gitlab::Sherlock.enabled?
+ %li
+ = link_to sherlock_transactions_path, class: 'admin-icon', title: 'Sherlock Transactions',
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('tachometer fw')
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 0cb367452f7..cd1c39f3226 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,30 +1,12 @@
-%ul
+%ul.list-unstyled.navbar-sub-nav
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- P
- %span
- Projects
+ Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- G
- %span
- Groups
+ Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do
- .shortcut-mappings
- .key
- = icon('arrow-up', 'aria-label' => 'hidden')
- S
- %span
- Snippets
- %li.divider
- = nav_link(controller: :help) do
- = link_to help_path, title: 'Help' do
- %span
- Help
+ Snippets
+ %li
+ = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
deleted file mode 100644
index 261445ecd2b..00000000000
--- a/app/views/layouts/nav/_group.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-.scrolling-tabs-container{ class: nav_control_class }
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
- %ul.nav-links.scrolling-tabs
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Home' do
- %span
- Group
- = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
- = link_to issues_group_path(@group), title: 'Issues' do
- %span
- Issues
- - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.badge.count= number_with_delimiter(issues.count)
- = nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
- %span
- Merge Requests
- - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
- %span.badge.count= number_with_delimiter(merge_requests.count)
- = nav_link(path: 'group_members#index') do
- = link_to group_group_members_path(@group), title: 'Members' do
- %span
- Members
- - if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
- = link_to edit_group_path(@group), title: 'Settings' do
- %span
- Settings
diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml
deleted file mode 100644
index cfdfcbebc9f..00000000000
--- a/app/views/layouts/nav/_new_dashboard.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-%ul.list-unstyled.navbar-sub-nav
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "home"}) do
- = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- Projects
-
- = nav_link(controller: ['dashboard/groups', 'explore/groups']) do
- = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
- Groups
-
- = nav_link(path: 'dashboard#activity', html_options: { class: "hidden-xs hidden-sm" }) do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- Activity
-
- %li.dropdown
- %a{ href: "#", data: { toggle: "dropdown" } }
- More
- = icon("chevron-down", class: "dropdown-chevron")
- .dropdown-menu
- %ul
- = nav_link(path: 'dashboard#activity', html_options: { class: "visible-xs visible-sm" }) do
- = link_to activity_dashboard_path, title: 'Activity' do
- Activity
-
- = nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
- Milestones
-
- = nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
- Snippets
- %li.divider
- %li
- = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_new_explore.html.haml b/app/views/layouts/nav/_new_explore.html.haml
deleted file mode 100644
index 40385f251e3..00000000000
--- a/app/views/layouts/nav/_new_explore.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%ul.list-unstyled.navbar-sub-nav
- = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
- = link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- Projects
- = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
- = link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do
- Groups
- %li.dropdown
- %a{ href: "#", data: { toggle: "dropdown" } }
- More
- = icon("chevron-down", class: "dropdown-chevron")
- .dropdown-menu
- %ul
- = nav_link(controller: :snippets) do
- = link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do
- Snippets
- %li.divider
- %li
- = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
deleted file mode 100644
index 448f6abedf2..00000000000
--- a/app/views/layouts/nav/_profile.html.haml
+++ /dev/null
@@ -1,57 +0,0 @@
-.scrolling-tabs-container
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
- %ul.nav-links.scrolling-tabs
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: 'Profile Settings' do
- %span
- Profile
- = nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account' do
- %span
- Account
- - if current_application_settings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
- %span
- Applications
- = nav_link(controller: :chat_names) do
- = link_to profile_chat_names_path, title: 'Chat' do
- %span
- Chat
- = nav_link(controller: :personal_access_tokens) do
- = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
- %span
- Access Tokens
- = nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails' do
- %span
- Emails
- - unless current_user.ldap_user?
- = nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password' do
- %span
- Password
- = nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications' do
- %span
- Notifications
-
- = nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys' do
- %span
- SSH Keys
- = nav_link(controller: :gpg_keys) do
- = link_to profile_gpg_keys_path, title: 'GPG Keys' do
- %span
- GPG Keys
- = nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences' do
- %span
- Preferences
- = nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Authentication log' do
- %span
- Authentication log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
deleted file mode 100644
index b88465848e3..00000000000
--- a/app/views/layouts/nav/_project.html.haml
+++ /dev/null
@@ -1,111 +0,0 @@
-- can_edit = can?(current_user, :admin_project, @project)
-.scrolling-tabs-container{ class: nav_control_class }
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
- %ul.nav-links.scrolling-tabs
- = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
- %span
- Project
-
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
- = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
- %span
- Repository
-
- - if project_nav_tab? :container_registry
- = nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
- %span
- Registry
-
- - if project_nav_tab? :issues
- = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
- %span
- Issues
- - if @project.issues_enabled?
- %span.badge.count.issue_counter
- = number_with_delimiter(@project.open_issues_count)
-
- - if project_nav_tab? :merge_requests
- - controllers = [:merge_requests, 'projects/merge_requests/conflicts']
- - controllers.push(:merge_requests, :labels, :milestones) unless @project.issues_enabled?
- = nav_link(controller: controllers) do
- = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
- %span
- Merge Requests
- %span.badge.count.merge_counter.js-merge-counter
- = number_with_delimiter(@project.open_merge_requests_count)
-
- - if project_nav_tab? :pipelines
- = nav_link(controller: [:pipelines, :builds, :environments, :artifacts]) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
-
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
- %span
- Wiki
-
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
- %span
- Snippets
-
- - if project_nav_tab? :project_members
- = nav_link(controller: :project_members) do
- = link_to project_project_members_path(@project), title: 'Members', class: 'shortcuts-members' do
- %span
- Members
-
- - if project_nav_tab? :settings
- = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
- = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
- %span
- Settings
-
- -# Shortcut to Project > Activity
- %li.hidden
- = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
- %span
- Activity
-
- -# Shortcut to Repository > Graph (formerly, Network)
- - if project_nav_tab? :network
- %li.hidden
- = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
- Graph
-
- -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- - unless @project.empty_repo?
- %li.hidden
- = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
- Charts
-
- -# Shortcut to Issues > New Issue
- %li.hidden
- = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
- Create a new issue
-
- -# Shortcut to Pipelines > Jobs
- - if project_nav_tab? :builds
- %li.hidden
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
- Jobs
-
- -# Shortcut to commits page
- - if project_nav_tab? :commits
- %li.hidden
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
- Commits
-
- -# Shortcut to issue boards
- %li.hidden
- = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml b/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml
new file mode 100644
index 00000000000..28022eebb19
--- /dev/null
+++ b/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml
@@ -0,0 +1,11 @@
+- dropdown_location = local_assigns.fetch(:location, nil)
+- button_tooltip = local_assigns.fetch(:title, _("Show parent pages"))
+- if defined?(@breadcrumb_dropdown_links) && @breadcrumb_dropdown_links.key?(dropdown_location)
+ %li.dropdown
+ %button.text-expander.has-tooltip.js-breadcrumbs-collapsed-expander{ type: "button", data: { toggle: "dropdown", container: "body" }, "aria-label": button_tooltip, title: button_tooltip }
+ = icon("ellipsis-h")
+ = icon("angle-right", class: "breadcrumbs-list-angle")
+ .dropdown-menu
+ %ul
+ - @breadcrumb_dropdown_links[dropdown_location].each_with_index do |link, index|
+ %li{ style: "text-indent: #{[index * 16, 60].min}px;" }= link
diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml
new file mode 100644
index 00000000000..a7370180bf6
--- /dev/null
+++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml
@@ -0,0 +1,15 @@
+- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: @project.web_url, avatar_url: @project.avatar_url } if @project&.persisted?
+.projects-dropdown-container
+ .project-dropdown-sidebar
+ %ul
+ = nav_link(path: 'dashboard/projects#index') do
+ = link_to dashboard_projects_path do
+ = _('Your projects')
+ = nav_link(path: 'projects#starred') do
+ = link_to starred_dashboard_projects_path do
+ = _('Starred projects')
+ = nav_link(path: 'projects#trending') do
+ = link_to explore_root_path do
+ = _('Explore projects')
+ .project-dropdown-content
+ #js-projects-dropdown{ data: { user_name: current_user.username, project: project_meta } }
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 3b53117deb6..3b53117deb6 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 5a1511b262f..5a1511b262f 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index ccb6d1492f1..ccb6d1492f1 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 760c4c97c33..760c4c97c33 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index c365839e605..67aa05b655c 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,10 +1,7 @@
- page_title "User Settings"
- header_title "User Settings", profile_path unless header_title
- sidebar "dashboard"
-- if show_new_nav?
- - nav "new_profile_sidebar"
- - @new_sidebar = true
-- else
- - nav "profile"
+- nav "profile"
+- @left_sidebar = true
= render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 54d56e9b873..6b847fb4b7c 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,11 +1,8 @@
- page_title @project.name_with_namespace
- page_description @project.description unless page_description
- header_title project_title(@project) unless header_title
-- if show_new_nav?
- - nav "new_project_sidebar"
- - @new_sidebar = true
-- else
- - nav "project"
+- nav "project"
+- @left_sidebar = true
- content_for :project_javascripts do
- project = @target_project || @project
@@ -14,12 +11,4 @@
:javascript
window.uploads_path = "#{project_uploads_path(project)}";
-- content_for :header_content do
- .js-dropdown-menu-projects
- .dropdown-menu.dropdown-select.dropdown-menu-projects
- = dropdown_title("Go to a project")
- = dropdown_filter("Search your projects")
- = dropdown_content
- = dropdown_loading
-
= render template: "layouts/application"
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 985bb79508f..c606b5a1e6c 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Edit Password"
- page_title "Password"
- @content_class = "limit-container-width" unless fluid_layout
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 2216708d354..06bb72b9f0d 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Access Tokens"
- page_title "Personal Access Tokens"
- @content_class = "limit-container-width" unless fluid_layout
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index a8ae0b92334..aa8d5a8bc1a 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,4 +1,4 @@
-- breadcrumb_title "Profile"
+- breadcrumb_title "Edit Profile"
- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 33e062c1c9c..0b03276efcc 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,8 +1,5 @@
- page_title 'Two-Factor Authentication', 'Account'
-- if show_new_nav?
- - add_to_breadcrumbs("Account", profile_account_path)
-- else
- - header_title "Two-Factor Authentication", profile_two_factor_auth_path
+- add_to_breadcrumbs("Account", profile_account_path)
- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index f47d84ef755..0175b519867 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -1,7 +1,6 @@
- project = local_assigns.fetch(:project)
-- flash_message_container = show_new_nav? ? :new_global_flash : :flash_message
-= content_for flash_message_container do
+= content_for :flash_message do
= render partial: 'deletion_failed', locals: { project: project }
- if current_user && can?(current_user, :download_code, project)
= render 'shared/no_ssh'
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index 61420fd0fb6..e3effe45d6c 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -14,6 +14,10 @@
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
%strong Only allow merge requests to be merged if all discussions are resolved
.checkbox
+ = form.label :resolve_outdated_diff_discussions do
+ = form.check_box :resolve_outdated_diff_discussions
+ %strong Automatically resolve merge request diff discussions when they become outdated
+ .checkbox
= form.label :printing_merge_request_link_enabled do
= form.check_box :printing_merge_request_link_enabled
%strong Show link to create/view merge request when pushing from the command line
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 5452c6db6a6..f80dadb8037 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,9 +1,7 @@
- @no_container = true
-- if show_new_nav?
- - add_to_breadcrumbs(_("Project"), project_path(@project))
-
- page_title _("Activity")
+
= render "projects/head"
= render 'projects/last_push'
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index a33743c2f57..4cc3218d967 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,8 +1,12 @@
+- breadcrumb_title _('Artifacts')
- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
= render "projects/pipelines/head"
= render "projects/jobs/header", show_controls: false
+- add_to_breadcrumbs(_('Jobs'), project_jobs_path(@project))
+- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
+
.tree-holder
.nav-block
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 5354ec8522e..303e20e8780 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -1,11 +1,9 @@
- @no_breadcrumb_container = true
- @no_container = true
- @content_class = "issue-boards-content"
+- breadcrumb_title "Issue Board"
- page_title "Boards"
-- if show_new_nav?
- - add_to_breadcrumbs("Issues", project_issues_path(@project))
-
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 945a5c11d6d..73583c6bbc2 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -2,9 +2,6 @@
- page_title "Branches"
= render "projects/commits/head"
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
-
%div{ class: container_class }
.top-area.adjust
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/commit/_invalid_signature_badge.html.haml b/app/views/projects/commit/_invalid_signature_badge.html.haml
deleted file mode 100644
index 3a73aae9d95..00000000000
--- a/app/views/projects/commit/_invalid_signature_badge.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- title = capture do
- .gpg-popover-icon.invalid
- = render 'shared/icons/icon_status_notfound_borderless.svg'
- %div
- This commit was signed with an <strong>unverified</strong> signature.
-
-- locals = { signature: signature, title: title, label: 'Unverified', css_classes: ['invalid'] }
-
-= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_other_user_signature_badge.html.haml b/app/views/projects/commit/_other_user_signature_badge.html.haml
new file mode 100644
index 00000000000..80eca96f7ce
--- /dev/null
+++ b/app/views/projects/commit/_other_user_signature_badge.html.haml
@@ -0,0 +1,6 @@
+- title = capture do
+ This commit was signed with a different user's verified signature.
+
+- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless', show_user: true }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml
new file mode 100644
index 00000000000..e737de48e22
--- /dev/null
+++ b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml
@@ -0,0 +1,7 @@
+- title = capture do
+ This commit was signed with a verified signature, but the committer email
+ is <strong>not verified</strong> to belong to the same user.
+
+- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'icon_status_notfound_borderless', show_user: true }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_signature.html.haml b/app/views/projects/commit/_signature.html.haml
index 60fa52557ef..145bc629380 100644
--- a/app/views/projects/commit/_signature.html.haml
+++ b/app/views/projects/commit/_signature.html.haml
@@ -1,5 +1,2 @@
- if signature
- - if signature.valid_signature?
- = render partial: 'projects/commit/valid_signature_badge', locals: { signature: signature }
- - else
- = render partial: 'projects/commit/invalid_signature_badge', locals: { signature: signature }
+ = render partial: "projects/commit/#{signature.verification_status}_signature_badge", locals: { signature: signature }
diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml
index d06b29db838..edff018ba6d 100644
--- a/app/views/projects/commit/_signature_badge.html.haml
+++ b/app/views/projects/commit/_signature_badge.html.haml
@@ -1,17 +1,27 @@
-- css_classes = commit_signature_badge_classes(css_classes)
+- signature = local_assigns.fetch(:signature)
+- title = local_assigns.fetch(:title)
+- label = local_assigns.fetch(:label)
+- css_class = local_assigns.fetch(:css_class)
+- icon = local_assigns.fetch(:icon)
+- show_user = local_assigns.fetch(:show_user, false)
+
+- css_classes = commit_signature_badge_classes(css_class)
- title = capture do
.gpg-popover-status
- = title
+ .gpg-popover-icon{ class: css_class }
+ = render "shared/icons/#{icon}.svg"
+ %div
+ = title
- content = capture do
- .clearfix
- = content
+ - if show_user
+ .clearfix
+ = render partial: 'projects/commit/signature_badge_user', locals: { signature: signature }
GPG Key ID:
%span.monospace= signature.gpg_key_primary_keyid
-
= link_to('Learn more about signing commits', help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
%button{ class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } }
diff --git a/app/views/projects/commit/_signature_badge_user.html.haml b/app/views/projects/commit/_signature_badge_user.html.haml
new file mode 100644
index 00000000000..b20198e76db
--- /dev/null
+++ b/app/views/projects/commit/_signature_badge_user.html.haml
@@ -0,0 +1,21 @@
+- gpg_key = signature.gpg_key
+- user = gpg_key&.user
+- user_name = signature.gpg_key_user_name
+- user_email = signature.gpg_key_user_email
+
+- if user
+ = link_to user_path(user), class: 'gpg-popover-user-link' do
+ %div
+ = user_avatar_without_link(user: user, size: 32)
+
+ %div
+ %strong= user.name
+ %div= user.to_reference
+- else
+ = mail_to user_email do
+ %div
+ = user_avatar_without_link(user_name: user_name, user_email: user_email, size: 32)
+
+ %div
+ %strong= user_name
+ %div= user_email
diff --git a/app/views/projects/commit/_unknown_key_signature_badge.html.haml b/app/views/projects/commit/_unknown_key_signature_badge.html.haml
new file mode 100644
index 00000000000..75c5cf57bcc
--- /dev/null
+++ b/app/views/projects/commit/_unknown_key_signature_badge.html.haml
@@ -0,0 +1 @@
+= render partial: 'projects/commit/unverified_signature_badge', locals: { signature: signature }
diff --git a/app/views/projects/commit/_unverified_key_signature_badge.html.haml b/app/views/projects/commit/_unverified_key_signature_badge.html.haml
new file mode 100644
index 00000000000..75c5cf57bcc
--- /dev/null
+++ b/app/views/projects/commit/_unverified_key_signature_badge.html.haml
@@ -0,0 +1 @@
+= render partial: 'projects/commit/unverified_signature_badge', locals: { signature: signature }
diff --git a/app/views/projects/commit/_unverified_signature_badge.html.haml b/app/views/projects/commit/_unverified_signature_badge.html.haml
new file mode 100644
index 00000000000..1af58027b83
--- /dev/null
+++ b/app/views/projects/commit/_unverified_signature_badge.html.haml
@@ -0,0 +1,6 @@
+- title = capture do
+ This commit was signed with an <strong>unverified</strong> signature.
+
+- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless' }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_valid_signature_badge.html.haml b/app/views/projects/commit/_valid_signature_badge.html.haml
deleted file mode 100644
index db1a41bbf64..00000000000
--- a/app/views/projects/commit/_valid_signature_badge.html.haml
+++ /dev/null
@@ -1,32 +0,0 @@
-- title = capture do
- .gpg-popover-icon.valid
- = render 'shared/icons/icon_status_success_borderless.svg'
- %div
- This commit was signed with a <strong>verified</strong> signature.
-
-- content = capture do
- - gpg_key = signature.gpg_key
- - user = gpg_key&.user
- - user_name = signature.gpg_key_user_name
- - user_email = signature.gpg_key_user_email
-
- - if user
- = link_to user_path(user), class: 'gpg-popover-user-link' do
- %div
- = user_avatar_without_link(user: user, size: 32)
-
- %div
- %strong= gpg_key.user.name
- %div @#{gpg_key.user.username}
- - else
- = mail_to user_email do
- %div
- = user_avatar_without_link(user_name: user_name, user_email: user_email, size: 32)
-
- %div
- %strong= user_name
- %div= user_email
-
-- locals = { signature: signature, title: title, content: content, label: 'Verified', css_classes: ['valid'] }
-
-= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_verified_signature_badge.html.haml b/app/views/projects/commit/_verified_signature_badge.html.haml
new file mode 100644
index 00000000000..423beba2120
--- /dev/null
+++ b/app/views/projects/commit/_verified_signature_badge.html.haml
@@ -0,0 +1,7 @@
+- title = capture do
+ This commit was signed with a <strong>verified</strong> signature and the
+ committer email is verified to belong to the same user.
+
+- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'icon_status_success_borderless', show_user: true }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 07c83c0a590..717de85c5d2 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Commits", project_commits_path(@project)
+- breadcrumb_title @commit.short_id
- container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
- limited_container_width = fluid_layout ? '' : 'limit-container-width'
- @content_class = limited_container_width
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 7ae56086177..e873b931683 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -5,9 +5,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
-
= content_for :sub_nav do
= render "head"
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 05de21e8dbf..2632fea6eba 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
+- breadcrumb_title "Compare Revisions"
- page_title "Compare"
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
%div{ class: container_class }
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 8bc863f77b3..7cc42455394 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,8 +1,6 @@
- @no_container = true
-- breadcrumb_title "Compare"
+- add_to_breadcrumbs "Compare Revisions", project_compare_index_path(@project)
- page_title "#{params[:from]}...#{params[:to]}"
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
%div{ class: container_class }
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 3467e357c49..8d008be5aae 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,7 +1,5 @@
- @no_container = true
- page_title "Cycle Analytics"
-- if show_new_nav?
- - add_to_breadcrumbs("Project", project_path(@project))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics')
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 9e26bdecd31..994119051d2 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "General Settings"
- page_title "General"
- @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test?
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index d17709380d5..5e980314307 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Details"
= render partial: 'flash_messages', locals: { project: @project }
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index d0f723af5bf..acc80b49dd0 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -1,10 +1,8 @@
- @no_container = true
- page_title "Environments"
+- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
= render "projects/pipelines/head"
-- if show_new_nav?
- - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
-
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments")
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 0ce0f5465fc..c35d1b5aaee 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Environments", project_environments_path(@project)
+- breadcrumb_title @environment.name
- page_title "Environments"
= render "projects/pipelines/head"
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index 9f5a1239a82..f0ef647ddb3 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -1,7 +1,5 @@
- @no_container = true
- page_title "Charts"
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_d3')
= webpack_bundle_tag('graphs')
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index f41a0d8293b..08b38428b50 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -5,9 +5,6 @@
= webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_show')
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
-
= render 'projects/commits/head'
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 34d5a3e1831..6fb5aa45166 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -4,4 +4,4 @@
= render 'shared/empty_states/issues'
- if @issues.present?
- = paginate @issues, theme: "gitlab"
+ = paginate @issues, theme: "gitlab", total_pages: @total_pages
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index aacb057840d..6fcb5975707 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -13,15 +13,14 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
-- if show_new_nav?
- - content_for :breadcrumbs_extra do
- = render "projects/issues/nav_btns"
+- content_for :breadcrumbs_extra do
+ = render "projects/issues/nav_btns"
- if project_issues(@project).exists?
%div{ class: (container_class) }
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index fd7ff176c5e..fbaf88356bf 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,4 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
+- add_to_breadcrumbs "Issues", project_issues_path(@project)
+- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
@@ -37,8 +39,7 @@
%ul
- if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue)
- / TODO: simplify condition back #36860
- - if @issue.author && current_user != @issue.author
+ - unless current_user == @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue
%li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close js-btn-issue-action #{issue_button_visibility(@issue, true)}", title: 'Close issue'
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index f5d5bc7eda9..43e23bb2200 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -46,14 +46,14 @@
%span.build-light-text Token:
#{@build.trigger_request.trigger.short_token}
- - if @build.trigger_request.variables
+ - if @build.trigger_variables.any?
%p
%button.btn.group.btn-group-justified.reveal-variables Reveal Variables
%dl.js-build-variables.trigger-build-variables.hide
- - @build.trigger_request.variables.each do |key, value|
- %dt.js-build-variable.trigger-build-variable= key
- %dd.js-build-value.trigger-build-value= value
+ - @build.trigger_variables.each do |trigger_variable|
+ %dt.js-build-variable.trigger-build-variable= trigger_variable[:key]
+ %dd.js-build-value.trigger-build-value= trigger_variable[:value]
%div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
%p
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index d78891546f7..8604c7d3ea4 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -2,9 +2,6 @@
- page_title "Jobs"
= render "projects/pipelines/head"
-- if show_new_nav?
- - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
-
%div{ class: container_class }
.top-area
- build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index fa086413fbe..975c08c06e6 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Jobs", project_jobs_path(@project)
+- breadcrumb_title "##{@build.id}"
- page_title "#{@build.name} (##{@build.id})", "Jobs"
= render "projects/pipelines/head"
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 4b9da02c6b8..ec9e8444ac5 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,7 +3,7 @@
- hide_class = ''
- can_admin_label = can?(current_user, :admin_label, @project)
-- if show_new_nav? && can?(current_user, :admin_label, @project)
+- if can?(current_user, :admin_label, @project)
- content_for :breadcrumbs_extra do
= link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new"
@@ -18,7 +18,7 @@
Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- if can_admin_label
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= link_to new_project_label_path(@project), class: "btn btn-new" do
New label
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 4e97f74dd6a..bd6f1c05949 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -5,4 +5,4 @@
= render 'shared/empty_states/merge_requests'
- if @merge_requests.present?
- = paginate @merge_requests, theme: "gitlab"
+ = paginate @merge_requests, theme: "gitlab", total_pages: @total_pages
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index c020e7db380..27c3002366b 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -12,9 +12,8 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
-- if show_new_nav?
- - content_for :breadcrumbs_extra do
- = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
+- content_for :breadcrumbs_extra do
+ = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'projects/last_push'
@@ -22,7 +21,7 @@
%div{ class: container_class }
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'shared/issuable/search_bar', type: :merge_requests
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index d27e121beb4..c2d16f7e731 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -1,4 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
+- add_to_breadcrumbs "Merge Requests", project_merge_requests_path(@project)
+- breadcrumb_title @merge_request.to_reference
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index e0b29b0c2e1..71ec88ef1c1 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title 'Milestones'
-- if show_new_nav? && can?(current_user, :admin_milestone, @project)
+- if can?(current_user, :admin_milestone, @project)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone'
@@ -11,10 +11,10 @@
.top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
- .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
+ .nav-controls.nav-controls-new-nav
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: "btn btn-new #{("visible-xs" if show_new_nav?)}", title: 'New milestone' do
+ = link_to new_project_milestone_path(@project), class: "btn btn-new visible-xs", title: 'New milestone' do
New milestone
.milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 0bf0e11c107..1f5f18801ad 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
+- breadcrumb_title @milestone.title
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
= render "shared/mr_head"
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index ab948df4a3f..e29cb277389 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -2,8 +2,6 @@
- page_title "Graph", @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('network')
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
= render "head"
%div{ class: container_class }
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index b04f5efe1f9..fb07141d2ac 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -31,7 +31,7 @@
%template{ 'v-if' => 'isResolved' }
= render 'shared/icons/icon_status_success_solid.svg'
%template{ 'v-else' => '' }
- = render 'shared/icons/icon_status_success.svg'
+ = render 'shared/icons/icon_resolve_discussion.svg'
- if current_user
- if note.emoji_awardable?
diff --git a/app/views/projects/pipeline_schedules/edit.html.haml b/app/views/projects/pipeline_schedules/edit.html.haml
index 9b2a7b5821d..d95fa6da903 100644
--- a/app/views/projects/pipeline_schedules/edit.html.haml
+++ b/app/views/projects/pipeline_schedules/edit.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs _("Schedules"), pipeline_schedules_path(@project)
+- breadcrumb_title "##{@schedule.id}"
- page_title _("Edit"), @schedule.description, _("Pipeline Schedule")
%h3.page-title
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 8426b29bb14..d9957b54a4d 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -1,4 +1,4 @@
-- breadcrumb_title "Schedules"
+- breadcrumb_title _("Schedules")
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
@@ -7,12 +7,10 @@
- @no_container = true
- page_title _("Pipeline Schedules")
-- if show_new_nav? && can?(current_user, :create_pipeline_schedule, @project)
+- if can?(current_user, :create_pipeline_schedule, @project)
- content_for :breadcrumbs_extra do
= link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create'
- - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
-
= render "projects/pipelines/head"
%div{ class: container_class }
@@ -22,7 +20,7 @@
= render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
- if can?(current_user, :create_pipeline_schedule, @project)
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
= link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
%span= _('New schedule')
diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml
index c7237cb96d8..cfdaf6d43bb 100644
--- a/app/views/projects/pipeline_schedules/new.html.haml
+++ b/app/views/projects/pipeline_schedules/new.html.haml
@@ -2,8 +2,7 @@
- @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project)
- page_title _("New Pipeline Schedule")
-- if show_new_nav?
- - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
+- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
%h3.page-title
= _("Schedule a new pipeline")
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index fd3ad69d85d..487ac87186d 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
+- breadcrumb_title "CI / CD Charts"
- page_title _("Charts"), _("Pipelines")
-- if show_new_nav?
- - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 63f85fc69a2..7cc9fe79afd 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Pipelines", project_pipelines_path(@project)
+- breadcrumb_title "##{@pipeline.id}"
- page_title "Pipeline"
= render "projects/pipelines/head"
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 255d7ef38e0..d407e187df0 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -60,8 +60,21 @@
= f.check_box :public_builds
%strong Public pipelines
.help-block
- Allow everyone to access pipelines for public and internal projects
+ Allow public access to pipelines and job details, including output logs and artifacts
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank'
+ .bs-callout.bs-callout-info
+ %p If enabled:
+ %ul
+ %li
+ For public projects, anyone can view pipelines and access job details (output logs and artifacts)
+ %li
+ For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)
+ %li
+ For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)
+ %p
+ If disabled, the access level will depend on the user's
+ permissions in the project.
+
%hr
.form-group
.checkbox
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9f7c5a315eb..25153fd0b6f 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,8 +1,5 @@
- page_title "Members"
-- if show_new_nav?
- - add_to_breadcrumbs("Settings", edit_project_path(@project))
-
.row.prepend-top-default
.col-lg-12
%h4
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 0a5a38a3694..c786298e341 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Tags", project_tags_path(@project)
+- breadcrumb_title @tag.name
- page_title "Edit", @tag.name, "Tags"
= render "projects/commits/head"
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index 8056217bb1e..3e2a24a4c32 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -1,8 +1,6 @@
- breadcrumb_title "Integrations"
- page_title @service.title, "Services"
-
-- if show_new_nav?
- - add_to_breadcrumbs("Settings", edit_project_path(@project))
+- add_to_breadcrumbs("Settings", edit_project_path(@project))
= render "projects/settings/head"
= render 'form'
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 0c4130857da..099e3b8493f 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -1,8 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
-- page_title "Pipelines"
-
-- if show_new_nav?
- - add_to_breadcrumbs("Settings", edit_project_path(@project))
+- page_title "CI / CD Settings"
+- page_title "CI / CD"
= render "projects/settings/head"
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index 149da96d3f6..933daa7f549 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,7 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
+- breadcrumb_title "Integrations Settings"
- page_title 'Integrations'
-- if show_new_nav?
- - add_to_breadcrumbs("Settings", edit_project_path(@project))
= render "projects/settings/head"
= render 'projects/hooks/index'
= render 'projects/services/index'
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index cb37f3c7580..6d4af72b8ea 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -1,9 +1,7 @@
+- breadcrumb_title "Repository Settings"
- page_title "Repository"
- @content_class = "limit-container-width" unless fluid_layout
-- if show_new_nav?
- - add_to_breadcrumbs("Settings", edit_project_path(@project))
-
= render "projects/settings/head"
- content_for :page_specific_javascripts do
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index a9b39cedb1d..3f0a24cfe83 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- breadcrumb_title "Project"
+- breadcrumb_title "Details"
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index d41cc8e0425..32844f5204a 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
+- breadcrumb_title @snippet.to_reference
- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
%h3.page-title
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index ccc5fe80755..1803e7f7211 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,6 +1,6 @@
- page_title "Snippets"
-- if show_new_nav? && can?(current_user, :create_project_snippet, @project)
+- if can?(current_user, :create_project_snippet, @project)
- content_for :breadcrumbs_extra do
= link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet"
@@ -9,7 +9,7 @@
- include_private = @project.team.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
- .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ .nav-controls.visible-xs
- if can?(current_user, :create_project_snippet, @project)
= link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet"
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index d3e6b456f48..1359a815429 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,3 +1,5 @@
+- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
+- breadcrumb_title "New"
- page_title "New Snippets"
%h3.page-title
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index d8e448dd2af..fda068f08c2 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,4 +1,6 @@
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
+- add_to_breadcrumbs "Snippets", dashboard_snippets_path
+- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
= render 'shared/snippets/header'
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 00000e0667c..a6fe02fcae0 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,11 +1,9 @@
- @no_container = true
- @sort ||= sort_value_recently_updated
- page_title "Tags"
+- add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
-- if show_new_nav?
- - add_to_breadcrumbs("Repository", project_tree_path(@project))
-
.flex-list{ class: container_class }
.top-area.adjust
.nav-text.row-main-content
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index d02cd70f4c3..5d6eb4f4026 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Tags", project_tags_path(@project)
+- breadcrumb_title @tag.name
- page_title @tag.name, "Tags"
= render "projects/commits/head"
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index dece1fad0bb..d533c611a38 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,4 +1,6 @@
- @no_container = true
+- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
+- breadcrumb_title "Pages"
- page_title "Pages", "Wiki"
%div{ class: container_class }
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 9dadd685ea2..b066a812ec8 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,14 +1,13 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
-- breadcrumb_title "Wiki"
+- breadcrumb_title @page.title.capitalize
+- wiki_breadcrumb_dropdown_links(@page.slug)
- page_title @page.title.capitalize, "Wiki"
+- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
- .wiki-breadcrumb
- %span= breadcrumb(@page.slug)
-
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
index 10e6c49ae9f..0ef9de5fed6 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -1,4 +1,4 @@
-<svg width="28" height="28" class="tanuki-logo" viewBox="0 0 36 36">
+<svg width="24" height="24" class="tanuki-logo" viewBox="0 0 36 36">
<path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
<path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
<path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
diff --git a/app/views/shared/icons/_caret_down.svg b/app/views/shared/icons/_caret_down.svg
new file mode 100644
index 00000000000..fd80fd0f651
--- /dev/null
+++ b/app/views/shared/icons/_caret_down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="caret-down" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></svg>
diff --git a/app/views/shared/icons/_icon_resolve_discussion.svg b/app/views/shared/icons/_icon_resolve_discussion.svg
new file mode 100644
index 00000000000..845562e9320
--- /dev/null
+++ b/app/views/shared/icons/_icon_resolve_discussion.svg
@@ -0,0 +1 @@
+<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill-rule="evenodd"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></svg>
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
index 845562e9320..eed5006bebe 100755
--- a/app/views/shared/icons/_icon_status_success.svg
+++ b/app/views/shared/icons/_icon_status_success.svg
@@ -1 +1 @@
-<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill-rule="evenodd"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></svg>
+<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></svg>
diff --git a/app/views/shared/icons/_mr_bold.svg b/app/views/shared/icons/_mr_bold.svg
index 5468545da2e..0f5be6e2bc8 100644
--- a/app/views/shared/icons/_mr_bold.svg
+++ b/app/views/shared/icons/_mr_bold.svg
@@ -1,2 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg>
-
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg>
diff --git a/app/views/shared/icons/_plus_square.svg b/app/views/shared/icons/_plus_square.svg
new file mode 100644
index 00000000000..7263d924f1f
--- /dev/null
+++ b/app/views/shared/icons/_plus_square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M9 7V4c0-.552-.448-1-1-1s-1 .448-1 1v3H4c-.552 0-1 .448-1 1s.448 1 1 1h3v3c0 .552.448 1 1 1s1-.448 1-1V9h3c.552 0 1-.448 1-1s-.448-1-1-1H9zM3 0h10c1.657 0 3 1.343 3 3v10c0 1.657-1.343 3-3 3H3c-1.657 0-3-1.343-3-3V3c0-1.657 1.343-3 3-3z"/></svg>
diff --git a/app/views/shared/icons/_todo_done.svg b/app/views/shared/icons/_todo_done.svg
new file mode 100644
index 00000000000..156dfa11df1
--- /dev/null
+++ b/app/views/shared/icons/_todo_done.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0l-2.83-2.83a1 1 0 0 1 1.415-1.413l2.123 2.12zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></svg>
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index cb706d80f23..f16bc8dd430 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -9,7 +9,6 @@
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
-- elsif issuable.author
- / TODO: change back to else #36860
+- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
index d8144a39b23..a38cd319e3c 100644
--- a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
+++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
@@ -37,15 +37,13 @@
%li.divider.droplab-item-ignore
- / TODO: remove condition #36860
- - if issuable.author
- %li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
- button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
- %button.btn.btn-transparent
- = icon('check', class: 'icon')
- .description
- %strong.title Report abuse
- %p.text
- Report
- = display_issuable_type.pluralize
- that are abusive, inappropriate or spam.
+ %li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
+ button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title Report abuse
+ %p.text
+ Report
+ = display_issuable_type.pluralize
+ that are abusive, inappropriate or spam.
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index b07bc45512f..0afa48b392c 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -3,7 +3,7 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sidebar')
-%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { "offset-top" => ("50" unless show_new_nav?), "spy" => ("affix" unless show_new_nav?), signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 17b34c5eeb3..119d189f21d 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -3,10 +3,8 @@
%span.sr-only
= visibility_level_label(@snippet.visibility_level)
= visibility_level_icon(@snippet.visibility_level, fw: false)
- %strong.item-title
- Snippet #{@snippet.to_reference}
%span.creator
- authored
+ Authored
= time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 706f13dd004..578327883e5 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,5 +1,7 @@
- @hide_top_links = true
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
+- add_to_breadcrumbs "Snippets", dashboard_snippets_path
+- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
= render 'shared/snippets/header'
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index f34dff2d656..9b5ff17aafa 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -6,7 +6,11 @@ class CreateGpgSignatureWorker
project = Project.find_by(id: project_id)
return unless project
+ commit = project.commit(commit_sha)
+
+ return unless commit
+
# This calculates and caches the signature in the database
- Gitlab::Gpg::Commit.new(project, commit_sha).signature
+ Gitlab::Gpg::Commit.new(commit).signature
end
end
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 8b0cfcc8af8..269776a1f62 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -53,7 +53,7 @@ class StuckCiJobsWorker
def drop_build(type, build, status, timeout)
Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout})"
Gitlab::OptimisticLocking.retry_lock(build, 3) do |b|
- b.drop
+ b.drop(:stuck_or_timeout_failure)
end
end
end
diff --git a/changelogs/unreleased/19650-remove-admin-section-from-search-results-if-user-doesnt-have-access.yml b/changelogs/unreleased/19650-remove-admin-section-from-search-results-if-user-doesnt-have-access.yml
new file mode 100644
index 00000000000..6d5baa8c10f
--- /dev/null
+++ b/changelogs/unreleased/19650-remove-admin-section-from-search-results-if-user-doesnt-have-access.yml
@@ -0,0 +1,5 @@
+---
+title: Hide admin link from default search results for non-admins
+merge_request: 14015
+author:
+type: fixed
diff --git a/changelogs/unreleased/35010-projects-nav-dropdown.yml b/changelogs/unreleased/35010-projects-nav-dropdown.yml
new file mode 100644
index 00000000000..c5bed723f55
--- /dev/null
+++ b/changelogs/unreleased/35010-projects-nav-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Add dropdown to Projects nav item
+merge_request: 13866
+author:
+type: added
diff --git a/changelogs/unreleased/35010-remove-goto-project-from-breadcrumb.yml b/changelogs/unreleased/35010-remove-goto-project-from-breadcrumb.yml
new file mode 100644
index 00000000000..6cd7f4e9cc6
--- /dev/null
+++ b/changelogs/unreleased/35010-remove-goto-project-from-breadcrumb.yml
@@ -0,0 +1,5 @@
+---
+title: Remove project select dropdown from breadcrumb
+merge_request: 14010
+author:
+type: changed
diff --git a/changelogs/unreleased/36821-fix-new-nav-wrapping-caret-and-increasing-height.yml b/changelogs/unreleased/36821-fix-new-nav-wrapping-caret-and-increasing-height.yml
new file mode 100644
index 00000000000..54c7a8c8788
--- /dev/null
+++ b/changelogs/unreleased/36821-fix-new-nav-wrapping-caret-and-increasing-height.yml
@@ -0,0 +1,5 @@
+---
+title: Fix new navigation wrapping and causing height to grow
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/36859-update-gpg-docs-with-gpg2.yml b/changelogs/unreleased/36859-update-gpg-docs-with-gpg2.yml
new file mode 100644
index 00000000000..e48a5704fdd
--- /dev/null
+++ b/changelogs/unreleased/36859-update-gpg-docs-with-gpg2.yml
@@ -0,0 +1,5 @@
+---
+title: Update gpg documentation with gpg2
+merge_request: 13851
+author: M M Arif
+type: other
diff --git a/changelogs/unreleased/36860-migrate-issues-author.yml b/changelogs/unreleased/36860-migrate-issues-author.yml
new file mode 100644
index 00000000000..3e9fcc55836
--- /dev/null
+++ b/changelogs/unreleased/36860-migrate-issues-author.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate issues authored by deleted user to the Ghost user
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/36994-toggle-for-automatically-collapsing-outdated-diff-comments.yml b/changelogs/unreleased/36994-toggle-for-automatically-collapsing-outdated-diff-comments.yml
new file mode 100644
index 00000000000..83f6b2d21e1
--- /dev/null
+++ b/changelogs/unreleased/36994-toggle-for-automatically-collapsing-outdated-diff-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Add repository toggle for automatically resolving outdated diff discussions
+merge_request: 14053
+author: AshleyDumaine
+type: added
diff --git a/changelogs/unreleased/37204-deprecate-git-user-manual-ssh-config.yml b/changelogs/unreleased/37204-deprecate-git-user-manual-ssh-config.yml
new file mode 100644
index 00000000000..593e74593c4
--- /dev/null
+++ b/changelogs/unreleased/37204-deprecate-git-user-manual-ssh-config.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate custom SSH client configuration for the git user
+merge_request: 13930
+author:
+type: deprecated
diff --git a/changelogs/unreleased/37331-button-MR-widget.yml b/changelogs/unreleased/37331-button-MR-widget.yml
new file mode 100644
index 00000000000..59bc1bd201e
--- /dev/null
+++ b/changelogs/unreleased/37331-button-MR-widget.yml
@@ -0,0 +1,5 @@
+---
+title: Fix buttons with different height in merge request widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/37406-success-status-icon.yml b/changelogs/unreleased/37406-success-status-icon.yml
new file mode 100644
index 00000000000..faac947f188
--- /dev/null
+++ b/changelogs/unreleased/37406-success-status-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken svg in jobs dropdown for success status
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/additional-time-series-charts.yml b/changelogs/unreleased/additional-time-series-charts.yml
new file mode 100644
index 00000000000..80c1af54881
--- /dev/null
+++ b/changelogs/unreleased/additional-time-series-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Added support the multiple time series for prometheus monitoring
+merge_request: !36893
+author:
+type: changed
diff --git a/changelogs/unreleased/api-gpg-key-management.yml b/changelogs/unreleased/api-gpg-key-management.yml
new file mode 100644
index 00000000000..0be35a5823b
--- /dev/null
+++ b/changelogs/unreleased/api-gpg-key-management.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Add GPG key management'
+merge_request: 13828
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/api_branches_head.yml b/changelogs/unreleased/api_branches_head.yml
new file mode 100644
index 00000000000..68d8d3d5168
--- /dev/null
+++ b/changelogs/unreleased/api_branches_head.yml
@@ -0,0 +1,5 @@
+---
+title: Add branch existence check to the APIv4 branches via HEAD request
+merge_request: 13979
+author: Vitaliy @blackst0ne Klachkov
+type: added
diff --git a/changelogs/unreleased/dont-remove-add-diff-btn-on-post.yml b/changelogs/unreleased/dont-remove-add-diff-btn-on-post.yml
new file mode 100644
index 00000000000..a7db18dbd60
--- /dev/null
+++ b/changelogs/unreleased/dont-remove-add-diff-btn-on-post.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed add diff note button not showing after deleting a comment
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/feature-dependency-status-badge.yml b/changelogs/unreleased/feature-dependency-status-badge.yml
new file mode 100644
index 00000000000..1becff3585a
--- /dev/null
+++ b/changelogs/unreleased/feature-dependency-status-badge.yml
@@ -0,0 +1,5 @@
+---
+title: Add badge for dependency status
+merge_request: 13588
+author: Markus Koller
+type: other
diff --git a/changelogs/unreleased/feature-gpg-verification-status.yml b/changelogs/unreleased/feature-gpg-verification-status.yml
new file mode 100644
index 00000000000..7518fafcdb8
--- /dev/null
+++ b/changelogs/unreleased/feature-gpg-verification-status.yml
@@ -0,0 +1,6 @@
+---
+title: 'Update the GPG verification semantics: A GPG signature must additionally match
+ the committer in order to be verified'
+merge_request: 13771
+author: Alexis Reigel
+type: changed
diff --git a/changelogs/unreleased/feature-sm-34518-extend-api-pipeline-schedule-variable-new.yml b/changelogs/unreleased/feature-sm-34518-extend-api-pipeline-schedule-variable-new.yml
new file mode 100644
index 00000000000..969a5aeaed3
--- /dev/null
+++ b/changelogs/unreleased/feature-sm-34518-extend-api-pipeline-schedule-variable-new.yml
@@ -0,0 +1,5 @@
+---
+title: 'Extend API: Pipeline Schedule Variable'
+merge_request: 13653
+author:
+type: added
diff --git a/changelogs/unreleased/feature-sm-37239-implement-failure_reason-on-ci_builds.yml b/changelogs/unreleased/feature-sm-37239-implement-failure_reason-on-ci_builds.yml
new file mode 100644
index 00000000000..006b0b45844
--- /dev/null
+++ b/changelogs/unreleased/feature-sm-37239-implement-failure_reason-on-ci_builds.yml
@@ -0,0 +1,5 @@
+---
+title: Implement `failure_reason` on `ci_builds`
+merge_request: 13937
+author:
+type: added
diff --git a/changelogs/unreleased/fix-import-export-performance.yml b/changelogs/unreleased/fix-import-export-performance.yml
new file mode 100644
index 00000000000..1f59c4eb179
--- /dev/null
+++ b/changelogs/unreleased/fix-import-export-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve Import/Export memory usage
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fuzzy-issue-search.yml b/changelogs/unreleased/fuzzy-issue-search.yml
new file mode 100644
index 00000000000..8195e97ed59
--- /dev/null
+++ b/changelogs/unreleased/fuzzy-issue-search.yml
@@ -0,0 +1,5 @@
+---
+title: Support a multi-word fuzzy seach issues/merge requests on search bar
+merge_request: 13780
+author: Hiroyuki Sato
+type: changed
diff --git a/changelogs/unreleased/issue-api-my-reaction.yml b/changelogs/unreleased/issue-api-my-reaction.yml
new file mode 100644
index 00000000000..1c12478fbc0
--- /dev/null
+++ b/changelogs/unreleased/issue-api-my-reaction.yml
@@ -0,0 +1,5 @@
+---
+title: Add my_reaction_emoji param to /issues and /merge_requests API
+merge_request: 14016
+author: Hiroyuki Sato
+type: added
diff --git a/changelogs/unreleased/mr-index-page-performance.yml b/changelogs/unreleased/mr-index-page-performance.yml
new file mode 100644
index 00000000000..df5f44c04fa
--- /dev/null
+++ b/changelogs/unreleased/mr-index-page-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Re-use issue/MR counts for the pagination system
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/sh-bump-jira-gem.yml b/changelogs/unreleased/sh-bump-jira-gem.yml
new file mode 100644
index 00000000000..d76b688caac
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-jira-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Bump jira-ruby gem to 1.4.1 to fix issues with HTTP proxies
+merge_request:
+author:
+type: fixed
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index c9018f3bf0e..d6c3c84851b 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -410,3 +410,9 @@
:why: https://gitlab.com/gitlab-com/organization/issues/116
:versions: []
:when: 2017-09-01 17:17:51.996511844 Z
+- - :blacklist
+ - Facebook BSD+PATENTS
+ - :who: Nick Thomas <nick@gitlab.com>
+ :why: https://gitlab.com/gitlab-com/organization/issues/117
+ :versions: []
+ :when: 2017-09-04 12:59:51.150798717 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 545c01e1156..c5704ac5857 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -508,7 +508,7 @@ production: &base
failure_count_threshold: 10 # number of failures before stopping attempts
failure_wait_time: 30 # Seconds after an access failure before allowing access again
failure_reset_time: 1800 # Time in seconds to expire failures
- storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt
+ storage_timeout: 30 # Time in seconds to wait before aborting a storage access attempt
## Backup settings
diff --git a/config/webpack.config.js b/config/webpack.config.js
index ad88e48550d..6b0cd023291 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -30,7 +30,7 @@ var config = {
blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js',
common: './commons/index.js',
- common_vue: ['vue', './vue_shared/common_vue.js'],
+ common_vue: './vue_shared/vue_resource_interceptor.js',
common_d3: ['d3'],
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff --git a/db/migrate/20170817123339_add_verification_status_to_gpg_signatures.rb b/db/migrate/20170817123339_add_verification_status_to_gpg_signatures.rb
new file mode 100644
index 00000000000..128cd109f8d
--- /dev/null
+++ b/db/migrate/20170817123339_add_verification_status_to_gpg_signatures.rb
@@ -0,0 +1,20 @@
+class AddVerificationStatusToGpgSignatures < ActiveRecord::Migration
+ DOWNTIME = false
+
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ # First we remove all signatures because we need to re-verify them all
+ # again anyway (because of the updated verification logic).
+ #
+ # This makes adding the column with default values faster
+ truncate(:gpg_signatures)
+
+ add_column_with_default(:gpg_signatures, :verification_status, :smallint, default: 0)
+ end
+
+ def down
+ remove_column(:gpg_signatures, :verification_status)
+ end
+end
diff --git a/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
new file mode 100644
index 00000000000..294141e4fdb
--- /dev/null
+++ b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
@@ -0,0 +1,36 @@
+class MigrateIssuesToGhostUser < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+ end
+
+ class Issue < ActiveRecord::Base
+ self.table_name = 'issues'
+
+ include ::EachBatch
+ end
+
+ def reset_column_in_migration_models
+ ActiveRecord::Base.clear_cache!
+
+ ::User.reset_column_information
+ end
+
+ def up
+ reset_column_in_migration_models
+
+ # we use the model method because rewriting it is too complicated and would require copying multiple methods
+ ghost_id = ::User.ghost.id
+
+ Issue.where('NOT EXISTS (?)', User.unscoped.select(1).where('issues.author_id = users.id')).each_batch do |relation|
+ relation.update_all(author_id: ghost_id)
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20170825154015_resolve_outdated_diff_discussions.rb b/db/migrate/20170825154015_resolve_outdated_diff_discussions.rb
new file mode 100644
index 00000000000..235530bb1e6
--- /dev/null
+++ b/db/migrate/20170825154015_resolve_outdated_diff_discussions.rb
@@ -0,0 +1,9 @@
+class ResolveOutdatedDiffDiscussions < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column(:projects, :resolve_outdated_diff_discussions, :boolean)
+ end
+end
diff --git a/db/migrate/20170830125940_add_failure_reason_to_ci_builds.rb b/db/migrate/20170830125940_add_failure_reason_to_ci_builds.rb
new file mode 100644
index 00000000000..5a7487b9227
--- /dev/null
+++ b/db/migrate/20170830125940_add_failure_reason_to_ci_builds.rb
@@ -0,0 +1,9 @@
+class AddFailureReasonToCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :failure_reason, :integer
+ end
+end
diff --git a/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb b/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb
new file mode 100644
index 00000000000..ab6e9fb565a
--- /dev/null
+++ b/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb
@@ -0,0 +1,14 @@
+class AddForeignKeyToIssueAuthor < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(:issues, :users, column: :author_id, on_delete: :nullify)
+ end
+
+ def down
+ remove_foreign_key(:issues, column: :author_id)
+ end
+end
diff --git a/db/migrate/20170905112933_add_resolved_by_push_to_notes.rb b/db/migrate/20170905112933_add_resolved_by_push_to_notes.rb
new file mode 100644
index 00000000000..ceb31ffb08a
--- /dev/null
+++ b/db/migrate/20170905112933_add_resolved_by_push_to_notes.rb
@@ -0,0 +1,9 @@
+class AddResolvedByPushToNotes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :notes, :resolved_by_push, :boolean
+ end
+end
diff --git a/db/post_migrate/20170830084744_destroy_gpg_signatures.rb b/db/post_migrate/20170830084744_destroy_gpg_signatures.rb
new file mode 100644
index 00000000000..b04d36f6537
--- /dev/null
+++ b/db/post_migrate/20170830084744_destroy_gpg_signatures.rb
@@ -0,0 +1,10 @@
+class DestroyGpgSignatures < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ truncate(:gpg_signatures)
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20170831195038_remove_valid_signature_from_gpg_signatures.rb b/db/post_migrate/20170831195038_remove_valid_signature_from_gpg_signatures.rb
new file mode 100644
index 00000000000..9b6745e33d9
--- /dev/null
+++ b/db/post_migrate/20170831195038_remove_valid_signature_from_gpg_signatures.rb
@@ -0,0 +1,11 @@
+class RemoveValidSignatureFromGpgSignatures < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ remove_column :gpg_signatures, :valid_signature
+ end
+
+ def down
+ add_column :gpg_signatures, :valid_signature, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a5f867df9ae..69911ce7107 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170824162758) do
+ActiveRecord::Schema.define(version: 20170905112933) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -247,6 +247,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.boolean "retried"
t.integer "stage_id"
t.boolean "protected"
+ t.integer "failure_reason"
end
add_index "ci_builds", ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree
@@ -608,11 +609,11 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.datetime "updated_at", null: false
t.integer "project_id"
t.integer "gpg_key_id"
- t.boolean "valid_signature"
t.binary "commit_sha"
t.binary "gpg_key_primary_keyid"
t.text "gpg_key_user_name"
t.text "gpg_key_user_email"
+ t.integer "verification_status", limit: 2, default: 0, null: false
end
add_index "gpg_signatures", ["commit_sha"], name: "index_gpg_signatures_on_commit_sha", unique: true, using: :btree
@@ -1001,6 +1002,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.text "note_html"
t.integer "cached_markdown_version"
t.text "change_position"
+ t.boolean "resolved_by_push"
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -1218,6 +1220,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.string "ci_config_path"
t.text "delete_error"
t.integer "storage_version", limit: 2
+ t.boolean "resolve_outdated_diff_discussions"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1707,6 +1710,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade
+ add_foreign_key "issues", "users", column: "author_id", name: "fk_05f1e72feb", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index 63ba8ff03e9..b250fa08382 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -160,7 +160,6 @@ have access to GitLab administration tools and settings.
### Integrations
- [Integrations](integration/README.md): How to integrate with systems such as JIRA, Redmine, Twitter.
-- [Koding](administration/integration/koding.md): Set up Koding to use with GitLab.
- [Mattermost](user/project/integrations/mattermost.md): Set up GitLab with Mattermost.
### Monitoring
diff --git a/doc/administration/integration/koding.md b/doc/administration/integration/koding.md
index b95c425842c..67f9f01efb8 100644
--- a/doc/administration/integration/koding.md
+++ b/doc/administration/integration/koding.md
@@ -1,6 +1,10 @@
# Koding & GitLab
-> [Introduced][ce-5909] in GitLab 8.11.
+>**Notes:**
+- **As of GitLab 10.0, the Koding integration is deprecated and will be removed
+ in a future version. The option to configure it is removed from GitLab's admin
+ area.**
+- [Introduced][ce-5909] in GitLab 8.11.
This document will guide you through installing and configuring Koding with
GitLab.
diff --git a/doc/api/README.md b/doc/api/README.md
index c2a08dcff07..db61497db53 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -61,16 +61,7 @@ following locations:
## Road to GraphQL
-Going forward, we will start on moving to
-[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
-controller-specific endpoints. GraphQL has a number of benefits:
-
-1. We avoid having to maintain two different APIs.
-2. Callers of the API can request only what they need.
-3. It is versioned by default.
-
-It will co-exist with the current v4 REST API. If we have a v5 API, this should
-be a compatibility layer on top of GraphQL.
+We have changed our plans to move to GraphQL. After reviewing the GraphQL license, anything related to the Facebook BSD plus patent license will not be allowed at GitLab.
## Basic usage
@@ -246,8 +237,8 @@ The following table gives an overview of how the API functions generally behave.
| ------------ | ----------- |
| `GET` | Access one or more resources and return the result as JSON. |
| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
-| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. |
-| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. |
+| `GET` / `PUT` | Return `200 OK` if the resource is accessed or modified successfully. The (modified) result is returned as JSON. |
+| `DELETE` | Returns `204 No Content` if the resuource was deleted successfully. |
The following table shows the possible return codes for API requests.
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 5ca766bf87d..e8deb3e07e9 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -94,7 +94,7 @@ Example response:
## Delete an environment
-It returns `200` if the environment was successfully deleted, and `404` if the environment does not exist.
+It returns `204` if the environment was successfully deleted, and `404` if the environment does not exist.
```
DELETE /projects/:id/environments/:environment_id
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 765246142c1..8ca66049d31 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -30,20 +30,22 @@ GET /issues?milestone=1.0.0&state=opened
GET /issues?iids[]=42&iids[]=43
GET /issues?author_id=5
GET /issues?assignee_id=5
-```
-
-| Attribute | Type | Required | Description |
-|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
-| `milestone` | string | no | The milestone title |
-| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Search issues against their `title` and `description` |
+GET /issues?my_reaction_emoji=star
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
+| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
+| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Search issues against their `title` and `description` |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
@@ -131,21 +133,23 @@ GET /groups/:id/issues?iids[]=42&iids[]=43
GET /groups/:id/issues?search=issue+title+or+description
GET /groups/:id/issues?author_id=5
GET /groups/:id/issues?assignee_id=5
+GET /groups/:id/issues?my_reaction_emoji=star
```
-| Attribute | Type | Required | Description |
-|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
-| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
-| `milestone` | string | no | The milestone title |
-| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Search group issues against their `title` and `description` |
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
+| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
+| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Search group issues against their `title` and `description` |
```bash
@@ -234,23 +238,25 @@ GET /projects/:id/issues?iids[]=42&iids[]=43
GET /projects/:id/issues?search=issue+title+or+description
GET /projects/:id/issues?author_id=5
GET /projects/:id/issues?assignee_id=5
-```
-
-| 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 |
-| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` |
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
-| `milestone` | string | no | The milestone title |
-| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Search project issues against their `title` and `description` |
-| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
-| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
+GET /projects/:id/issues?my_reaction_emoji=star
+```
+
+| 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 |
+| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
+| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
+| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Search project issues against their `title` and `description` |
+| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
+| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
@@ -1093,3 +1099,4 @@ Example response:
```
[ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004
+[ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 4f67aa4b9d4..bff8a2d3e4d 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -22,24 +22,26 @@ GET /merge_requests?state=all
GET /merge_requests?milestone=release
GET /merge_requests?labels=bug,reproduced
GET /merge_requests?author_id=5
+GET /merge_requests?my_reaction_emoji=star
GET /merge_requests?scope=assigned-to-me
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged`|
-| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
-| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
-| `milestone` | string | no | Return merge requests for a specific milestone |
-| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
-| `labels` | string | no | Return merge requests matching a comma separated list of labels |
-| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
-| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
-| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` |
-| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
-| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
+| Attribute | Type | Required | Description |
+| ------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
+| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged` |
+| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
+| `milestone` | string | no | Return merge requests for a specific milestone |
+| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
+| `labels` | string | no | Return merge requests matching a comma separated list of labels |
+| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
+| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
+| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` |
+| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
+| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
+| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
```json
[
@@ -116,25 +118,27 @@ GET /projects/:id/merge_requests?state=all
GET /projects/:id/merge_requests?iids[]=42&iids[]=43
GET /projects/:id/merge_requests?milestone=release
GET /projects/:id/merge_requests?labels=bug,reproduced
+GET /projects/:id/merge_requests?my_reaction_emoji=star
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
-| `iids[]` | Array[integer] | no | Return the request having the given `iid` |
-| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged`|
-| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
-| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
-| `milestone` | string | no | Return merge requests for a specific milestone |
-| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
-| `labels` | string | no | Return merge requests matching a comma separated list of labels |
-| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
-| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
-| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| Attribute | Type | Required | Description |
+| ------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| `id` | integer | yes | The ID of a project |
+| `iids[]` | Array[integer] | no | Return the request having the given `iid` |
+| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged` |
+| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
+| `milestone` | string | no | Return merge requests for a specific milestone |
+| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
+| `labels` | string | no | Return merge requests matching a comma separated list of labels |
+| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
+| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
+| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
```json
[
@@ -1315,3 +1319,4 @@ Example response:
```
[ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060
+[ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md
index 433654c18cc..c28f48e5fc6 100644
--- a/doc/api/pipeline_schedules.md
+++ b/doc/api/pipeline_schedules.md
@@ -84,7 +84,13 @@ curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
- }
+ },
+ "variables": [
+ {
+ "key": "TEST_VARIABLE_1",
+ "value": "TEST_1"
+ }
+ ]
}
```
@@ -271,3 +277,86 @@ curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gi
}
}
```
+
+## Pipeline schedule variable
+
+> [Introduced][ce-34518] in GitLab 10.0.
+
+## Create a new pipeline schedule variable
+
+Create a new variable of a pipeline schedule.
+
+```
+POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables
+```
+
+| 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 |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
+| `value` | string | yes | The `value` of a variable |
+
+```sh
+curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "key=NEW_VARIABLE" --form "value=new value" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/variables"
+```
+
+```json
+{
+ "key": "NEW_VARIABLE",
+ "value": "new value"
+}
+```
+
+## Edit a pipeline schedule variable
+
+Updates the variable of a pipeline schedule.
+
+```
+PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key
+```
+
+| 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 |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+| `key` | string | yes | The `key` of a variable |
+| `value` | string | yes | The `value` of a variable |
+
+```sh
+curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "value=updated value" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/variables/NEW_VARIABLE"
+```
+
+```json
+{
+ "key": "NEW_VARIABLE",
+ "value": "updated value"
+}
+```
+
+## Delete a pipeline schedule variable
+
+Delete the variable of a pipeline schedule.
+
+```
+DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key
+```
+
+| 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 |
+| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
+| `key` | string | yes | The `key` of a variable |
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/variables/NEW_VARIABLE"
+```
+
+```json
+{
+ "key": "NEW_VARIABLE",
+ "value": "updated value"
+}
+```
+
+[ce-34518]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34518 \ No newline at end of file
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index 24c8ff5fa7a..ad2521230e6 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -95,8 +95,7 @@ Parameters:
## Delete snippet
-Deletes an existing project snippet. This is an idempotent function and deleting a non-existent
-snippet still returns a `200 OK` status code.
+Deletes an existing project snippet. This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
```
DELETE /projects/:id/snippets/:snippet_id
diff --git a/doc/api/projects.md b/doc/api/projects.md
index d3f8e509612..3144220e588 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1,6 +1,6 @@
# Projects API
-### Project visibility level
+## Project visibility level
Project in GitLab can be either private, internal or public.
This is determined by the `visibility` field in the project.
@@ -16,16 +16,15 @@ Values for the project visibility level are:
* `public`:
The project can be cloned without any authentication.
-## List projects
+## List all projects
-Get a list of visible projects for authenticated user. When accessed without authentication, only public projects are returned.
+Get a list of all visible projects across GitLab for the authenticated user.
+When accessed without authentication, only public projects are returned.
```
GET /projects
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
@@ -70,6 +69,7 @@ Parameters:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -137,6 +137,7 @@ Parameters:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -191,16 +192,15 @@ Parameters:
]
```
-### List a user's projects
+## List user projects
-Get a list of visible projects for the given user. When accessed without authentication, only public projects are returned.
+Get a list of visible projects for the given user. When accessed without
+authentication, only public projects are returned.
```
GET /users/:user_id/projects
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `user_id` | string | yes | The ID or username of the user |
@@ -246,6 +246,7 @@ Parameters:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -313,6 +314,7 @@ Parameters:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -367,7 +369,7 @@ Parameters:
]
```
-### Get single project
+## Get single project
Get a specific project. This endpoint can be accessed without authentication if
the project is publicly accessible.
@@ -376,8 +378,6 @@ the project is publicly accessible.
GET /projects/:id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -411,6 +411,7 @@ Parameters:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -480,17 +481,14 @@ Parameters:
Get the users list of a project.
-
-Parameters:
+```
+GET /projects/:id/users
+```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `search` | string | no | Search for specific users |
-```
-GET /projects/:id/users
-```
-
```json
[
{
@@ -512,11 +510,11 @@ GET /projects/:id/users
]
```
-### Get project events
+## Get project events
-Please refer to the [Events API documentation](events.md#list-a-projects-visible-events)
+Please refer to the [Events API documentation](events.md#list-a-projects-visible-events).
-### Create project
+## Create project
Creates a new project owned by the authenticated user.
@@ -524,8 +522,6 @@ Creates a new project owned by the authenticated user.
POST /projects
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | yes if path is not provided | The name of the new project. Equals path if not provided. |
@@ -537,6 +533,7 @@ Parameters:
| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
@@ -551,7 +548,7 @@ Parameters:
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
| `ci_config_path` | string | no | The path to CI config file |
-### Create project for user
+## Create project for user
Creates a new project owned by the specified user. Available only for admins.
@@ -559,8 +556,6 @@ Creates a new project owned by the specified user. Available only for admins.
POST /projects/user/:user_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `user_id` | integer | yes | The user ID of the project owner |
@@ -574,6 +569,7 @@ Parameters:
| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
@@ -588,7 +584,7 @@ Parameters:
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
| `ci_config_path` | string | no | The path to CI config file |
-### Edit project
+## Edit project
Updates an existing project.
@@ -596,8 +592,6 @@ Updates an existing project.
PUT /projects/:id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -610,6 +604,7 @@ Parameters:
| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
@@ -623,24 +618,24 @@ Parameters:
| `avatar` | mixed | no | Image file for avatar of the project |
| `ci_config_path` | string | no | The path to CI config file |
-### Fork project
+## Fork project
Forks a project into the user namespace of the authenticated user or the one provided.
-The forking operation for a project is asynchronous and is completed in a background job. The request will return immediately. To determine whether the fork of the project has completed, query the `import_status` for the new project.
+The forking operation for a project is asynchronous and is completed in a
+background job. The request will return immediately. To determine whether the
+fork of the project has completed, query the `import_status` for the new project.
```
POST /projects/:id/fork
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to |
-### Star a project
+## Star a project
Stars a given project. Returns status code `304` if the project is already starred.
@@ -648,8 +643,6 @@ Stars a given project. Returns status code `304` if the project is already starr
POST /projects/:id/star
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -683,6 +676,7 @@ Example response:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -717,7 +711,7 @@ Example response:
}
```
-### Unstar a project
+## Unstar a project
Unstars a given project. Returns status code `304` if the project is not starred.
@@ -758,6 +752,7 @@ Example response:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -792,7 +787,7 @@ Example response:
}
```
-### Archive a project
+## Archive a project
Archives the project if the user is either admin or the project owner of this project. This action is
idempotent, thus archiving an already archived project will not change the project.
@@ -839,6 +834,7 @@ Example response:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -885,7 +881,7 @@ Example response:
}
```
-### Unarchive a project
+## Unarchive a project
Unarchives the project if the user is either admin or the project owner of this project. This action is
idempotent, thus unarchiving an non-archived project will not change the project.
@@ -932,6 +928,7 @@ Example response:
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
@@ -978,7 +975,7 @@ Example response:
}
```
-### Remove project
+## Remove project
Removes a project including all associated resources (issues, merge requests etc.)
@@ -986,15 +983,11 @@ Removes a project including all associated resources (issues, merge requests etc
DELETE /projects/:id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-## Uploads
-
-### Upload a file
+## Upload a file
Uploads a file to the specified project to be used in an issue or merge request description, or a comment.
@@ -1002,8 +995,6 @@ Uploads a file to the specified project to be used in an issue or merge request
POST /projects/:id/uploads
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1028,15 +1019,11 @@ Returned object:
}
```
-**Note**: The returned `url` is relative to the project path.
+>**Note**: The returned `url` is relative to the project path.
In Markdown contexts, the link is automatically expanded when the format in
`markdown` is used.
-## Project members
-
-Please consult the [Project Members](members.md) documentation.
-
-### Share project with group
+## Share project with group
Allow to share project with group.
@@ -1044,8 +1031,6 @@ Allow to share project with group.
POST /projects/:id/share
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1053,7 +1038,7 @@ Parameters:
| `group_access` | integer | yes | The permissions level to grant the group |
| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 |
-### Delete a shared project link within a group
+## Delete a shared project link within a group
Unshare the project from the group. Returns `204` and no content on success.
@@ -1061,8 +1046,6 @@ Unshare the project from the group. Returns `204` and no content on success.
DELETE /projects/:id/share/:group_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1085,8 +1068,6 @@ Get a list of project hooks.
GET /projects/:id/hooks
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1099,8 +1080,6 @@ Get a specific hook for a project.
GET /projects/:id/hooks/:hook_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1132,8 +1111,6 @@ Adds a hook to a specified project.
POST /projects/:id/hooks
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1157,8 +1134,6 @@ Edits a hook for a specified project.
PUT /projects/:id/hooks/:hook_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1184,8 +1159,6 @@ Either the hook is available or not.
DELETE /projects/:id/hooks/:hook_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1194,126 +1167,16 @@ Parameters:
Note the JSON response differs if the hook is available or not. If the project hook
is available before it is returned in the JSON response or an empty response is returned.
-## Branches
-
-For more information please consult the [Branches](branches.md) documentation.
-
-### List branches
-
-Lists all branches of a project.
-
-```
-GET /projects/:id/repository/branches
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-
-```json
-[
- {
- "name": "async",
- "commit": {
- "id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
- "parent_ids": [
- "3f94fc7c85061973edc9906ae170cc269b07ca55"
- ],
- "message": "give Caolan credit where it's due (up top)",
- "author_name": "Jeremy Ashkenas",
- "author_email": "jashkenas@example.com",
- "authored_date": "2010-12-08T21:28:50+00:00",
- "committer_name": "Jeremy Ashkenas",
- "committer_email": "jashkenas@example.com",
- "committed_date": "2010-12-08T21:28:50+00:00"
- },
- "protected": false,
- "developers_can_push": false,
- "developers_can_merge": false
- },
- {
- "name": "gh-pages",
- "commit": {
- "id": "101c10a60019fe870d21868835f65c25d64968fc",
- "parent_ids": [
- "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
- ],
- "message": "Underscore.js 1.5.2",
- "author_name": "Jeremy Ashkenas",
- "author_email": "jashkenas@example.com",
- "authored_date": "2013-09-07T12:58:21+00:00",
- "committer_name": "Jeremy Ashkenas",
- "committer_email": "jashkenas@example.com",
- "committed_date": "2013-09-07T12:58:21+00:00"
- },
- "protected": false,
- "developers_can_push": false,
- "developers_can_merge": false
- }
-]
-```
-
-### Single branch
-
-A specific branch of a project.
-
-```
-GET /projects/:id/repository/branches/:branch
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | The name of the branch |
-| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
-| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
-
-### Protect single branch
-
-Protects a single branch of a project.
-
-```
-PUT /projects/:id/repository/branches/:branch/protect
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | The name of the branch |
-
-### Unprotect single branch
-
-Unprotects a single branch of a project.
-
-```
-PUT /projects/:id/repository/branches/:branch/unprotect
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | The name of the branch |
-
## Admin fork relation
Allows modification of the forked relationship between existing projects. Available only for admins.
-### Create a forked from/to relation between existing projects.
+### Create a forked from/to relation between existing projects
```
POST /projects/:id/fork/:forked_from_id
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1325,8 +1188,6 @@ Parameters:
DELETE /projects/:id/fork
```
-Parameter:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
@@ -1341,8 +1202,6 @@ accessible.
GET /projects
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `search` | string | yes | A string contained in the project name |
@@ -1355,14 +1214,20 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a
## Start the Housekeeping task for a Project
->**Note:** This feature was introduced in GitLab 9.0
+> Introduced in GitLab 9.0.
```
POST /projects/:id/housekeeping
```
-Parameters:
-
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+
+## Branches
+
+Read more in the [Branches](branches.md) documentation.
+
+## Project members
+
+Read more in the [Project members](members.md) documentation.
diff --git a/doc/api/users.md b/doc/api/users.md
index 57a13eb477d..9f3e4caf2f4 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -299,10 +299,7 @@ e.g. when renaming the email address to some existing one.
## User deletion
Deletes a user. Available only for administrators.
-This is an idempotent function, calling this function for a non-existent user id
-still returns a status code `200 OK`.
-The JSON response differs if the user was actually deleted or not.
-In the former the user is returned and in the latter not.
+This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
```
DELETE /users/:id
@@ -524,8 +521,7 @@ Parameters:
## Delete SSH key for current user
Deletes key owned by currently authenticated user.
-This is an idempotent function and calling it on a key that is already deleted
-or not available results in `200 OK`.
+This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
```
DELETE /user/keys/:key_id
@@ -548,7 +544,216 @@ Parameters:
- `id` (required) - id of specified user
- `key_id` (required) - SSH key ID
-Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
+## List all GPG keys
+
+Get a list of currently authenticated user's GPG keys.
+
+```
+GET /user/gpg_keys
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+]
+```
+
+## Get a specific GPG key
+
+Get a specific GPG key of currently authenticated user.
+
+```
+GET /user/gpg_keys/:key_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `key_id` | integer | yes | The ID of the GPG key |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+```
+
+## Add a GPG key
+
+Creates a new GPG key owned by the currently authenticated user.
+
+```
+POST /user/gpg_keys
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| key | string | yes | The new GPG key |
+
+```bash
+curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+]
+```
+
+## Delete a GPG key
+
+Delete a GPG key owned by currently authenticated user.
+
+```
+DELETE /user/gpg_keys/:key_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `key_id` | integer | yes | The ID of the GPG key |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1
+```
+
+Returns `204 No Content` on success, or `404 Not found` if the key cannot be found.
+
+## List all GPG keys for given user
+
+Get a list of a specified user's GPG keys. Available only for admins.
+
+```
+GET /users/:id/gpg_keys
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the user |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+]
+```
+
+## Get a specific GPG key for a given user
+
+Get a specific GPG key for a given user. Available only for admins.
+
+```
+GET /users/:id/gpg_keys/:key_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the user |
+| `key_id` | integer | yes | The ID of the GPG key |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+```
+
+## Add a GPG key for a given user
+
+Create new GPG key owned by the specified user. Available only for admins.
+
+```
+POST /users/:id/gpg_keys
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the user |
+| `key_id` | integer | yes | The ID of the GPG key |
+
+```bash
+curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----",
+ "created_at": "2017-09-05T09:17:46.264Z"
+ }
+]
+```
+
+## Delete a GPG key for a given user
+
+Delete a GPG key owned by a specified user. Available only for admins.
+
+```
+DELETE /users/:id/gpg_keys/:key_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the user |
+| `key_id` | integer | yes | The ID of the GPG key |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
+```
## List emails
@@ -654,8 +859,7 @@ Parameters:
## Delete email for current user
Deletes email owned by currently authenticated user.
-This is an idempotent function and calling it on a email that is already deleted
-or not available results in `200 OK`.
+This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
```
DELETE /user/emails/:email_id
@@ -678,8 +882,6 @@ Parameters:
- `id` (required) - id of specified user
- `email_id` (required) - email ID
-Will return `200 OK` on success, or `404 Not found` if either user or email cannot be found.
-
## Block user
Blocks the specified user. Available only for admin.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 28b27921f8b..cbf06afa294 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -274,9 +274,7 @@ session - and even a multiplexer like `screen` or `tmux`!
>**Note:**
Container-based deployments often lack basic tools (like an editor), and may
be stopped or restarted at any time. If this happens, you will lose all your
-changes! Treat this as a debugging tool, not a comprehensive online IDE. You
-can use [Koding](../administration/integration/koding.md) for online
-development.
+changes! Treat this as a debugging tool, not a comprehensive online IDE.
---
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 4ccf1b56771..f5d3b524d6e 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -107,9 +107,26 @@ To lock/unlock a Runner:
1. Check the **Lock to current projects** option
1. Click **Save changes** for the changes to take effect
+## Assigning a Runner to another project
+
+If you are Master on a project where a specific Runner is assigned to, and the
+Runner is not [locked only to that project](#locking-a-specific-runner-from-being-enabled-for-other-projects),
+you can enable the Runner also on any other project where you have Master permissions.
+
+To enable/disable a Runner in your project:
+
+1. Visit your project's **Settings âž” Pipelines**
+1. Find the Runner you wish to enable/disable
+1. Click **Enable for this project** or **Disable for this project**
+
+> **Note**:
+Consider that if you don't lock your specific Runner to a specific project, any
+user with Master role in you project can assign your runner to another arbitrary
+project without requiring your authorization, so use it with caution.
+
## Protected Runners
->**Notes:**
+>
[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194)
in GitLab 10.0.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index cacfd2ed254..d0ac3ec6163 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -130,7 +130,7 @@ There are also two edge cases worth mentioning:
### types
-> Deprecated, and will be removed in 10.0. Use [stages](#stages) instead.
+> Deprecated, and could be removed in one of the future releases. Use [stages](#stages) instead.
Alias for [stages](#stages).
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 0742b202807..2607353782a 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -28,8 +28,9 @@ As always, the Frontend Architectural Experts are available to help with any Vue
All new features built with Vue.js must follow a [Flux architecture][flux].
The main goal we are trying to achieve is to have only one data flow and only one data entry.
-In order to achieve this goal, each Vue bundle needs a Store - where we keep all the data -,
-a Service - that we use to communicate with the server - and a main Vue component.
+In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below:
+
+Each Vue bundle needs a Store - where we keep all the data -,a Service - that we use to communicate with the server - and a main Vue component.
Think of the Main Vue Component as the entry point of your application. This is the only smart
component that should exist in each Vue feature.
@@ -74,6 +75,59 @@ provided as a prop to the main component.
Don't forget to follow [these steps.][page_specific_javascript]
+### Bootstrapping Gotchas
+#### Providing data from Haml to JavaScript
+While mounting a Vue application may be a need to provide data from Rails to JavaScript.
+To do that, provide the data through `data` attributes in the HTML element and query them while mounting the application.
+
+_Note:_ You should only do this while initing the application, because the mounted element will be replaced with Vue-generated DOM.
+
+The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function
+instead of querying the DOM inside the main vue component is that makes tests easier by avoiding the need to
+create a fixture or an HTML element in the unit test. See the following example:
+
+```javascript
+// haml
+.js-vue-app{ data: { endpoint: 'foo' }}
+
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '.js-vue-app',
+ data() {
+ const dataset = this.$options.el.dataset;
+ return {
+ endpoint: dataset.endpoint,
+ };
+ },
+ render(createElement) {
+ return createElement('my-component', {
+ props: {
+ endpoint: this.isLoading,
+ },
+ });
+ },
+}));
+```
+
+#### Accessing the `gl` object
+When we need to query the `gl` object for data that won't change during the application's lyfecyle, we should do it in the same place where we query the DOM.
+By following this practice, we can avoid the need to mock the `gl` object, which will make tests easier.
+It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component:
+
+##### example:
+```javascript
+
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '.js-vue-app',
+ render(createElement) {
+ return createElement('my-component', {
+ props: {
+ username: gon.current_username,
+ },
+ });
+ },
+}));
+```
+
### A folder for Components
This folder holds all components that are specific of this new feature.
@@ -89,6 +143,29 @@ in one table would not be a good use of this pattern.
You can read more about components in Vue.js site, [Component System][component-system]
+#### Components Gotchas
+1. Using SVGs in components: To use an SVG in a template we need to make it a property we can access through the component.
+A `prop` and a property returned by the `data` functions require `vue` to set a `getter` and a `setter` for each of them.
+The SVG should be a computed property in order to improve performance, note that computed properties are cached based on their dependencies.
+
+```javascript
+// bad
+import svg from 'svg.svg';
+data() {
+ return {
+ myIcon: svg,
+ };
+};
+
+// good
+import svg from 'svg.svg';
+computed: {
+ myIcon() {
+ return svg;
+ }
+}
+```
+
### A folder for the Store
The Store is a class that allows us to manage the state in a single
@@ -430,11 +507,23 @@ describe('Todos App', () => {
});
});
```
+#### `mountComponent` helper
+There is an helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props:
+
+```javascript
+import Vue from 'vue';
+import mountComponent from 'helpers/vue_mount_component_helper.js'
+import component from 'component.vue'
+
+const Component = Vue.extend(component);
+const data = {prop: 'foo'};
+const vm = mountComponent(Component, data);
+```
+
#### Test the component's output
The main return value of a Vue component is the rendered output. In order to test the component we
need to test the rendered output. [Vue][vue-test] guide's to unit test show us exactly that:
-
### Stubbing API responses
[Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with
the response we need:
@@ -481,6 +570,198 @@ new Component({
new Component().$mount();
```
+## Vuex
+To manage the state of an application you may use [Vuex][vuex-docs].
+
+_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
+
+### Separation of concerns
+Vuex is composed of State, Getters, Mutations, Actions and Modules.
+
+When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state.
+_Note:_ The action itself will not update the state, only a mutation should update the state.
+
+#### File structure
+When using Vuex at GitLab, separate this concerns into different files to improve readability. If you can, separate the Mutation Types as well:
+
+```
+└── store
+ ├── index.js # where we assemble modules and export the store
+ ├── actions.js # actions
+ ├── mutations.js # mutations
+ ├── getters.js # getters
+ └── mutation_types.js # mutation types
+```
+The following examples show an application that lists and adds users to the state.
+
+##### `index.js`
+This is the entry point for our store. You can use the following as a guide:
+
+```javascript
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ actions,
+ getters,
+ state: {
+ users: [],
+ },
+});
+```
+_Note:_ If the state of the application is too complex, an individual file for the state may be better.
+
+#### `actions.js`
+An action commits a mutatation. In this file, we will write the actions that will call the respective mutation:
+
+```javascript
+ import * as types from './mutation-types'
+
+ export const addUser = ({ commit }, user) => {
+ commit(types.ADD_USER, user);
+ };
+```
+
+To dispatch an action from a component, use the `mapActions` helper:
+```javascript
+import { mapActions } from 'vuex';
+
+{
+ methods: {
+ ...mapActions([
+ 'addUser',
+ ]),
+ onClickUser(user) {
+ this.addUser(user);
+ },
+ },
+};
+```
+
+#### `getters.js`
+Sometimes we may need to get derived state based on store state, like filtering for a specific prop. This can be done through the `getters`:
+
+```javascript
+// get all the users with pets
+export getUsersWithPets = (state, getters) => {
+ return state.users.filter(user => user.pet !== undefined);
+};
+```
+
+To access a getter from a component, use the `mapGetters` helper:
+```javascript
+import { mapGetters } from 'vuex';
+
+{
+ computed: {
+ ...mapGetters([
+ 'getUsersWithPets',
+ ]),
+ },
+};
+```
+
+#### `mutations.js`
+The only way to actually change state in a Vuex store is by committing a mutation.
+
+```javascript
+ import * as types from './mutation-types'
+ export default {
+ [types.ADD_USER](state, user) {
+ state.users.push(user);
+ },
+ };
+```
+
+#### `mutations_types.js`
+From [vuex mutations docs][vuex-mutations]:
+> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
+
+```javascript
+export const ADD_USER = 'ADD_USER';
+```
+
+### How to include the store in your application
+The store should be included in the main component of your application:
+```javascript
+ // app.vue
+ import store from 'store'; // it will include the index.js file
+
+ export default {
+ name: 'application',
+ store,
+ ...
+ };
+```
+
+### Vuex Gotchas
+1. Avoid calling a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency through out the application. From Vuex docs:
+
+ > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
+
+ ```javascript
+ // component.vue
+
+ // bad
+ created() {
+ this.$store.commit('mutation');
+ }
+
+ // good
+ created() {
+ this.$store.dispatch('action');
+ }
+ ```
+1. When possible, use mutation types instead of hardcoding strings. It will be less error prone.
+1. The State will be accessible in all components descending from the use where the store is instantiated.
+
+### Testing Vuex
+#### Testing Vuex concerns
+Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations.
+
+#### Testing components that need a store
+Smaller components might use `store` properties to access the data.
+In order to write unit tests for those components, we need to include the store and provide the correct state:
+
+```javascript
+//component_spec.js
+import Vue from 'vue';
+import store from './store';
+import component from './component.vue'
+
+describe('component', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(issueActions);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should show a user', () => {
+ const user = {
+ name: 'Foo',
+ age: '30',
+ };
+
+ // populate the store
+ store.dipatch('addUser', user);
+
+ vm = new Component({
+ store,
+ propsData: props,
+ }).$mount();
+ });
+});
+```
+
[vue-docs]: http://vuejs.org/guide/index.html
[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
@@ -493,3 +774,7 @@ new Component().$mount();
[vue-test]: https://vuejs.org/v2/guide/unit-testing.html
[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
[flux]: https://facebook.github.io/flux
+[vuex-docs]: https://vuex.vuejs.org
+[vuex-structure]: https://vuex.vuejs.org/en/structure.html
+[vuex-mutations]: https://vuex.vuejs.org/en/mutations.html
+[vuex-testing]: https://vuex.vuejs.org/en/testing.html
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 60da7b9166d..9a5811d8474 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -64,6 +64,7 @@ Libraries with the following licenses are unacceptable for use:
- [GNU GPL][GPL] (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects.
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
+- [Facebook BSD + PATENTS][Facebook]: is a 3-clause BSD license with a patent grant that has been deemed [Category X][x-list] by the Apache foundation.
## Requesting Approval for Licenses
@@ -103,5 +104,7 @@ Gems which are included only in the "development" or "test" groups by Bundler ar
[OSL-GNU]: https://www.gnu.org/licenses/license-list.en.html#OSL
[Org-Repo]: https://gitlab.com/gitlab-com/organization
[UNLICENSE]: https://unlicense.org
+[Facebook]: https://code.facebook.com/pages/850928938376556
+[x-list]: https://www.apache.org/legal/resolved.html#category-x
[Acceptable-Licenses]: #acceptable-licenses
[Unacceptable-Licenses]: #unacceptable-licenses
diff --git a/doc/integration/README.md b/doc/integration/README.md
index d70b9a7f54b..09d96bdd338 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -13,7 +13,6 @@ Bitbucket.org account
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
-- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
- [LDAP](ldap.md) Set up sign in via LDAP
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index cf28f1a2eca..793de9d777c 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -193,6 +193,38 @@ How to add your SSH key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Ecl
[winputty]: https://the.earth.li/~sgtatham/putty/0.67/htmldoc/Chapter8.html#pubkey-puttygen
+## SSH on the GitLab server
+
+GitLab integrates with the system-installed SSH daemon, designating a user
+(typically named `git`) through which all access requests are handled. Users
+connecting to the GitLab server over SSH are identified by their SSH key instead
+of their username.
+
+SSH *client* operations performed on the GitLab server wil be executed as this
+user. Although it is possible to modify the SSH configuration for this user to,
+e.g., provide a private SSH key to authenticate these requests by, this practice
+is **not supported** and is strongly discouraged as it presents significant
+security risks.
+
+The GitLab check process includes a check for this condition, and will direct you
+to this section if your server is configured like this, e.g.:
+
+```
+$ gitlab-rake gitlab:check
+# ...
+Git user has default SSH configuration? ... no
+ Try fixing it:
+ mkdir ~/gitlab-check-backup-1504540051
+ sudo mv /var/lib/git/.ssh/id_rsa ~/gitlab-check-backup-1504540051
+ sudo mv /var/lib/git/.ssh/id_rsa.pub ~/gitlab-check-backup-1504540051
+ For more information see:
+ doc/ssh/README.md in section "SSH on the GitLab server"
+ Please fix the error above and rerun the checks.
+```
+
+Remove the custom configuration as soon as you're able to. These customizations
+are *explicitly not supported* and may stop working at any time.
+
## Troubleshooting
If on Git clone you are prompted for a password like `git@gitlab.com's password:`
diff --git a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
new file mode 100644
index 00000000000..9a798ddd178
--- /dev/null
+++ b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 8b1d299484c..efea99eb120 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -116,6 +116,23 @@ are resolved.
![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
+### Automatically resolve merge request diff discussions when they become outdated
+
+> [Introduced][ce-14053] in GitLab 10.0.
+
+You can automatically resolve merge request diff discussions on lines modified
+with a new push.
+
+Navigate to your project's settings page, select the **Automatically resolve
+merge request diffs discussions on lines changed with a push** check box and hit
+**Save** for the changes to take effect.
+
+![Automatically resolve merge request diff discussions when they become outdated](img/automatically_resolve_outdated_discussions.png)
+
+From now on, any discussions on a diff will be resolved by default if a push
+makes that diff section outdated. Discussions on lines that don't change and
+top-level resolvable discussions are not automatically resolved.
+
## Threaded discussions
> [Introduced][ce-7527] in GitLab 9.1.
@@ -141,6 +158,7 @@ comments in greater detail.
[ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527
[ce-7180]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7180
[ce-8266]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8266
+[ce-14053]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14053
[resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index dcf210e1085..bd0a58c4cca 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -21,16 +21,16 @@ The following table depicts the various user permission levels in a project.
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View confidential issues | (✓) [^1] | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of jobs | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
-| See a job log | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse job artifacts | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
-| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | | ✓ | ✓ | ✓ | ✓ |
-| Download project | | ✓ | ✓ | ✓ | ✓ |
+| Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
+| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
+| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
+| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
+| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ |
+| Download project | [^1] | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
@@ -71,8 +71,8 @@ The following table depicts the various user permission levels in a project.
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
-| Force push to protected branches [^3] | | | | | |
-| Remove protected branches [^3] | | | | | |
+| Force push to protected branches [^4] | | | | | |
+| Remove protected branches [^4] | | | | | |
| Remove pages | | | | | ✓ |
## Project features permissions
@@ -215,13 +215,13 @@ users:
| Run CI job | | ✓ | ✓ | ✓ |
| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
-| Clone source and LFS from internal projects | | ✓ [^4] | ✓ [^4] | ✓ |
-| Clone source and LFS from private projects | | ✓ [^5] | ✓ [^5] | ✓ [^5] |
+| Clone source and LFS from internal projects | | ✓ [^5] | ✓ [^5] | ✓ |
+| Clone source and LFS from private projects | | ✓ [^6] | ✓ [^6] | ✓ [^6] |
| Push source and LFS | | | | |
| Pull container images from current project | | ✓ | ✓ | ✓ |
| Pull container images from public projects | | ✓ | ✓ | ✓ |
-| Pull container images from internal projects| | ✓ [^4] | ✓ [^4] | ✓ |
-| Pull container images from private projects | | ✓ [^5] | ✓ [^5] | ✓ [^5] |
+| Pull container images from internal projects| | ✓ [^5] | ✓ [^5] | ✓ |
+| Pull container images from private projects | | ✓ [^6] | ✓ [^6] | ✓ [^6] |
| Push container images to current project | | ✓ | ✓ | ✓ |
| Push container images to other projects | | | | |
@@ -243,12 +243,11 @@ with the permissions described on the documentation on [auditor users permission
Auditor users are available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/)
only.
-----
-
-[^1]: Guest users can only view the confidential issues they created themselves
-[^2]: If **Public pipelines** is enabled in **Project Settings > Pipelines**
-[^3]: Not allowed for Guest, Reporter, Developer, Master, or Owner
-[^4]: Only if user is not external one.
-[^5]: Only if user is a member of the project.
+[^1]: On public and internal projects, all users are able to perform this action.
+[^2]: Guest users can only view the confidential issues they created themselves
+[^3]: If **Public pipelines** is enabled in **Project Settings > Pipelines**
+[^4]: Not allowed for Guest, Reporter, Developer, Master, or Owner
+[^5]: Only if user is not external one.
+[^6]: Only if user is a member of the project.
[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
[new-mod]: project/new_ci_build_permissions_model.md
diff --git a/doc/user/project/import/cvs.md b/doc/user/project/import/cvs.md
new file mode 100644
index 00000000000..cabd0eef8d6
--- /dev/null
+++ b/doc/user/project/import/cvs.md
@@ -0,0 +1,68 @@
+# Migrating from CVS
+
+[CVS](https://savannah.nongnu.org/projects/cvs) is an old centralized version
+control system similar to [SVN](svn.md).
+
+## CVS vs Git
+
+The following list illustrates the main differences between CVS and Git:
+
+- **Git is distributed.** On the other hand, CVS is centralized using a client-server
+ architecture. This translates to Git having a more flexible workflow since
+ your working area is a copy of the entire repository. This decreases the
+ overhead when switching branches or merging for example, since you don't have
+ to communicate with a remote server.
+- **Atomic operations.** In Git all operations are
+ [atomic](https://en.wikipedia.org/wiki/Atomic_commit), either they succeed as
+ whole, or they fail without any changes. In CVS, commits (and other operations)
+ are not atomic. If an operation on the repository is interrupted in the middle,
+ the repository can be left in an inconsistent state.
+- **Storage method.** Changes in CVS are per file (changeset), while in Git
+ a committed file(s) is stored in its entirety (snapshot). That means that's
+ very easy in Git to revert or undo a whole change.
+- **Revision IDs.** The fact that in CVS changes are per files, the revision ID
+ is depicted by version numbers, for example `1.4` reflects how many time a
+ given file has been changed. In Git, each version of a project as a whole
+ (each commit) has its unique name given by SHA-1.
+- **Merge tracking.** Git uses a commit-before-merge approach rather than
+ merge-before-commit (or update-then-commit) like CVS. If while you were
+ preparing to create a new commit (new revision) somebody created a
+ new commit on the same branch and pushed to the central repository, CVS would
+ force you to first update your working directory and resolve conflicts before
+ allowing you to commit. This is not the case with Git. You first commit, save
+ your state in version control, then you merge the other developer's changes.
+ You can also ask the other developer to do the merge and resolve any conflicts
+ themselves.
+- **Signed commits.** Git supports signing your commits with GPG for additional
+ security and verification that the commit indeed came from its original author.
+ GitLab can [integrate with GPG](../repository/gpg_signed_commits/index.md)
+ and show whether a signed commit is correctly verified.
+
+_Some of the items above were taken from this great
+[Stack Overflow post](https://stackoverflow.com/a/824241/974710). For a more
+complete list of differences, consult the
+Wikipedia article on [comparing the different version control software](https://en.wikipedia.org/wiki/Comparison_of_version_control_software)._
+
+## Why migrate
+
+CVS is old with no new release since 2008. Git provides more tools to work
+with (`git bisect` for one) which makes for a more productive workflow.
+Migrating to Git/GitLab there is:
+
+- **Shorter learning curve**, Git has a big community and a vast number of
+ tutorials to get you started (see our [Git topic](../../../topics/git/index.md)).
+- **Integration with modern tools**, migrating to Git and GitLab you can have
+ an open source end-to-end software development platform with built-in version
+ control, issue tracking, code review, CI/CD, and more.
+- **Support for many network protocols**. Git supports SSH, HTTP/HTTPS and rsync
+ among others, whereas CVS supports only SSH and its own insecure pserver
+ protocol with no user authentication.
+
+## How to migrate
+
+Here's a few links to get you started with the migration:
+
+- [Migrate using the `cvs-fast-export` tool](http://www.catb.org/~esr/reposurgeon/dvcs-migration-guide.html) ([_source code_](https://gitlab.com/esr/cvs-fast-export))
+- [Stack Overflow post on importing the CVS repo](https://stackoverflow.com/a/11490134/974710)
+- [Convert a CVS repository to Git](http://www.techrepublic.com/blog/linux-and-open-source/convert-cvs-repositories-to-git/)
+- [Man page of the `git-cvsimport` tool](https://www.kernel.org/pub/software/scm/git/docs/git-cvsimport.html)
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 0f53ccc447b..0a6c8546e61 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -9,6 +9,7 @@
1. [From ClearCase](clearcase.md)
1. [From Perforce](perforce.md)
1. [From TFS](tfs.md)
+1. [From CVS](cvs.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 41a96246292..d6b3d59d407 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -67,8 +67,6 @@ website with GitLab Pages
**Other features:**
- [Cycle Analytics](cycle_analytics.md): Review your development lifecycle
-- [Koding integration](koding.md) (not available on GitLab.com): Integrate
-with Koding to have access to a web terminal right from the GitLab UI
- [Syntax highlighting](highlighting.md): An alternative to customize
your code blocks, overriding GitLab's default choice of language
diff --git a/doc/user/project/issues/img/confidential_issues_system_notes.png b/doc/user/project/issues/img/confidential_issues_system_notes.png
index 82e0dd8e85e..355be80ecb6 100755..100644
--- a/doc/user/project/issues/img/confidential_issues_system_notes.png
+++ b/doc/user/project/issues/img/confidential_issues_system_notes.png
Binary files differ
diff --git a/doc/user/project/koding.md b/doc/user/project/koding.md
index 455e2ee47b4..86e06a39e59 100644
--- a/doc/user/project/koding.md
+++ b/doc/user/project/koding.md
@@ -1,6 +1,9 @@
# Koding integration
-> [Introduced][ce-5909] in GitLab 8.11.
+>**Notes:**
+- **As of GitLab 10.0, the Koding integration is deprecated and will be removed
+ in a future version.**
+- [Introduced][ce-5909] in GitLab 8.11.
This document will guide you through using Koding integration on GitLab in
detail. For configuring and installing please follow the
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 3ff5a08d72c..dbc1305101f 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -66,10 +66,30 @@ in the pipelines settings page.
## Visibility of pipelines
-For public and internal projects, the pipelines page can be accessed by
-anyone and those logged in respectively. If you wish to hide it so that only
-the members of the project or group have access to it, uncheck the **Public
-pipelines** checkbox and save the changes.
+Access to pipelines and job details (including output of logs and artifacts)
+is checked against your current user access level and the **Public pipelines**
+project setting.
+
+If **Public pipelines** is enabled (default):
+
+- for **public** projects, anyone can view the pipelines and access the job details
+ (output logs and artifacts)
+- for **internal** projects, any logged in user can view the pipelines
+ and access the job details
+ (output logs and artifacts)
+- for **private** projects, any member (guest or higher) can view the pipelines
+ and access the job details
+ (output logs and artifacts)
+
+If **Public pipelines** is disabled:
+
+- for **public** projects, anyone can view the pipelines, but only members
+ (reporter or higher) can access the job details (output logs and artifacts)
+- for **internal** projects, any logged in user can view the pipelines,
+ but only members (reporter or higher) can access the job details (output logs
+ and artifacts)
+- for **private** projects, only members (reporter or higher)
+ can view the pipelines and access the job details (output logs and artifacts)
## Auto-cancel pending pipelines
diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png
index 33936a7d6d7..088ecfa6d89 100644
--- a/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png
+++ b/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png
Binary files differ
diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png
index 22565cf7c7e..4e3392406b1 100644
--- a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png
+++ b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png
Binary files differ
diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png
index 1778b2ddf2b..766970dee81 100644
--- a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png
+++ b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png
Binary files differ
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index ff419d714f9..20aadb8f7ff 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -22,14 +22,25 @@ GitLab uses its own keyring to verify the GPG signature. It does not access any
public key server.
In order to have a commit verified on GitLab the corresponding public key needs
-to be uploaded to GitLab. For a signature to be verified two prerequisites need
+to be uploaded to GitLab. For a signature to be verified three conditions need
to be met:
1. The public key needs to be added your GitLab account
1. One of the emails in the GPG key matches your **primary** email
+1. The committer's email matches the verified email from the gpg key
## Generating a GPG key
+>**Notes:**
+- If your Operating System has `gpg2` installed, replace `gpg` with `gpg2` in
+ the following commands.
+- If Git is using `gpg` and you get errors like `secret key not available` or
+ `gpg: signing failed: secret key not available`, run the following command to
+ change to `gpg2`:
+ ```
+ git config --global gpg.program gpg2
+ ```
+
If you don't already have a GPG key, the following steps will help you get
started:
diff --git a/doc/user/search/img/issue_search_by_term.png b/doc/user/search/img/issue_search_by_term.png
new file mode 100644
index 00000000000..3cefa3adb8b
--- /dev/null
+++ b/doc/user/search/img/issue_search_by_term.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index f5c7ce49e8e..21e96d8b11c 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,20 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+### Searching for specific terms
+
+You can filter issues and merge requests by specific terms included in titles or descriptions.
+
+* Syntax
+ * Searches look for all the words in a query, in any order. E.g.: searching
+ issues for `display bug` will return all issues matching both those words, in any order.
+ * To find the exact term, use double quotes: `"display bug"`
+* Limitation
+ * For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
+ issues for `included in titles` is same as `included titles`
+
+![filter issues by specific terms](img/issue_search_by_term.png)
+
### Issues and merge requests per group
Similar to **Issues and merge requests per project**, you can also search for issues
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 8fb2ac34c32..962e39dde9a 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -36,13 +36,13 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I should see project "Community" home page' do
- page.within '.breadcrumbs .title' do
+ page.within '.breadcrumbs .breadcrumb-item-text' do
expect(page).to have_content 'Community'
end
end
step 'I should see project "Internal" home page' do
- page.within '.breadcrumbs .title' do
+ page.within '.breadcrumbs .breadcrumb-item-text' do
expect(page).to have_content 'Internal'
end
end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 100e674abed..9ce86ca45d0 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -18,7 +18,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should see project "Community" home page' do
Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
- page.within '.breadcrumbs .title' do
+ page.within '.breadcrumbs .breadcrumb-item-text' do
expect(page).to have_content 'Community'
end
end
diff --git a/features/support/gitaly.rb b/features/support/gitaly.rb
new file mode 100644
index 00000000000..3cd5f4ce497
--- /dev/null
+++ b/features/support/gitaly.rb
@@ -0,0 +1,3 @@
+Spinach.hooks.before_scenario do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
+end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index a989394ad91..642c1140fcc 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -24,17 +24,22 @@ module API
present paginate(branches), with: Entities::RepoBranch, project: user_project
end
- desc 'Get a single branch' do
- success Entities::RepoBranch
- end
- params do
- requires :branch, type: String, desc: 'The name of the branch'
- end
- get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
- branch = user_project.repository.find_branch(params[:branch])
- not_found!("Branch") unless branch
+ resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
+ desc 'Get a single branch' do
+ success Entities::RepoBranch
+ end
+ params do
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ head do
+ user_project.repository.branch_exists?(params[:branch]) ? status(204) : status(404)
+ end
+ get do
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
- present branch, with: Entities::RepoBranch, project: user_project
+ present branch, with: Entities::RepoBranch, project: user_project
+ end
end
# Note: This API will be deprecated in favor of the protected branches API.
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 6314ea63197..829eef18795 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -103,7 +103,7 @@ module API
when 'success'
status.success!
when 'failed'
- status.drop!
+ status.drop!(:api_failure)
when 'canceled'
status.cancel!
else
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index f13f2d723bb..9114b69606b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -119,6 +119,7 @@ module API
expose :archived?, as: :archived
expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
@@ -491,6 +492,10 @@ module API
expose :user, using: Entities::UserPublic
end
+ class GPGKey < Grape::Entity
+ expose :id, :key, :created_at
+ end
+
class Note < Grape::Entity
# Only Issue and MergeRequest have iid
NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
@@ -819,7 +824,7 @@ module API
class Variable < Grape::Entity
expose :key, :value
- expose :protected?, as: :protected
+ expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
end
class Pipeline < PipelineBasic
@@ -840,6 +845,7 @@ module API
class PipelineScheduleDetails < PipelineSchedule
expose :last_pipeline, using: Entities::PipelineBasic
+ expose :variables, using: Entities::Variable
end
class EnvironmentBasic < Grape::Entity
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index f57ff0f2632..4c0db4d42b1 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -46,6 +46,15 @@ module API
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
+ def redis_ping
+ result = Gitlab::Redis::SharedState.with { |redis| redis.ping }
+
+ result == 'PONG'
+ rescue => e
+ Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
+ false
+ end
+
private
def set_project
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 622bd9650e4..c0fef56378f 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -88,7 +88,8 @@ module API
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
- gitlab_rev: Gitlab::REVISION
+ gitlab_rev: Gitlab::REVISION,
+ redis: redis_ping
}
end
@@ -142,6 +143,14 @@ module API
{ success: true, recovery_codes: codes }
end
+ post '/pre_receive' do
+ status 200
+
+ reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
+
+ { reference_counter_increased: reference_counter_increased }
+ end
+
post "/notify_post_receive" do
status 200
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index e4c2c390853..1729df2aad0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -36,6 +36,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 7bcbf9f20ff..56d72d511da 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -40,6 +40,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
end
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index ef01cbc7875..37f32411296 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -31,10 +31,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
-
present pipeline_schedule, with: Entities::PipelineScheduleDetails
end
@@ -74,9 +70,6 @@ module API
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
@@ -93,9 +86,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.own!(current_user)
@@ -112,21 +102,84 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :admin_pipeline_schedule, pipeline_schedule
destroy_conditionally!(pipeline_schedule)
end
+
+ desc 'Create a new pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ variable_params = declared_params(include_missing: false)
+ variable = pipeline_schedule.variables.create(variable_params)
+ if variable.persisted?
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Edit a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule_variable.update(declared_params(include_missing: false))
+ present pipeline_schedule_variable, with: Entities::Variable
+ else
+ render_validation_error!(pipeline_schedule_variable)
+ end
+ end
+
+ desc 'Delete a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :admin_pipeline_schedule, pipeline_schedule
+
+ status :accepted
+ present pipeline_schedule_variable.destroy, with: Entities::Variable
+ end
end
helpers do
def pipeline_schedule
@pipeline_schedule ||=
- user_project.pipeline_schedules
- .preload(:owner, :last_pipeline)
- .find_by(id: params.delete(:pipeline_schedule_id))
+ user_project
+ .pipeline_schedules
+ .preload(:owner, :last_pipeline)
+ .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule|
+ unless can?(current_user, :read_pipeline_schedule, pipeline_schedule)
+ not_found!('Pipeline Schedule')
+ end
+ end
+ end
+
+ def pipeline_schedule_variable
+ @pipeline_schedule_variable ||=
+ pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable|
+ unless pipeline_schedule_variable
+ not_found!('Pipeline Schedule Variable')
+ end
+ end
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 4845242a173..7dc19788462 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,6 +16,7 @@ module API
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
@@ -236,6 +237,7 @@ module API
at_least_one_of_ce =
[
:jobs_enabled,
+ :resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
:description,
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 11999354594..a3987c560dd 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -114,6 +114,8 @@ module API
requires :id, type: Integer, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed)
+ optional :failure_reason, type: String, values: CommitStatus.failure_reasons.keys,
+ desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
@@ -127,7 +129,7 @@ module API
when 'success'
job.success
when 'failed'
- job.drop
+ job.drop(params[:failure_reason] || :unknown_failure)
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 96f47bb618a..1825c90a23b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -233,6 +233,86 @@ module API
destroy_conditionally!(key)
end
+ desc 'Add a GPG key to a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params.delete(:id))
+ not_found!('User') unless user
+
+ key = user.gpg_keys.new(declared_params(include_missing: false))
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Get the GPG keys of a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ delete ':id/gpg_keys/:key_id' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ status 204
+ key.destroy
+ end
+
+ desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post ':id/gpg_keys/:key_id/revoke' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
desc 'Add an email address to a specified user. Available only for admins.' do
success Entities::Email
end
@@ -492,6 +572,76 @@ module API
destroy_conditionally!(key)
end
+ desc "Get the currently authenticated user's GPG keys" do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ use :pagination
+ end
+ get 'gpg_keys' do
+ present paginate(current_user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Get a single GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ get 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ present key, with: Entities::GPGKey
+ end
+
+ desc 'Add a new GPG key to the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post 'gpg_keys' do
+ key = current_user.gpg_keys.new(declared_params)
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Revoke a GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post 'gpg_keys/:key_id/revoke' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
+ desc 'Delete a GPG key from the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ status 204
+ key.destroy
+ end
+
desc "Get the currently authenticated user's email addresses" do
success Entities::Email
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index a9a35f2a4bd..ac47a713966 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -64,6 +64,7 @@ module API
expose :owner, using: ::API::Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 449876c10d9..74df246bdfe 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -18,6 +18,7 @@ module API
optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
@@ -296,9 +297,9 @@ module API
use :optional_params
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
- :shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :public, :visibility_level, :public_builds,
- :request_access_enabled, :only_allow_merge_if_build_succeeds,
+ :shared_runners_enabled, :resolve_outdated_diff_discussions,
+ :container_registry_enabled, :lfs_enabled, :public, :visibility_level,
+ :public_builds, :request_access_enabled, :only_allow_merge_if_build_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index e9d4c35307b..534911fde5c 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -16,25 +16,31 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
- project = find_project(params[:id])
- trigger = Ci::Trigger.find_by_token(params[:token].to_s)
- not_found! unless project && trigger
- unauthorized! unless trigger.project == project
-
# validate variables
- variables = params[:variables].to_h
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ params[:variables] = params[:variables].to_h
+ unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
- # create request and trigger builds
- result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
- pipeline = result.pipeline
+ project = find_project(params[:id])
+ not_found! unless project
+
+ result = Ci::PipelineTriggerService.new(project, nil, params).execute
+ not_found! unless result
- if pipeline.persisted?
- present result.trigger_request, with: ::API::V3::Entities::TriggerRequest
+ if result[:http_status]
+ render_api_error!(result[:message], result[:http_status])
else
- render_validation_error!(pipeline)
+ pipeline = result[:pipeline]
+
+ # We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables.
+ # Ci::TriggerRequest doesn't save variables anymore.
+ # Here is copying Ci::PipelineVariable to Ci::TriggerRequest.variables for presenting the variables.
+ # The same endpoint in v4 API pressents Pipeline instead of TriggerRequest, so it doesn't need such a process.
+ trigger_request = pipeline.trigger_requests.last
+ trigger_request.variables = params[:variables]
+
+ present trigger_request, with: ::API::V3::Entities::TriggerRequest
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index c67f7724307..75d4efc0bc5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -134,15 +134,19 @@ module Gitlab
# This is to work around a bug in libgit2 that causes in-memory refs to
# be stale/invalid when packed-refs is changed.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
- #
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/474
def find_branch(name, force_reload = false)
- reload_rugged if force_reload
+ gitaly_migrate(:find_branch) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.find_branch(name)
+ else
+ reload_rugged if force_reload
- rugged_ref = rugged.branches[name]
- if rugged_ref
- target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ rugged_ref = rugged.branches[name]
+ if rugged_ref
+ target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
+ Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ end
+ end
end
end
@@ -605,6 +609,49 @@ module Gitlab
# TODO: implement this method
end
+ def add_branch(branch_name, committer:, target:)
+ target_object = Ref.dereference_object(lookup(target))
+ raise InvalidRef.new("target not found: #{target}") unless target_object
+
+ OperationService.new(committer, self).add_branch(branch_name, target_object.oid)
+ find_branch(branch_name)
+ rescue Rugged::ReferenceError => ex
+ raise InvalidRef, ex
+ end
+
+ def add_tag(tag_name, committer:, target:, message: nil)
+ target_object = Ref.dereference_object(lookup(target))
+ raise InvalidRef.new("target not found: #{target}") unless target_object
+
+ committer = Committer.from_user(committer) if committer.is_a?(User)
+
+ options = nil # Use nil, not the empty hash. Rugged cares about this.
+ if message
+ options = {
+ message: message,
+ tagger: Gitlab::Git.committer_hash(email: committer.email, name: committer.name)
+ }
+ end
+
+ OperationService.new(committer, self).add_tag(tag_name, target_object.oid, options)
+
+ find_tag(tag_name)
+ rescue Rugged::ReferenceError => ex
+ raise InvalidRef, ex
+ end
+
+ def rm_branch(branch_name, committer:)
+ OperationService.new(committer, self).rm_branch(find_branch(branch_name))
+ end
+
+ def rm_tag(tag_name, committer:)
+ OperationService.new(committer, self).rm_tag(find_tag(tag_name))
+ end
+
+ def find_tag(name)
+ tags.find { |tag| tag.name == name }
+ end
+
# Delete the specified branch from the repository
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/476
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 8c0008c6971..a1a25cf2079 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -78,6 +78,20 @@ module Gitlab
raise ArgumentError, e.message
end
+ def find_branch(branch_name)
+ request = Gitaly::DeleteBranchRequest.new(
+ repository: @gitaly_repo,
+ name: GitalyClient.encode(branch_name)
+ )
+
+ response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request)
+ branch = response.branch
+ return unless branch
+
+ target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
+ Gitlab::Git::Branch.new(@repository, encode!(branch.name.dup), branch.target_commit.id, target_commit)
+ end
+
private
def consume_refs_response(response)
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index 45e9f9d65ae..025f826e65f 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -39,7 +39,7 @@ module Gitlab
fingerprints = CurrentKeyChain.fingerprints_from_key(key)
GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
- raw_key.uids.map { |uid| { name: uid.name, email: uid.email } }
+ raw_key.uids.map { |uid| { name: uid.name, email: uid.email.downcase } }
end
end
end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 606c7576f70..86bd9f5b125 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -1,17 +1,12 @@
module Gitlab
module Gpg
class Commit
- def self.for_commit(commit)
- new(commit.project, commit.sha)
- end
-
- def initialize(project, sha)
- @project = project
- @sha = sha
+ def initialize(commit)
+ @commit = commit
@signature_text, @signed_text =
begin
- Rugged::Commit.extract_signature(project.repository.rugged, sha)
+ Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha)
rescue Rugged::OdbError
nil
end
@@ -26,7 +21,7 @@ module Gitlab
return @signature if @signature
- cached_signature = GpgSignature.find_by(commit_sha: @sha)
+ cached_signature = GpgSignature.find_by(commit_sha: @commit.sha)
return @signature = cached_signature if cached_signature.present?
@signature = create_cached_signature!
@@ -73,20 +68,31 @@ module Gitlab
def attributes(gpg_key)
user_infos = user_infos(gpg_key)
+ verification_status = verification_status(gpg_key)
{
- commit_sha: @sha,
- project: @project,
+ commit_sha: @commit.sha,
+ project: @commit.project,
gpg_key: gpg_key,
gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
gpg_key_user_name: user_infos[:name],
gpg_key_user_email: user_infos[:email],
- valid_signature: gpg_signature_valid_signature_value(gpg_key)
+ verification_status: verification_status
}
end
- def gpg_signature_valid_signature_value(gpg_key)
- !!(gpg_key && gpg_key.verified? && verified_signature.valid?)
+ def verification_status(gpg_key)
+ return :unknown_key unless gpg_key
+ return :unverified_key unless gpg_key.verified?
+ return :unverified unless verified_signature.valid?
+
+ if gpg_key.verified_and_belongs_to_email?(@commit.committer_email)
+ :verified
+ elsif gpg_key.user.all_emails.include?(@commit.committer_email)
+ :same_user_different_email
+ else
+ :other_user
+ end
end
def user_infos(gpg_key)
diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
index a525ee7a9ee..e085eab26c9 100644
--- a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
+++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
@@ -8,7 +8,7 @@ module Gitlab
def run
GpgSignature
.select(:id, :commit_sha, :project_id)
- .where('gpg_key_id IS NULL OR valid_signature = ?', false)
+ .where('gpg_key_id IS NULL OR verification_status <> ?', GpgSignature.verification_statuses[:verified])
.where(gpg_key_primary_keyid: @gpg_key.primary_keyid)
.find_each { |sig| sig.gpg_commit.update_signature!(sig) }
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 78795dd3d92..ec73846d844 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -116,6 +116,7 @@ excluded_attributes:
statuses:
- :trace
- :token
+ - :when
push_event_payload:
- :event_id
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index cbc8d170936..3bc095a99a9 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -9,6 +9,8 @@ module Gitlab
@user = user
@shared = shared
@project = project
+ @project_id = project.id
+ @saved = true
end
def restore
@@ -22,8 +24,10 @@ module Gitlab
@project_members = @tree_hash.delete('project_members')
- ActiveRecord::Base.no_touching do
- create_relations
+ ActiveRecord::Base.uncached do
+ ActiveRecord::Base.no_touching do
+ create_relations
+ end
end
rescue => e
@shared.error(e)
@@ -48,21 +52,24 @@ module Gitlab
# the configuration yaml file too.
# Finally, it updates each attribute in the newly imported project.
def create_relations
- saved = []
default_relation_list.each do |relation|
- next unless relation.is_a?(Hash) || @tree_hash[relation.to_s].present?
+ if relation.is_a?(Hash)
+ create_sub_relations(relation, @tree_hash)
+ elsif @tree_hash[relation.to_s].present?
+ save_relation_hash(@tree_hash[relation.to_s], relation)
+ end
+ end
- create_sub_relations(relation, @tree_hash) if relation.is_a?(Hash)
+ @saved
+ end
- relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
- relation_hash_list = @tree_hash[relation_key.to_s]
+ def save_relation_hash(relation_hash_batch, relation_key)
+ relation_hash = create_relation(relation_key, relation_hash_batch)
- next unless relation_hash_list
+ @saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash)
- relation_hash = create_relation(relation_key, relation_hash_list)
- saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
- end
- saved.all?
+ # Restore the project again, extra query that skips holding the AR objects in memory
+ @restored_project = Project.find(@project_id)
end
def default_relation_list
@@ -93,20 +100,42 @@ module Gitlab
# issue, finds any subrelations such as notes, creates them and assign them back to the hash
#
# Recursively calls this method if the sub-relation is a hash containing more sub-relations
- def create_sub_relations(relation, tree_hash)
+ def create_sub_relations(relation, tree_hash, save: true)
relation_key = relation.keys.first.to_s
return if tree_hash[relation_key].blank?
- [tree_hash[relation_key]].flatten.each do |relation_item|
- relation.values.flatten.each do |sub_relation|
- # We just use author to get the user ID, do not attempt to create an instance.
- next if sub_relation == :author
+ tree_array = [tree_hash[relation_key]].flatten
+
+ # Avoid keeping a possible heavy object in memory once we are done with it
+ while relation_item = tree_array.shift
+ # The transaction at this level is less speedy than one single transaction
+ # But we can't have it in the upper level or GC won't get rid of the AR objects
+ # after we save the batch.
+ Project.transaction do
+ process_sub_relation(relation, relation_item)
+
+ # For every subrelation that hangs from Project, save the associated records alltogether
+ # This effectively batches all records per subrelation item, only keeping those in memory
+ # We have to keep in mind that more batch granularity << Memory, but >> Slowness
+ if save
+ save_relation_hash([relation_item], relation_key)
+ tree_hash[relation_key].delete(relation_item)
+ end
+ end
+ end
+
+ tree_hash.delete(relation_key) if save
+ end
+
+ def process_sub_relation(relation, relation_item)
+ relation.values.flatten.each do |sub_relation|
+ # We just use author to get the user ID, do not attempt to create an instance.
+ next if sub_relation == :author
- create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash)
+ create_sub_relations(sub_relation, relation_item, save: false) if sub_relation.is_a?(Hash)
- relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
- relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
- end
+ relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
+ relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
end
end
@@ -121,14 +150,12 @@ module Gitlab
end
def create_relation(relation, relation_hash_list)
- relation_type = relation.to_sym
-
relation_array = [relation_hash_list].flatten.map do |relation_hash|
- Gitlab::ImportExport::RelationFactory.create(relation_sym: relation_type,
- relation_hash: parsed_relation_hash(relation_hash, relation_type),
+ Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym,
+ relation_hash: parsed_relation_hash(relation_hash, relation.to_sym),
members_mapper: members_mapper,
user: @user,
- project: restored_project)
+ project: @restored_project)
end.compact
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 5d6de8bc475..9fd0b709ef2 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -16,7 +16,7 @@ module Gitlab
error_out(error.message, caller[0].dup)
@errors << error.message
# Debug:
- Rails.logger.error(error.backtrace)
+ Rails.logger.error(error.backtrace.join("\n"))
end
private
diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb
new file mode 100644
index 00000000000..505810964bc
--- /dev/null
+++ b/lib/gitlab/issuables_count_for_state.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ # Class for counting and caching the number of issuables per state.
+ class IssuablesCountForState
+ # The name of the RequestStore cache key.
+ CACHE_KEY = :issuables_count_for_state
+
+ # The state values that can be safely casted to a Symbol.
+ STATES = %w[opened closed merged all].freeze
+
+ # finder - The finder class to use for retrieving the issuables.
+ def initialize(finder)
+ @finder = finder
+ @cache =
+ if RequestStore.active?
+ RequestStore[CACHE_KEY] ||= initialize_cache
+ else
+ initialize_cache
+ end
+ end
+
+ def for_state_or_opened(state = nil)
+ self[state || :opened]
+ end
+
+ # Returns the count for the given state.
+ #
+ # state - The name of the state as either a String or a Symbol.
+ #
+ # Returns an Integer.
+ def [](state)
+ state = state.to_sym if cast_state_to_symbol?(state)
+
+ cache_for_finder[state] || 0
+ end
+
+ private
+
+ def cache_for_finder
+ @cache[@finder]
+ end
+
+ def cast_state_to_symbol?(state)
+ state.is_a?(String) && STATES.include?(state)
+ end
+
+ def initialize_cache
+ Hash.new { |hash, finder| hash[finder] = finder.count_by_state }
+ end
+ end
+end
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index b42bc67ccfc..7c2d1d8f887 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -4,6 +4,7 @@ module Gitlab
extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3
+ REGEX_QUOTED_WORD = /(?<=^| )"[^"]+"(?= |$)/
class_methods do
def to_pattern(query)
@@ -17,6 +18,28 @@ module Gitlab
def partial_matching?(query)
query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
end
+
+ def to_fuzzy_arel(column, query)
+ words = select_fuzzy_words(query)
+
+ matches = words.map { |word| arel_table[column].matches(to_pattern(word)) }
+
+ matches.reduce { |result, match| result.and(match) }
+ end
+
+ def select_fuzzy_words(query)
+ quoted_words = query.scan(REGEX_QUOTED_WORD)
+
+ query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
+
+ words = query.split(/\s+/)
+
+ quoted_words.map! { |quoted_word| quoted_word[1..-2] }
+
+ words.concat(quoted_words)
+
+ words.select { |word| partial_matching?(word) }
+ end
end
end
end
diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb
new file mode 100644
index 00000000000..7b486d78cf0
--- /dev/null
+++ b/lib/system_check/app/git_user_default_ssh_config_check.rb
@@ -0,0 +1,69 @@
+module SystemCheck
+ module App
+ class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck
+ # These files are allowed in the .ssh directory. The `config` file is not
+ # whitelisted as it may change the SSH client's behaviour dramatically.
+ WHITELIST = %w[
+ authorized_keys
+ authorized_keys2
+ known_hosts
+ ].freeze
+
+ set_name 'Git user has default SSH configuration?'
+ set_skip_reason 'skipped (git user is not present or configured)'
+
+ def skip?
+ !home_dir || !File.directory?(home_dir)
+ end
+
+ def check?
+ forbidden_files.empty?
+ end
+
+ def show_error
+ backup_dir = "~/gitlab-check-backup-#{Time.now.to_i}"
+
+ instructions = forbidden_files.map do |filename|
+ "sudo mv #{Shellwords.escape(filename)} #{backup_dir}"
+ end
+
+ try_fixing_it("mkdir #{backup_dir}", *instructions)
+ for_more_information('doc/ssh/README.md in section "SSH on the GitLab server"')
+ fix_and_rerun
+ end
+
+ private
+
+ def git_user
+ Gitlab.config.gitlab.user
+ end
+
+ def home_dir
+ return @home_dir if defined?(@home_dir)
+
+ @home_dir =
+ begin
+ File.expand_path("~#{git_user}")
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ def ssh_dir
+ return nil unless home_dir
+
+ File.join(home_dir, '.ssh')
+ end
+
+ def forbidden_files
+ @forbidden_files ||=
+ begin
+ present = Dir[File.join(ssh_dir, '*')]
+ whitelisted = WHITELIST.map { |basename| File.join(ssh_dir, basename) }
+
+ present - whitelisted
+ end
+ end
+ end
+ end
+end
diff --git a/lib/system_check/app/init_script_up_to_date_check.rb b/lib/system_check/app/init_script_up_to_date_check.rb
index 015c7ed1731..53a47eb0f42 100644
--- a/lib/system_check/app/init_script_up_to_date_check.rb
+++ b/lib/system_check/app/init_script_up_to_date_check.rb
@@ -7,26 +7,22 @@ module SystemCheck
set_skip_reason 'skipped (omnibus-gitlab has no init script)'
def skip?
- omnibus_gitlab?
- end
+ return true if omnibus_gitlab?
- def multi_check
- recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab')
+ unless init_file_exists?
+ self.skip_reason = "can't check because of previous errors"
- unless File.exist?(SCRIPT_PATH)
- $stdout.puts "can't check because of previous errors".color(:magenta)
- return
+ true
end
+ end
+
+ def check?
+ recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab')
recipe_content = File.read(recipe_path)
script_content = File.read(SCRIPT_PATH)
- if recipe_content == script_content
- $stdout.puts 'yes'.color(:green)
- else
- $stdout.puts 'no'.color(:red)
- show_error
- end
+ recipe_content == script_content
end
def show_error
@@ -38,6 +34,12 @@ module SystemCheck
)
fix_and_rerun
end
+
+ private
+
+ def init_file_exists?
+ File.exist?(SCRIPT_PATH)
+ end
end
end
end
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index 7f9e2ffffc2..0f5742dd67f 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -62,6 +62,25 @@ module SystemCheck
call_or_return(@skip_reason) || 'skipped'
end
+ # Define a reason why we skipped the SystemCheck (during runtime)
+ #
+ # This is used when you need dynamic evaluation like when you have
+ # multiple reasons why a check can fail
+ #
+ # @param [String] reason to be displayed
+ def skip_reason=(reason)
+ @skip_reason = reason
+ end
+
+ # Skip reason defined during runtime
+ #
+ # This value have precedence over the one defined in the subclass
+ #
+ # @return [String] the reason
+ def skip_reason
+ @skip_reason
+ end
+
# Does the check support automatically repair routine?
#
# @return [Boolean] whether check implemented `#repair!` method or not
diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb
new file mode 100644
index 00000000000..1db7bf2b782
--- /dev/null
+++ b/lib/system_check/incoming_email/foreman_configured_check.rb
@@ -0,0 +1,23 @@
+module SystemCheck
+ module IncomingEmail
+ class ForemanConfiguredCheck < SystemCheck::BaseCheck
+ set_name 'Foreman configured correctly?'
+
+ def check?
+ path = Rails.root.join('Procfile')
+
+ File.exist?(path) && File.read(path) =~ /^mail_room:/
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Enable mail_room in your Procfile.'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb
new file mode 100644
index 00000000000..dee108d987b
--- /dev/null
+++ b/lib/system_check/incoming_email/imap_authentication_check.rb
@@ -0,0 +1,45 @@
+module SystemCheck
+ module IncomingEmail
+ class ImapAuthenticationCheck < SystemCheck::BaseCheck
+ set_name 'IMAP server credentials are correct?'
+
+ def check?
+ if mailbox_config
+ begin
+ imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
+ imap.starttls if config[:start_tls]
+ imap.login(config[:email], config[:password])
+ connected = true
+ rescue
+ connected = false
+ end
+ end
+
+ connected
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Check that the information in config/gitlab.yml is correct'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mailbox_config
+ return @config if @config
+
+ config_path = Rails.root.join('config', 'mail_room.yml').to_s
+ erb = ERB.new(File.read(config_path))
+ erb.filename = config_path
+ config_file = YAML.load(erb.result)
+
+ @config = config_file[:mailboxes]&.first
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/initd_configured_check.rb b/lib/system_check/incoming_email/initd_configured_check.rb
new file mode 100644
index 00000000000..ea23b8ef49c
--- /dev/null
+++ b/lib/system_check/incoming_email/initd_configured_check.rb
@@ -0,0 +1,32 @@
+module SystemCheck
+ module IncomingEmail
+ class InitdConfiguredCheck < SystemCheck::BaseCheck
+ set_name 'Init.d configured correctly?'
+
+ def skip?
+ omnibus_gitlab?
+ end
+
+ def check?
+ mail_room_configured?
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Enable mail_room in the init.d configuration.'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mail_room_configured?
+ path = '/etc/default/gitlab'
+ File.exist?(path) && File.read(path).include?('mail_room_enabled=true')
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/mail_room_running_check.rb b/lib/system_check/incoming_email/mail_room_running_check.rb
new file mode 100644
index 00000000000..c1807501829
--- /dev/null
+++ b/lib/system_check/incoming_email/mail_room_running_check.rb
@@ -0,0 +1,43 @@
+module SystemCheck
+ module IncomingEmail
+ class MailRoomRunningCheck < SystemCheck::BaseCheck
+ set_name 'MailRoom running?'
+
+ def skip?
+ return true if omnibus_gitlab?
+
+ unless mail_room_configured?
+ self.skip_reason = "can't check because of previous errors"
+ true
+ end
+ end
+
+ def check?
+ mail_room_running?
+ end
+
+ def show_error
+ try_fixing_it(
+ sudo_gitlab('RAILS_ENV=production bin/mail_room start')
+ )
+ for_more_information(
+ see_installation_guide_section('Install Init Script'),
+ 'see log/mail_room.log for possible errors'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mail_room_configured?
+ path = '/etc/default/gitlab'
+ File.exist?(path) && File.read(path).include?('mail_room_enabled=true')
+ end
+
+ def mail_room_running?
+ ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww))
+ ps_ux.include?("mail_room")
+ end
+ end
+ end
+end
diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb
index 6604b1078cf..00221f77cf4 100644
--- a/lib/system_check/simple_executor.rb
+++ b/lib/system_check/simple_executor.rb
@@ -23,7 +23,7 @@ module SystemCheck
#
# @param [BaseCheck] check class
def <<(check)
- raise ArgumentError unless check < BaseCheck
+ raise ArgumentError unless check.is_a?(Class) && check < BaseCheck
@checks << check
end
@@ -48,7 +48,7 @@ module SystemCheck
# When implements skip method, we run it first, and if true, skip the check
if check.can_skip? && check.skip?
- $stdout.puts check_klass.skip_reason.color(:magenta)
+ $stdout.puts check.skip_reason.try(:color, :magenta) || check_klass.skip_reason.color(:magenta)
return
end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index f7f2fa2f14c..35ba729c156 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -1,5 +1,4 @@
require "gettext_i18n_rails/tasks"
-require 'simple_po_parser'
namespace :gettext do
# Customize list of translatable files
@@ -23,6 +22,8 @@ namespace :gettext do
desc 'Lint all po files in `locale/'
task lint: :environment do
+ require 'simple_po_parser'
+
FastGettext.silence_errors
files = Dir.glob(Rails.root.join('locale/*/gitlab.po'))
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 1bd36bbe20a..654f638c454 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -33,6 +33,7 @@ namespace :gitlab do
SystemCheck::App::RedisVersionCheck,
SystemCheck::App::RubyVersionCheck,
SystemCheck::App::GitVersionCheck,
+ SystemCheck::App::GitUserDefaultSSHConfigCheck,
SystemCheck::App::ActiveUsersCheck
]
@@ -308,133 +309,24 @@ namespace :gitlab do
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
warn_user_is_not_gitlab
- start_checking "Reply by email"
if Gitlab.config.incoming_email.enabled
- check_imap_authentication
+ checks = [
+ SystemCheck::IncomingEmail::ImapAuthenticationCheck
+ ]
if Rails.env.production?
- check_initd_configured_correctly
- check_mail_room_running
+ checks << SystemCheck::IncomingEmail::InitdConfiguredCheck
+ checks << SystemCheck::IncomingEmail::MailRoomRunningCheck
else
- check_foreman_configured_correctly
+ checks << SystemCheck::IncomingEmail::ForemanConfiguredCheck
end
- else
- puts 'Reply by email is disabled in config/gitlab.yml'
- end
-
- finished_checking "Reply by email"
- end
-
- # Checks
- ########################
-
- def check_initd_configured_correctly
- return if omnibus_gitlab?
-
- print "Init.d configured correctly? ... "
-
- path = "/etc/default/gitlab"
-
- if File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- "Enable mail_room in the init.d configuration."
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
- end
- end
-
- def check_foreman_configured_correctly
- print "Foreman configured correctly? ... "
- path = Rails.root.join("Procfile")
-
- if File.exist?(path) && File.read(path) =~ /^mail_room:/
- puts "yes".color(:green)
+ SystemCheck.run('Reply by email', checks)
else
- puts "no".color(:red)
- try_fixing_it(
- "Enable mail_room in your Procfile."
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
- end
- end
-
- def check_mail_room_running
- return if omnibus_gitlab?
-
- print "MailRoom running? ... "
-
- path = "/etc/default/gitlab"
-
- unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "can't check because of previous errors".color(:magenta)
- return
- end
-
- if mail_room_running?
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- sudo_gitlab("RAILS_ENV=production bin/mail_room start")
- )
- for_more_information(
- see_installation_guide_section("Install Init Script"),
- "see log/mail_room.log for possible errors"
- )
- fix_and_rerun
- end
- end
-
- def check_imap_authentication
- print "IMAP server credentials are correct? ... "
-
- config_path = Rails.root.join('config', 'mail_room.yml').to_s
- erb = ERB.new(File.read(config_path))
- erb.filename = config_path
- config_file = YAML.load(erb.result)
-
- config = config_file[:mailboxes].first
-
- if config
- begin
- imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
- imap.starttls if config[:start_tls]
- imap.login(config[:email], config[:password])
- connected = true
- rescue
- connected = false
- end
- end
-
- if connected
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- "Check that the information in config/gitlab.yml is correct"
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
+ puts 'Reply by email is disabled in config/gitlab.yml'
end
end
-
- def mail_room_running?
- ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww))
- ps_ux.include?("mail_room")
- end
end
namespace :ldap do
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index d8ab1f253e8..fcd4aa29834 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 10:02-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
@@ -57,9 +57,18 @@ msgstr "Ðабор от графики отноÑно непрекъÑнатат
msgid "About auto deploy"
msgstr "ОтноÑно автоматичното внедрÑване"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивно"
@@ -84,6 +93,12 @@ msgstr "ДобавÑне на нова папка"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Ðрхивиран проект! Хранилището е Ñамо за четене"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Прикачете файл чрез влачене и пуÑкане или %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Клон"
@@ -137,6 +209,9 @@ msgstr "Разглеждане на файловете"
msgid "ByAuthor|by"
msgstr "от"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð½Ð° непрекъÑната интеграциÑ"
@@ -164,6 +239,9 @@ msgstr "СпиÑък Ñ Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ð¸"
msgid "Charts"
msgstr "Графики"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Подбиране на това подаване"
@@ -259,12 +337,18 @@ msgstr "Подадено от"
msgid "Compare"
msgstr "Сравнение"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "РъководÑтво за ÑътрудничеÑтво"
msgid "Contributors"
msgstr "Сътрудници"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Копиране на адреÑа в буфера за обмен"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "ВнедрÑване"
msgstr[1] "ВнедрÑваниÑ"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "ОпиÑание"
@@ -399,6 +486,9 @@ msgstr "Редактиране"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Редактиране на плана %{id} за Ñхема"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "От Ñъздаването на проблема до внедрÑваÐ
msgid "From merge request merge until deploy to production"
msgstr "От прилагането на заÑвката за Ñливане до внедрÑването в крайната верÑиÑ"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Към Вашето разклонение"
msgid "GoToYourFork|Fork"
msgstr "Разклонение"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr "Ðачало"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ОÑвежаването започна уÑпешно"
@@ -515,14 +617,8 @@ msgstr "ПредÑтавÑме Ви анализа на циклите"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Задачи за поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец"
-
-msgid "Jobs for last week"
-msgstr "Задачи за поÑледната Ñедмица"
-
-msgid "Jobs for last year"
-msgstr "Задачи за поÑледната година"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Изключено"
@@ -530,6 +626,9 @@ msgstr "Изключено"
msgid "LFSStatus|Enabled"
msgstr "Включено"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "ПоÑÐ»ÐµÐ´Ð½Ð¸Ñ %d ден"
@@ -562,20 +661,38 @@ msgstr "ÐапуÑкане на групата"
msgid "Leave project"
msgstr "ÐапуÑкане на проекта"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Ограничено до показване на най-много %d Ñъбитие"
msgstr[1] "Ограничено до показване на най-много %d ÑъбитиÑ"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Медиана"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "добавите SSH ключ"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "УчаÑтие"
msgid "NotificationLevel|Watch"
msgstr "Ðаблюдение"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Филтър"
@@ -686,9 +806,15 @@ msgstr "Отворен"
msgid "Options"
msgstr "Опции"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "СобÑтвеник"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "Схема"
@@ -701,6 +827,9 @@ msgstr "План за Ñхема"
msgid "Pipeline Schedules"
msgstr "Планове за Ñхема"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "ÐеуÑпешни:"
@@ -764,6 +893,15 @@ msgstr "Схеми"
msgid "Pipelines charts"
msgstr "Графики за Ñхемите"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "вÑички"
@@ -776,6 +914,12 @@ msgstr "Ñ ÐµÑ‚Ð°Ð¿"
msgid "Pipeline|with stages"
msgstr "Ñ ÐµÑ‚Ð°Ð¿Ð¸"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "ИзнаÑÑнето на проекта започна. Ще получ
msgid "Project home"
msgstr "Ðачална Ñтраница на проекта"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Етап"
msgid "ProjectNetworkGraph|Graph"
msgstr "Графика"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "ОтмÑна на това подаване"
msgid "Revert this merge request"
msgstr "ОтмÑна на тази заÑвка за Ñливане"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Запазване на плана за Ñхема"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Изберете целеви клон"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Задайте парола на профила Ñи, за да можете да изтеглÑте и изпращате промени чрез %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "ÐаÑтройка на авт. внедрÑване"
msgid "SetPasswordToCloneLink|set a password"
msgstr "зададете парола"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Показване на %d Ñъбитие"
msgstr[1] "Показване на %d ÑъбитиÑ"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Изходен код"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "ИÑкате ли да видите данните? Помолете аÐ
msgid "We don't have enough data to show this stage."
msgstr "ÐÑма доÑтатъчно данни за този етап."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "ОттеглÑне на заÑвката за доÑтъп"
@@ -1284,4 +1452,5 @@ msgstr "извеÑÑ‚Ð¸Ñ Ð¿Ð¾ е-поща"
msgid "parent"
msgid_plural "parents"
msgstr[0] "родител"
-msgstr[1] "родители" \ No newline at end of file
+msgstr[1] "родители"
+
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 3cefb26d234..86deb620f0b 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:29-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -23,20 +23,20 @@ msgstr[1] ""
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%s zusätzlicher Commit wurde ausgelassen um Leistungsprobleme zu verhindern."
+msgstr[1] "%s zusätzliche Commits wurden ausgelassen um Leistungsprobleme zu verhindern."
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
+msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird den Zugriff beim nächsten Versuch zulassen."
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
+msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird den Zugriff für %{number_of_seconds} Sekunden blockieren."
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
-msgstr ""
+msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird es nicht weiter versuchen. Setze die Speicherinformation nach Behebung des Problems zurück."
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
@@ -44,7 +44,7 @@ msgstr[0] ""
msgstr[1] ""
msgid "(checkout the %{link} for information on how to install it)."
-msgstr ""
+msgstr "(beachte die Informationen zur Installation auf %{link})."
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -52,12 +52,21 @@ msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
-msgstr ""
+msgstr "Eine Sammlung von Graphen bezüglich kontinuierlicher Integration"
msgid "About auto deploy"
+msgstr "Ãœber automatische Bereitstellung "
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr "Zugriff auf fehlerhafte Speicher wurde vorübergehend deaktiviert, um die Wiederherstellung zu ermöglichen. Für den zukünftigen Zugriff, behebe bitte das Problem und setze danach die Speicherinformationen zurück."
+
+msgid "Account"
msgstr ""
msgid "Active"
@@ -67,42 +76,105 @@ msgid "Activity"
msgstr ""
msgid "Add Changelog"
-msgstr ""
+msgstr "Änderungsliste hinzufügen "
msgid "Add Contribution guide"
-msgstr ""
+msgstr "Mitarbeitsanleitung hinzufügen"
msgid "Add License"
msgstr ""
msgid "Add an SSH key to your profile to pull or push via SSH."
-msgstr ""
+msgstr "Füge einen SSH Schlüssel zu deinem Profil hinzu, um mittels SSH zu übertragen (push) oder abzurufen (pull)."
msgid "Add new directory"
-msgstr ""
+msgstr "Erstelle eine neues Verzeichnis"
msgid "All"
+msgstr "Alle"
+
+msgid "Appearances"
msgstr ""
-msgid "Archived project! Repository is read-only"
+msgid "Applications"
msgstr ""
+msgid "Archived project! Repository is read-only"
+msgstr "Archiviertes Projekt! Repository ist nicht änderbar."
+
msgid "Are you sure you want to delete this pipeline schedule?"
-msgstr ""
+msgstr "Bist Du sicher, dass Du diesen Pipeline-Zeitplan löschen möchtest?"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "Bist Du sicher, dass Du alle Änderungen zurücksetzen willst?"
msgid "Are you sure you want to reset registration token?"
-msgstr ""
+msgstr "Bist Du sicher, dass Du den Registrierungstoken zurücksetzen willst?"
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "Bist Du sicher, dass Du den Systemüberwachungstoken zurücksetzen willst?"
msgid "Are you sure?"
-msgstr ""
+msgstr "Bist Du sicher?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Datei mittels Drag &amp; Drop oder %{upload_link} hinzufügen"
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
msgstr ""
msgid "Branch"
@@ -111,31 +183,34 @@ msgstr[0] ""
msgstr[1] ""
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
-msgstr ""
+msgstr "Branch <strong>%{branch_name}</strong> wurde erstellt. Um die automatische Bereitstellung einzurichten, wähle eine GitLab CI Yaml Vorlage und committe Deine Änderungen. %{link_to_autodeploy_doc}"
msgid "BranchSwitcherPlaceholder|Search branches"
-msgstr ""
+msgstr "Branches durchsuchen"
msgid "BranchSwitcherTitle|Switch branch"
-msgstr ""
+msgstr "Branch wechseln"
msgid "Branches"
msgstr ""
msgid "Browse Directory"
-msgstr ""
+msgstr "Verzeichnisse durchsuchen"
msgid "Browse File"
-msgstr ""
+msgstr "Datei durchsuchen"
msgid "Browse Files"
-msgstr ""
+msgstr "Dateien durchsuchen"
msgid "Browse files"
-msgstr ""
+msgstr "Dateien durchsuchen"
msgid "ByAuthor|by"
-msgstr "Von"
+msgstr "von"
+
+msgid "CI / CD"
+msgstr ""
msgid "CI configuration"
msgstr ""
@@ -144,88 +219,91 @@ msgid "Cancel"
msgstr ""
msgid "Cancel edit"
-msgstr ""
+msgstr "Bearbeitung abbrechen"
msgid "ChangeTypeActionLabel|Pick into branch"
-msgstr ""
+msgstr "In dem Branch wählen"
msgid "ChangeTypeActionLabel|Revert in branch"
-msgstr ""
+msgstr "Im Branch wiederherstellen"
msgid "ChangeTypeAction|Cherry-pick"
-msgstr ""
+msgstr "Herauspicken"
msgid "ChangeTypeAction|Revert"
-msgstr ""
+msgstr "Wiederherstellen "
msgid "Changelog"
-msgstr ""
+msgstr "Änderungsliste "
msgid "Charts"
+msgstr "Diagramme"
+
+msgid "Chat"
msgstr ""
msgid "Cherry-pick this commit"
-msgstr ""
+msgstr "Diesen Commit herauspicken "
msgid "Cherry-pick this merge request"
-msgstr ""
+msgstr "Diesen Merge Request herauspicken"
msgid "CiStatusLabel|canceled"
-msgstr ""
+msgstr "abgebrochen"
msgid "CiStatusLabel|created"
-msgstr ""
+msgstr "erstellt"
msgid "CiStatusLabel|failed"
-msgstr ""
+msgstr "fehlgeschlagen"
msgid "CiStatusLabel|manual action"
-msgstr ""
+msgstr "manuelles Eingreifen"
msgid "CiStatusLabel|passed"
-msgstr ""
+msgstr "absolviert"
msgid "CiStatusLabel|passed with warnings"
-msgstr ""
+msgstr "mit Warnungen absolviert"
msgid "CiStatusLabel|pending"
-msgstr ""
+msgstr "ausstehend"
msgid "CiStatusLabel|skipped"
-msgstr ""
+msgstr "übersprungen"
msgid "CiStatusLabel|waiting for manual action"
-msgstr ""
+msgstr "wartet auf manuelles Eingreifen"
msgid "CiStatusText|blocked"
-msgstr ""
+msgstr "blockiert"
msgid "CiStatusText|canceled"
-msgstr ""
+msgstr "abgebrochen"
msgid "CiStatusText|created"
-msgstr ""
+msgstr "erstellt"
msgid "CiStatusText|failed"
-msgstr ""
+msgstr "fehlgeschlagen"
msgid "CiStatusText|manual"
-msgstr ""
+msgstr "manuell"
msgid "CiStatusText|passed"
-msgstr ""
+msgstr "absolviert"
msgid "CiStatusText|pending"
-msgstr ""
+msgstr "ausstehend"
msgid "CiStatusText|skipped"
-msgstr ""
+msgstr "übersprungen"
msgid "CiStatus|running"
-msgstr ""
+msgstr "laufend"
msgid "Comments"
-msgstr ""
+msgstr "Kommentare"
msgid "Commit"
msgid_plural "Commits"
@@ -233,106 +311,112 @@ msgstr[0] ""
msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
-msgstr ""
+msgstr "Dauer der Commits in Minuten für die letzten 30 Commits"
msgid "Commit message"
-msgstr ""
+msgstr "Commit Nachricht"
msgid "CommitBoxTitle|Commit"
-msgstr ""
+msgstr "Commit"
msgid "CommitMessage|Add %{file_name}"
-msgstr ""
+msgstr "%{file_name} hinzufügen"
msgid "Commits"
msgstr ""
msgid "Commits feed"
-msgstr ""
+msgstr "Liste der Commits"
msgid "Commits|History"
-msgstr ""
+msgstr "Verlauf"
msgid "Committed by"
-msgstr ""
+msgstr "Committed von"
msgid "Compare"
+msgstr "Vergleichen"
+
+msgid "Container Registry"
msgstr ""
msgid "Contribution guide"
-msgstr ""
+msgstr "Mitarbeitsanleitung"
msgid "Contributors"
+msgstr "Mitarbeiter"
+
+msgid "Copy SSH public key to clipboard"
msgstr ""
msgid "Copy URL to clipboard"
-msgstr ""
+msgstr "Kopiere URL in die Zwischenablage"
msgid "Copy commit SHA to clipboard"
-msgstr ""
+msgstr "Kopiere Commit SHA in die Zwischenablage"
msgid "Create New Directory"
-msgstr ""
+msgstr "Erstelle neues Verzeichnis"
msgid "Create a new branch"
-msgstr ""
+msgstr "Erstelle einen neuen Branch"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
-msgstr ""
+msgstr "Erstelle einen persönlichen Zugriffstoken in Deinem Konto um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)."
msgid "Create directory"
-msgstr ""
+msgstr "Erstelle Verzeichnis"
msgid "Create empty bare repository"
-msgstr ""
+msgstr "Erstelle leeres Repository"
msgid "Create merge request"
-msgstr ""
+msgstr "Erstelle Merge Request"
msgid "Create new..."
-msgstr ""
+msgstr "Erstelle neues..."
msgid "CreateNewFork|Fork"
-msgstr ""
+msgstr "Ableger"
msgid "CreateTag|Tag"
-msgstr ""
+msgstr "Tag "
msgid "CreateTokenToCloneLink|create a personal access token"
-msgstr ""
+msgstr "Erstelle einen persönlichen Zugriffstoken"
msgid "Cron Timezone"
-msgstr ""
+msgstr "Cron Zeitzone"
msgid "Cron syntax"
-msgstr ""
+msgstr "Cron Syntax"
msgid "Custom notification events"
-msgstr ""
+msgstr "Individuelle Benachrichtigungsereignisse"
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
-msgstr ""
+msgstr "Individuelle Benachrichtigungsstufen sind identisch mit den Beteiligungsstufen. Mit individuellen Benachrichtigungsstufen erhältst Du ebenfalls Mitteilungen für ausgewählte Ereignisse. Für weitere Informationen lies %{notification_link}. "
msgid "Cycle Analytics"
-msgstr ""
+msgstr "Arbeitsablaufsanalysen"
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "Cycle Analytics liefern einen Überblick darüber, wie viel Zeit in Ihrem Projekt von einer Idee bis zum Produktivdeployment vergeht."
+msgstr "Arbeitsablaufsanalysen verschaffen einen Überblick, welche Zeit Dein Projekt von der Idee zur Realisierung benötigt."
msgid "CycleAnalyticsStage|Code"
-msgstr "Code"
+msgstr "Entwicklung"
msgid "CycleAnalyticsStage|Issue"
-msgstr "Issue"
+msgstr "Ticket"
msgid "CycleAnalyticsStage|Plan"
msgstr "Planung"
msgid "CycleAnalyticsStage|Production"
-msgstr "Produktiv"
+msgstr "Produktion"
msgid "CycleAnalyticsStage|Review"
-msgstr "Review"
+msgstr "Überprüfung"
msgid "CycleAnalyticsStage|Staging"
msgstr "Staging"
@@ -341,281 +425,314 @@ msgid "CycleAnalyticsStage|Test"
msgstr "Test"
msgid "Define a custom pattern with cron syntax"
-msgstr ""
+msgstr "Erstelle ein individuelles Muster mittels Cron Syntax"
msgid "Delete"
-msgstr ""
+msgstr "Löschen"
msgid "Deploy"
msgid_plural "Deploys"
-msgstr[0] "Deployment"
-msgstr[1] "Deployments"
+msgstr[0] "Bereitstellung"
+msgstr[1] "Bereitstellungen"
-msgid "Description"
+msgid "Deploy Keys"
msgstr ""
+msgid "Description"
+msgstr "Beschreibung"
+
msgid "Details"
msgstr ""
msgid "Directory name"
-msgstr ""
+msgstr "Verzeichnisname"
msgid "Discard changes"
-msgstr ""
+msgstr "Änderungen verwerfen"
msgid "Don't show again"
-msgstr ""
+msgstr "Nicht erneut anzeigen"
msgid "Download"
-msgstr ""
+msgstr "Herunterladen"
msgid "Download tar"
-msgstr ""
+msgstr "TAR-Datei herunterladen"
msgid "Download tar.bz2"
-msgstr ""
+msgstr "TAR.BZ2-Datei herunterladen"
msgid "Download tar.gz"
-msgstr ""
+msgstr "TAR.GZ-Datei herunterladen"
msgid "Download zip"
-msgstr ""
+msgstr "ZIP-Datei herunterladen"
msgid "DownloadArtifacts|Download"
-msgstr ""
+msgstr "Herunterladen"
msgid "DownloadCommit|Email Patches"
-msgstr ""
+msgstr "E-Mail Patch"
msgid "DownloadCommit|Plain Diff"
-msgstr ""
+msgstr "Unterschiede"
msgid "DownloadSource|Download"
-msgstr ""
+msgstr "Herunterladen"
msgid "Edit"
-msgstr ""
+msgstr "Bearbeiten"
msgid "Edit Pipeline Schedule %{id}"
+msgstr "Pipeline Zeitplan bearbeiten %{id}"
+
+msgid "Emails"
msgstr ""
msgid "EventFilterBy|Filter by all"
-msgstr ""
+msgstr "Filtere alle"
msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "Filtere nach Kommentaren"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "Filtere nach Tickets"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "Filtere nach Merge Requests"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "Filtere nach Ãœbertragungen"
msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "Filtere nach Teams"
msgid "Every day (at 4:00am)"
-msgstr ""
+msgstr "Täglich (um 4:00 Uhr)"
msgid "Every month (on the 1st at 4:00am)"
-msgstr ""
+msgstr "Monatlich (am Ersten um 4:00 Uhr)"
msgid "Every week (Sundays at 4:00am)"
-msgstr ""
+msgstr "Wöchentlich (Sonntags um 4:00 Uhr)"
msgid "Failed to change the owner"
-msgstr ""
+msgstr "Wechsel des Besitzers fehlgeschlagen"
msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "Entfernung der Pipelineplanung fehlgeschlagen"
msgid "Files"
-msgstr ""
+msgstr "Dateien"
msgid "Filter by commit message"
-msgstr ""
+msgstr "Filter nach Commit Nachricht"
msgid "Find by path"
-msgstr ""
+msgstr "Finde über den Pfad"
msgid "Find file"
-msgstr ""
+msgstr "Finde Datei"
msgid "FirstPushedBy|First"
msgstr "Erster"
msgid "FirstPushedBy|pushed by"
-msgstr "gepusht von"
+msgstr "übertragen von"
msgid "Fork"
msgid_plural "Forks"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ableger"
+msgstr[1] "Ableger"
msgid "ForkedFromProjectPath|Forked from"
-msgstr ""
+msgstr "Ableger von"
msgid "From issue creation until deploy to production"
-msgstr "Vom Anlegen des Issues bis zum Produktivdeployment"
+msgstr "Von der Ticketbeschreibung bis zur Bereitstellung"
msgid "From merge request merge until deploy to production"
-msgstr "Vom Merge Request bis zum Produktivdeployment"
+msgstr "Vom Umsetzen des Merge Request bis zur Bereitstellung auf dem Produktivsystem"
-msgid "Git storage health information has been reset"
+msgid "GPG Keys"
msgstr ""
-msgid "GitLab Runner section"
+msgid "Geo Nodes"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr "Informationen über den Speicherzustand von Gitlab wurden zurückgesetzt."
+
+msgid "GitLab Runner section"
+msgstr "GitLab Runner Bereich"
+
msgid "Go to your fork"
-msgstr ""
+msgstr "Gehe zu Deinem Ableger"
msgid "GoToYourFork|Fork"
+msgstr "Ableger"
+
+msgid "Group overview"
msgstr ""
msgid "Health Check"
-msgstr ""
+msgstr "Systemzustand"
msgid "Health information can be retrieved from the following endpoints. More information is available"
-msgstr ""
+msgstr "Informationen über den Systemzustand können von folgenden Endpunkten erhalten werden. Mehr Informationen gibt es"
msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "Zugriffstoken ist"
msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "OK"
msgid "HealthCheck|No Health Problems Detected"
-msgstr ""
+msgstr "Keine Probleme erkannt"
msgid "HealthCheck|Unhealthy"
-msgstr ""
+msgstr "Problematisch"
msgid "Home"
+msgstr "Startseite"
+
+msgid "Hooks"
msgstr ""
msgid "Housekeeping successfully started"
-msgstr ""
+msgstr "Aufräumen erfolgreich gestartet"
msgid "Import repository"
-msgstr ""
+msgstr "Repository importieren"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "Installiere einen Runner der mit GitLab CI kompatibel ist"
msgid "Interval Pattern"
-msgstr ""
+msgstr "Intervallmuster"
msgid "Introducing Cycle Analytics"
-msgstr "Was sind Cycle Analytics?"
+msgstr "Arbeitsablaufsanalysen vorgestellt"
msgid "Issue events"
-msgstr ""
-
-msgid "Jobs for last month"
-msgstr ""
+msgstr "Ticketereignisse"
-msgid "Jobs for last week"
-msgstr ""
-
-msgid "Jobs for last year"
+msgid "Issues"
msgstr ""
msgid "LFSStatus|Disabled"
-msgstr ""
+msgstr "Deaktiviert"
msgid "LFSStatus|Enabled"
+msgstr "Aktiviert"
+
+msgid "Labels"
msgstr ""
msgid "Last %d day"
msgid_plural "Last %d days"
-msgstr[0] "Letzter %d Tag"
+msgstr[0] "Letzten %d Tag"
msgstr[1] "Letzten %d Tage"
msgid "Last Pipeline"
-msgstr ""
+msgstr "Letzte Pipeline"
msgid "Last Update"
-msgstr ""
+msgstr "Letzte Aktualisierung"
msgid "Last commit"
-msgstr ""
+msgstr "Letzter Commit"
msgid "LastPushEvent|You pushed to"
-msgstr ""
+msgstr "Du übertrugst an"
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "am"
msgid "Learn more in the"
-msgstr ""
+msgstr "Erfahre mehr in den"
msgid "Learn more in the|pipeline schedules documentation"
-msgstr ""
+msgstr "Pipelineplanungsdokumentation"
msgid "Leave group"
-msgstr ""
+msgstr "Verlasse die Gruppe"
msgid "Leave project"
+msgstr "Verlasse das Projekt"
+
+msgid "License"
msgstr ""
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
-msgstr[0] "Eingeschränkt auf maximal %d Ereignis"
-msgstr[1] "Eingeschränkt auf maximal %d Ereignisse"
+msgstr[0] "Limitiere die Anzeige auf höchstens %d Ereignis"
+msgstr[1] "Limitiere die Anzeige auf höchstens %d Ereignisse"
+
+msgid "Locked Files"
+msgstr ""
msgid "Median"
msgstr ""
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
+msgstr "Ereignisse zusammenführen"
+
+msgid "Messages"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "einen SSH Schlüssel hinzufügst"
+
+msgid "Monitoring"
msgstr ""
msgid "More information is available|here"
-msgstr ""
+msgstr "hier"
msgid "New Issue"
msgid_plural "New Issues"
-msgstr[0] "Neues Issue"
-msgstr[1] "Neue Issues"
+msgstr[0] "Neues Ticket"
+msgstr[1] "Neue Tickets"
msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "Neuer Pipeline Zeitplan"
msgid "New branch"
-msgstr ""
+msgstr "Neuer Branch"
msgid "New directory"
-msgstr ""
+msgstr "Neues Verzeichnis"
msgid "New file"
-msgstr ""
+msgstr "Neue Datei"
msgid "New issue"
-msgstr ""
+msgstr "Neues Ticket"
msgid "New merge request"
-msgstr ""
+msgstr "Neuer Merge Request"
msgid "New schedule"
-msgstr ""
+msgstr "Neuer Zeitplan"
msgid "New snippet"
-msgstr ""
+msgstr "Neuer Schnipsel"
msgid "New tag"
-msgstr ""
+msgstr "Neuer Tag"
msgid "No repository"
-msgstr ""
+msgstr "Kein Repository"
msgid "No schedules"
-msgstr ""
+msgstr "Keine Zeitpläne"
msgid "Not available"
msgstr "Nicht verfügbar"
@@ -624,241 +741,274 @@ msgid "Not enough data"
msgstr "Nicht genügend Daten"
msgid "Notification events"
-msgstr ""
+msgstr "Benachrichtigungsereignisse"
msgid "NotificationEvent|Close issue"
-msgstr ""
+msgstr "Ticket abschließen"
msgid "NotificationEvent|Close merge request"
-msgstr ""
+msgstr "Merge Request abschließen"
msgid "NotificationEvent|Failed pipeline"
-msgstr ""
+msgstr "Fehlgeschlagene Pipeline"
msgid "NotificationEvent|Merge merge request"
-msgstr ""
+msgstr "Merge Request umsetzen"
msgid "NotificationEvent|New issue"
-msgstr ""
+msgstr "Neues Ticket"
msgid "NotificationEvent|New merge request"
-msgstr ""
+msgstr "Neuer Merge Request"
msgid "NotificationEvent|New note"
-msgstr ""
+msgstr "Neue Notiz"
msgid "NotificationEvent|Reassign issue"
-msgstr ""
+msgstr "Ticket neu zuweisen"
msgid "NotificationEvent|Reassign merge request"
-msgstr ""
+msgstr "Merge Request neu zuweisen"
msgid "NotificationEvent|Reopen issue"
-msgstr ""
+msgstr "Ticket wieder öffnen"
msgid "NotificationEvent|Successful pipeline"
-msgstr ""
+msgstr "Erfolgreiche Pipeline"
msgid "NotificationLevel|Custom"
-msgstr ""
+msgstr "Individuell"
msgid "NotificationLevel|Disabled"
-msgstr ""
+msgstr "Deaktiviert"
msgid "NotificationLevel|Global"
-msgstr ""
+msgstr "Global"
msgid "NotificationLevel|On mention"
-msgstr ""
+msgstr "Zum Vermerk"
msgid "NotificationLevel|Participate"
-msgstr ""
+msgstr "Teilnehmen"
msgid "NotificationLevel|Watch"
+msgstr "Beobachten"
+
+msgid "Notifications"
msgstr ""
msgid "OfSearchInADropdown|Filter"
-msgstr ""
+msgstr "Filter"
msgid "OpenedNDaysAgo|Opened"
-msgstr "Erstellt"
+msgstr "Ungelöst"
msgid "Options"
+msgstr "Optionen"
+
+msgid "Overview"
msgstr ""
msgid "Owner"
+msgstr "Besitzer"
+
+msgid "Password"
msgstr ""
msgid "Pipeline"
msgstr ""
msgid "Pipeline Health"
-msgstr "Pipeline Kennzahlen"
+msgstr "Zustand der Pipeline"
msgid "Pipeline Schedule"
-msgstr ""
+msgstr "Zeitplan der Pipeline"
msgid "Pipeline Schedules"
+msgstr "Zustände der Pipeline"
+
+msgid "Pipeline quota"
msgstr ""
msgid "PipelineCharts|Failed:"
-msgstr ""
+msgstr "Fehlgeschlagen:"
msgid "PipelineCharts|Overall statistics"
-msgstr ""
+msgstr "Gesamte Statisktiken"
msgid "PipelineCharts|Success ratio:"
-msgstr ""
+msgstr "Erfolgsverhältnis:"
msgid "PipelineCharts|Successful:"
-msgstr ""
+msgstr "Erfolgreich:"
msgid "PipelineCharts|Total:"
-msgstr ""
+msgstr "Insgesamt:"
msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "Aktiviert"
msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "Aktiv"
msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "Alle"
msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "Inaktiv"
msgid "PipelineSchedules|Input variable key"
-msgstr ""
+msgstr "Schlüssel der Eingangsvariable"
msgid "PipelineSchedules|Input variable value"
-msgstr ""
+msgstr "Wert der Eingangsvariable"
msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "Nächste Durchführung"
msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "Nichts"
msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "Beschreibe diese Pipeline"
msgid "PipelineSchedules|Remove variable row"
-msgstr ""
+msgstr "Entferne Variablenreihe"
msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "Eigentümer werden"
msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "Ziel"
msgid "PipelineSchedules|Variables"
-msgstr ""
+msgstr "Variablen"
msgid "PipelineSheduleIntervalPattern|Custom"
-msgstr ""
+msgstr "Individuell"
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
+msgstr "Pipelinediagramme"
+
+msgid "Pipelines for last month"
msgstr ""
-msgid "Pipeline|all"
+msgid "Pipelines for last week"
msgstr ""
-msgid "Pipeline|success"
+msgid "Pipelines for last year"
msgstr ""
+msgid "Pipeline|all"
+msgstr "Alle"
+
+msgid "Pipeline|success"
+msgstr "Erfolg"
+
msgid "Pipeline|with stage"
-msgstr ""
+msgstr "mit Stage"
msgid "Pipeline|with stages"
+msgstr "mit Stages"
+
+msgid "Preferences"
msgstr ""
-msgid "Project"
+msgid "Profile Settings"
msgstr ""
+msgid "Project"
+msgstr "Projekt"
+
msgid "Project '%{project_name}' queued for deletion."
-msgstr ""
+msgstr "Das Projekt '%{project_name}' wurde zur Löschung eingeplant."
msgid "Project '%{project_name}' was successfully created."
-msgstr ""
+msgstr "Das Projekt '%{project_name}' wurde erfolgreich erstellt."
msgid "Project '%{project_name}' was successfully updated."
-msgstr ""
+msgstr "Das Projekt '%{project_name}' wurde erfolgreich aktualisiert."
msgid "Project '%{project_name}' will be deleted."
-msgstr ""
+msgstr "Das Projekt '%{project_name}' wird gelöscht."
msgid "Project access must be granted explicitly to each user."
-msgstr ""
+msgstr "Jedem Nutzer muss explizit der Zugriff auf das Projekt gewährt werden."
msgid "Project details"
-msgstr ""
+msgstr "Projektdetails"
msgid "Project export could not be deleted."
-msgstr ""
+msgstr "Der Export des Projekts konnte nich gelöscht werden."
msgid "Project export has been deleted."
-msgstr ""
+msgstr "Der Export des Projekts wurde gelöscht."
msgid "Project export link has expired. Please generate a new export from your project settings."
-msgstr ""
+msgstr "Der Link für den Export des Projektes ist abgelaufen. Bitte generiere einen neuen Export in den Projekteinstellungen."
msgid "Project export started. A download link will be sent by email."
-msgstr ""
+msgstr "Export des Projektes gestartet. Ein Link zum herunterladen wir Dir per E-Mail zugesandt."
msgid "Project home"
+msgstr "Startseite des Projektes"
+
+msgid "Project overview"
msgstr ""
msgid "ProjectActivityRSS|Subscribe"
-msgstr ""
+msgstr "Abonnieren"
msgid "ProjectFeature|Disabled"
-msgstr ""
+msgstr "Dekativiert"
msgid "ProjectFeature|Everyone with access"
-msgstr ""
+msgstr "Jeder mit Zugriff"
msgid "ProjectFeature|Only team members"
-msgstr ""
+msgstr "Nur Teammitglieder"
msgid "ProjectFileTree|Name"
-msgstr ""
+msgstr "Name"
msgid "ProjectLastActivity|Never"
-msgstr ""
+msgstr "Niemals"
msgid "ProjectLifecycle|Stage"
-msgstr "Phase"
+msgstr "Stage"
msgid "ProjectNetworkGraph|Graph"
+msgstr "Diagramm"
+
+msgid "Push Rules"
msgstr ""
msgid "Push events"
-msgstr ""
+msgstr "Ãœbertragungsereignisse"
msgid "Read more"
-msgstr "Mehr"
+msgstr "Mehr lesen"
msgid "Readme"
-msgstr ""
+msgstr "Lies mich"
msgid "RefSwitcher|Branches"
-msgstr ""
+msgstr "Branches"
msgid "RefSwitcher|Tags"
-msgstr ""
+msgstr "Tags"
msgid "Related Commits"
msgstr "Zugehörige Commits"
msgid "Related Deployed Jobs"
-msgstr "Zugehörige Deploymentjobs"
+msgstr "Zugehörige ausgelieferte Jobs"
msgid "Related Issues"
-msgstr "Zugehörige Issues"
+msgstr "Zugehörige Tickets"
msgid "Related Jobs"
msgstr "Zugehörige Jobs"
@@ -867,72 +1017,81 @@ msgid "Related Merge Requests"
msgstr "Zugehörige Merge Requests"
msgid "Related Merged Requests"
-msgstr "Zugehörige abgeschlossene Merge Requests"
+msgstr "Zugehörige umgesetzte Merge Requests"
msgid "Remind later"
-msgstr ""
+msgstr "Später erinnern"
msgid "Remove project"
-msgstr ""
+msgstr "Projekt entfernen"
msgid "Repository"
msgstr ""
msgid "Request Access"
-msgstr ""
+msgstr "Anfrage auf Zugriff"
msgid "Reset git storage health information"
-msgstr ""
+msgstr "Informationen über Speicherzustand zurücksetzen"
msgid "Reset health check access token"
-msgstr ""
+msgstr "Zugriffstoken für Systemzustand zurücksetzen"
msgid "Reset runners registration token"
-msgstr ""
+msgstr "Registrierungstoken für Runner zurücksetzen"
msgid "Revert this commit"
-msgstr ""
+msgstr "Commit zurücksetzen"
msgid "Revert this merge request"
+msgstr "Merge Request zurücksetzen"
+
+msgid "SSH Keys"
msgstr ""
msgid "Save pipeline schedule"
-msgstr ""
+msgstr "Zeitplan der Pipeline speichern"
msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "Plane eine neue Pipeline"
msgid "Scheduling Pipelines"
-msgstr ""
+msgstr "Pipelines planen"
msgid "Search branches and tags"
-msgstr ""
+msgstr "Suche nach Branches und Tags"
msgid "Select Archive Format"
-msgstr ""
+msgstr "Archivierungsformat auswählen"
msgid "Select a timezone"
-msgstr ""
+msgstr "Zeitzone auswählen"
msgid "Select existing branch"
-msgstr ""
+msgstr "Existierenden Branch auswählen"
msgid "Select target branch"
+msgstr "Zielbranch auswählen"
+
+msgid "Service Templates"
msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}."
-msgstr ""
+msgstr "Lege ein Passwort für dein Konto fest, um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)."
msgid "Set up CI"
-msgstr ""
+msgstr "CI einrichten"
msgid "Set up Koding"
-msgstr ""
+msgstr "Koding einrichten"
msgid "Set up auto deploy"
-msgstr ""
+msgstr "Automatische Bereitstellung einrichten"
msgid "SetPasswordToCloneLink|set a password"
+msgstr "ein Passwort festlegst"
+
+msgid "Settings"
msgstr ""
msgid "Showing %d event"
@@ -940,23 +1099,29 @@ msgid_plural "Showing %d events"
msgstr[0] "Zeige %d Ereignis"
msgstr[1] "Zeige %d Ereignisse"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
+msgstr "Quellcode"
+
+msgid "Spam Logs"
msgstr ""
msgid "Specify the following URL during the Runner setup:"
-msgstr ""
+msgstr "Lege die folgende URL während des Runner Setups fest:"
msgid "StarProject|Star"
-msgstr ""
+msgstr "Favorisieren"
msgid "Start a %{new_merge_request} with these changes"
-msgstr ""
+msgstr "Beginne einen %{new_merge_request} mit diesen Änderungen"
msgid "Start the Runner!"
-msgstr ""
+msgstr "Starte den Runner!"
msgid "Switch branch/tag"
-msgstr ""
+msgstr "Zu Branch/Tag wechseln"
msgid "Tag"
msgid_plural "Tags"
@@ -967,308 +1132,311 @@ msgid "Tags"
msgstr ""
msgid "Target Branch"
-msgstr ""
+msgstr "Zielbranch"
msgid "Team"
msgstr ""
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "Die Code-Phase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Sie Ihren ersten Merge Request anlegen, werden dessen Daten automatisch ergänzt."
+msgstr "Die Entwicklungsphase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Du Deinen ersten Merge Request anlegst, werden dessen Daten automatisch ergänzt."
msgid "The collection of events added to the data gathered for that stage."
msgstr "Ereignisse, die für diese Phase ausgewertet wurden."
msgid "The fork relationship has been removed."
-msgstr ""
+msgstr "Die Beziehung des Ablegers wurde entfernt."
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "Die Issue-Phase stellt die Zeit vom Anlegen eines Issues bis zum Zuweisen eines Meilensteins oder Hinzufügen zum Issue Board dar. Erstellen Sie einen Issue, damit dessen Daten hier erscheinen."
+msgstr "Die Ticketphase stellt die Zeit vom Anlegen eines Tickets bis zum Zuweisen eines Meilensteins oder Hinzufügen zur Aufgabentafel dar. Erstelle einen Ticket, damit dessen Daten hier erscheinen."
msgid "The phase of the development lifecycle."
-msgstr "Die Phase im Entwicklungsprozess."
+msgstr "Die Phase des Entwicklungslebenszyklus."
msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
-msgstr ""
+msgstr "Die Pipelinezeitpläne starten Pipelines in der Zukunft, wiederholend, für bestimmte Branches oder Tags. Diese geplanten Pipelines haben denselben begrenzten Zugriff auf das Projekt, wie der zugeordnete Nutzer."
msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Pushen des ersten Commits dar. Sobald Sie den ersten Commit pushen, werden dessen Daten hier erscheinen."
+msgstr "Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Übertragen des ersten Commits dar. Sobald Du den ersten Commit überträgst, werden dessen Daten hier erscheinen."
msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "Die Produktiv-Phase stellt die Gesamtzeit vom Anlegen eines Issues bis zum Deployment auf dem Produktivsystem dar. Sobald Sie den vollständigen Entwicklungszyklus von einer Idee bis zum Produktivdeployment durchlaufen haben, erscheinen die zugehörigen Daten hier."
+msgstr "Die Produktionsphase stellt die Gesamtzeit vom Anlegen eines Tickets bis zur Bereitstellung des Codes auf dem Produktivsystem dar. Sobald Du den vollständigen Entwicklungszyklus, von einer Idee bis zur Fertigstellung, durchlaufen hast, erscheinen die zugehörigen Daten hier."
msgid "The project can be accessed by any logged in user."
-msgstr ""
+msgstr "Auf das Projekt kann jeder angemeldete Nutzer zugreifen."
msgid "The project can be accessed without any authentication."
-msgstr ""
+msgstr "Auf das Projekt kann ohne Authentifizierung zugegriffen werden."
msgid "The repository for this project does not exist."
-msgstr ""
+msgstr "Das Repository für das Projekt existiert nicht."
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "Die Review-Phase stellt die Zeit vom Anlegen eines Merge Requests bis zum Mergen dar. Sobald Sie Ihren ersten Merge Request abschließen, werden dessen Daten hier automatisch angezeigt."
+msgstr "Die Überprüfungsphase stellt die Zeit vom Anlegen eines Merge Requests bis dessen Umsetzung dar. Sobald Du Deinen ersten Merge Request abschließt, werden dessen Daten hier automatisch angezeigt."
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "Die Staging-Phase stellt die Zeit zwischen Mergen eines Merge Requests und dem Produktivdeployment dar. Sobald Sie das erste Produktivdeployment durchgeführt haben, werden dessen Daten hier automatisch angezeigt."
+msgstr "Die Staging-Phase stellt die Zeit zwischen der Umsetzung eines Merge Requests und der Bereitstellung des Codes auf dem Produktivsystem dar. Sobald Du das erste Mal auf das Produktivsystem ausgeliefert hast, werden dessen Daten hier automatisch angezeigt."
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "Die Test-Phase stellt die Zeit dar, die GitLab CI benötigt um die Pipelines von Merge Requests abzuarbeiten. Sobald die erste Pipeline abgeschlossen ist, werden deren Daten hier automatisch angezeigt."
+msgstr "Die Testphase stellt die Zeit dar, die GitLab CI benötigt um die Pipelines von zugehörigen Merge Requests abzuarbeiten. Sobald die erste Pipeline abgeschlossen ist, werden deren Daten hier automatisch angezeigt."
msgid "The time taken by each data entry gathered by that stage."
-msgstr "Zeit die für das jeweilige Ereignis in der Phase ermittelt wurde."
+msgstr "Zeit, die für das jeweilige Ereignis in der Phase ermittelt wurde."
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6."
msgid "There are problems accessing Git storage: "
-msgstr ""
+msgstr "Es gibt ein Problem beim Zugriff auf den Gitspeicher:"
msgid "This means you can not push code until you create an empty repository or import existing one."
-msgstr ""
+msgstr "Dies bedeutet, dass Du keinen Code übertragen kannst, bevor Du kein leeres Repositorium erstellt oder ein Existierendes importiert hast."
msgid "Time before an issue gets scheduled"
-msgstr "Zeit bis ein Issue geplant wird"
+msgstr "Zeit bis ein Ticket geplant wird"
msgid "Time before an issue starts implementation"
-msgstr "Zeit bis die Implementierung für ein Issue beginnt"
+msgstr "Zeit bis die Implementierung für ein Ticket beginnt"
msgid "Time between merge request creation and merge/close"
-msgstr "Zeit zwischen Anlegen und Mergen/Schließen eines Merge Requests"
+msgstr "Zeit zwischen einem Merge Request und dessen Umsetzung / Schließung"
msgid "Time until first merge request"
msgstr "Zeit bis zum ersten Merge Request"
msgid "Timeago|%s days ago"
-msgstr ""
+msgstr "seit %s Tagen"
msgid "Timeago|%s days remaining"
-msgstr ""
+msgstr "%s Tage verbleibend"
msgid "Timeago|%s hours remaining"
-msgstr ""
+msgstr "%s Stunden verbleibend"
msgid "Timeago|%s minutes ago"
-msgstr ""
+msgstr "seit %s Minuten "
msgid "Timeago|%s minutes remaining"
-msgstr ""
+msgstr "%s Minuten verbleibend"
msgid "Timeago|%s months ago"
-msgstr ""
+msgstr "seit %s Monaten"
msgid "Timeago|%s months remaining"
-msgstr ""
+msgstr "%s Monate verbleibend"
msgid "Timeago|%s seconds remaining"
-msgstr ""
+msgstr "%s Sekunden verbleibend"
msgid "Timeago|%s weeks ago"
-msgstr ""
+msgstr "seit %s Wochen"
msgid "Timeago|%s weeks remaining"
-msgstr ""
+msgstr "%s Wochen verbleibend"
msgid "Timeago|%s years ago"
-msgstr ""
+msgstr "seit %s Jahren"
msgid "Timeago|%s years remaining"
-msgstr ""
+msgstr "%s Jahre verbleibend"
msgid "Timeago|1 day remaining"
-msgstr ""
+msgstr "1 Tag verbleibend"
msgid "Timeago|1 hour remaining"
-msgstr ""
+msgstr "1 Stunde verbleibend"
msgid "Timeago|1 minute remaining"
-msgstr ""
+msgstr "1 Minute verbleibend"
msgid "Timeago|1 month remaining"
-msgstr ""
+msgstr "1 Monat verbleibend"
msgid "Timeago|1 week remaining"
-msgstr ""
+msgstr "1 Woche verbleibend"
msgid "Timeago|1 year remaining"
-msgstr ""
+msgstr "1 Jahr verbleibend"
msgid "Timeago|Past due"
-msgstr ""
+msgstr "Fällig"
msgid "Timeago|a day ago"
-msgstr ""
+msgstr "vor einem Tag"
msgid "Timeago|a month ago"
-msgstr ""
+msgstr "vor einem Monat"
msgid "Timeago|a week ago"
-msgstr ""
+msgstr "vor einer Woche"
msgid "Timeago|a while"
-msgstr ""
+msgstr "eine Weile"
msgid "Timeago|a year ago"
-msgstr ""
+msgstr "vor einem Jahr"
msgid "Timeago|about %s hours ago"
-msgstr ""
+msgstr "vor ungefähr %s Stunden"
msgid "Timeago|about a minute ago"
-msgstr ""
+msgstr "vor ungefähr einer Minute"
msgid "Timeago|about an hour ago"
-msgstr ""
+msgstr "vor ungefähr einer Stunde"
msgid "Timeago|in %s days"
-msgstr ""
+msgstr "in %s Tagen"
msgid "Timeago|in %s hours"
-msgstr ""
+msgstr "in %s Stunden"
msgid "Timeago|in %s minutes"
-msgstr ""
+msgstr "in %s Minuten"
msgid "Timeago|in %s months"
-msgstr ""
+msgstr "in %s Monaten"
msgid "Timeago|in %s seconds"
-msgstr ""
+msgstr "in %s Sekunden"
msgid "Timeago|in %s weeks"
-msgstr ""
+msgstr "in %s Wochen"
msgid "Timeago|in %s years"
-msgstr ""
+msgstr "in %s Jahren"
msgid "Timeago|in 1 day"
-msgstr ""
+msgstr "in 1 Tag"
msgid "Timeago|in 1 hour"
-msgstr ""
+msgstr "in 1 Stunde"
msgid "Timeago|in 1 minute"
-msgstr ""
+msgstr "in 1 Minute"
msgid "Timeago|in 1 month"
-msgstr ""
+msgstr "in 1 Monat"
msgid "Timeago|in 1 week"
-msgstr ""
+msgstr "in 1 Woche"
msgid "Timeago|in 1 year"
-msgstr ""
+msgstr "in 1 Jahr"
msgid "Timeago|less than a minute ago"
-msgstr ""
+msgstr "vor weniger als einer Minute"
msgid "Time|hr"
msgid_plural "Time|hrs"
-msgstr[0] "h"
-msgstr[1] "h"
+msgstr[0] "Std."
+msgstr[1] "Stdn."
msgid "Time|min"
msgid_plural "Time|mins"
-msgstr[0] "min"
-msgstr[1] "min"
+msgstr[0] "Min."
+msgstr[1] "Min."
msgid "Time|s"
-msgstr "s"
+msgstr "Sek."
msgid "Total Time"
msgstr "Gesamtzeit"
msgid "Total test time for all commits/merges"
-msgstr "Gesamte Testlaufzeit für alle Commits/Merges"
+msgstr "Gesamte Testzeit für alle Commits/Merges"
msgid "Unstar"
-msgstr ""
+msgstr "Entfavorisieren"
msgid "Upload New File"
-msgstr ""
+msgstr "Eine Neue Datei hochladen"
msgid "Upload file"
-msgstr ""
+msgstr "Eine Datei hochladen"
msgid "UploadLink|click to upload"
-msgstr ""
+msgstr "Zum Upload klicken"
msgid "Use the following registration token during setup:"
-msgstr ""
+msgstr "Benutze den folgenden Registrierungstoken während des Setups:"
msgid "Use your global notification setting"
-msgstr ""
+msgstr "Benutze Deine globalen Benachrichtigungseinstellungen"
msgid "View open merge request"
-msgstr ""
+msgstr "Zeige offene Merge Requests."
msgid "VisibilityLevel|Internal"
-msgstr ""
+msgstr "Intern"
msgid "VisibilityLevel|Private"
-msgstr ""
+msgstr "Privat"
msgid "VisibilityLevel|Public"
-msgstr ""
+msgstr "Öffentlich"
msgid "VisibilityLevel|Unknown"
-msgstr ""
+msgstr "Unbekannt"
msgid "Want to see the data? Please ask an administrator for access."
-msgstr "Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Administrator."
+msgstr "Du möchtest diese Daten sehen? Bitte frage einen Administrator nach dem Zugang."
msgid "We don't have enough data to show this stage."
msgstr "Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen."
-msgid "Withdraw Access Request"
+msgid "Wiki"
msgstr ""
+msgid "Withdraw Access Request"
+msgstr "Zugriffsanfrage widerrufen"
+
msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr ""
+msgstr "Du bist dabei %{group_name} zu entfernen. Entfernte Gruppen können NICHT wiederhergestellt werden! Bist Du dir WIRKLICH sicher?"
msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr ""
+msgstr "Du bist dabei %{project_name_with_namespace} zu entfernen. Entfernte Projekte können NICHT wiederhergestellt werden! Bist Du dir WIRKLICH sicher?"
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
-msgstr ""
+msgstr "Du bist dabei, die Beziehung des Ablegers zum Ursprungsprojekt %{forked_from_project}, zu entfernen. Bist Du dir WIRKLICH sicher?"
msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
-msgstr ""
+msgstr "Du bist dabei %{project_name_with_namespace} einem andere Besitzer zu übergeben. Bist Du dir WIRKLICH sicher?"
msgid "You can only add files when you are on a branch"
-msgstr ""
+msgstr "Du kannst Dateien nur hinzufügen, wenn Du dich auf einem Branch befindest."
msgid "You have reached your project limit"
-msgstr ""
+msgstr "Du hast die Projektbegrenzung erreicht."
msgid "You must sign in to star a project"
-msgstr ""
+msgstr "Du musst angemeldet sein, um ein Projekt zu favorisieren."
msgid "You need permission."
-msgstr "Sie benötigen Zugriffsrechte."
+msgstr "Du brauchst eine Genehmigung."
msgid "You will not get any notifications via email"
-msgstr ""
+msgstr "Du wirst keine Benachrichtigungen per E-Mail erhalten."
msgid "You will only receive notifications for the events you choose"
-msgstr ""
+msgstr "Du wirst nur Benachrichtigungen für, von Dir ausgewählte, Ereignisse erhalten."
msgid "You will only receive notifications for threads you have participated in"
-msgstr ""
+msgstr "Du wirst nur Benachrichtigungen für Unterhaltungen, an denen Du teilgenommen hast, erhalten."
msgid "You will receive notifications for any activity"
-msgstr ""
+msgstr "Du wirst bei jeder Aktivität Benachrichtigungen erhalten."
msgid "You will receive notifications only for comments in which you were @mentioned"
-msgstr ""
+msgstr "Du wirst nur Benachrichtigungen für Kommentare erhalten, in denen du @erwähnt wurdest."
msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
-msgstr ""
+msgstr "Du kannst erst mittels '%{protocol}' übertragen (push) oder abrufen (pull), nachdem Du für dein Konto '%{set_password_link}'."
msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
-msgstr ""
+msgstr "Du kannst erst mittels SSH übertragen (push) oder abrufen (pull), nachdem Du Deinem Konto '%{add_ssh_key_link}'."
msgid "Your name"
-msgstr ""
+msgstr "Dein Name"
msgid "day"
msgid_plural "days"
@@ -1276,12 +1444,13 @@ msgstr[0] "Tag"
msgstr[1] "Tage"
msgid "new merge request"
-msgstr ""
+msgstr "Neuer Merge Request"
msgid "notification emails"
-msgstr ""
+msgstr "Benachrichtungsemail"
msgid "parent"
msgid_plural "parents"
-msgstr[0] ""
-msgstr[1] "" \ No newline at end of file
+msgstr[0] "Vorgänger"
+msgstr[1] "Vorgänger"
+
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index 84232be601e..0ac591d4927 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -82,9 +82,6 @@ msgstr ""
msgid "Add new directory"
msgstr ""
-msgid "All"
-msgstr ""
-
msgid "Archived project! Repository is read-only"
msgstr ""
@@ -225,9 +222,6 @@ msgstr ""
msgid "CiStatus|running"
msgstr ""
-msgid "Comments"
-msgstr ""
-
msgid "Commit"
msgid_plural "Commits"
msgstr[0] ""
@@ -400,24 +394,6 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
-msgid "EventFilterBy|Filter by all"
-msgstr ""
-
-msgid "EventFilterBy|Filter by comments"
-msgstr ""
-
-msgid "EventFilterBy|Filter by issue events"
-msgstr ""
-
-msgid "EventFilterBy|Filter by merge events"
-msgstr ""
-
-msgid "EventFilterBy|Filter by push events"
-msgstr ""
-
-msgid "EventFilterBy|Filter by team"
-msgstr ""
-
msgid "Every day (at 4:00am)"
msgstr ""
@@ -513,9 +489,6 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
-msgid "Issue events"
-msgstr ""
-
msgid "Jobs for last month"
msgstr ""
@@ -545,12 +518,6 @@ msgstr ""
msgid "Last commit"
msgstr ""
-msgid "LastPushEvent|You pushed to"
-msgstr ""
-
-msgid "LastPushEvent|at"
-msgstr ""
-
msgid "Learn more in the"
msgstr ""
@@ -571,9 +538,6 @@ msgstr[1] ""
msgid "Median"
msgstr ""
-msgid "Merge events"
-msgstr ""
-
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -777,9 +741,6 @@ msgstr ""
msgid "Pipeline|with stages"
msgstr ""
-msgid "Project"
-msgstr ""
-
msgid "Project '%{project_name}' queued for deletion."
msgstr ""
@@ -813,9 +774,6 @@ msgstr ""
msgid "Project home"
msgstr ""
-msgid "ProjectActivityRSS|Subscribe"
-msgstr ""
-
msgid "ProjectFeature|Disabled"
msgstr ""
@@ -837,9 +795,6 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph"
msgstr ""
-msgid "Push events"
-msgstr ""
-
msgid "Read more"
msgstr ""
@@ -970,9 +925,6 @@ msgstr ""
msgid "Target Branch"
msgstr ""
-msgid "Team"
-msgstr ""
-
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr ""
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 4617de25a7c..8f25c893ecd 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:53-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:21-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Esperanto\n"
"Language: eo_UY\n"
@@ -57,9 +57,18 @@ msgstr "Aro da diagramoj pri la seninterrompa integrado"
msgid "About auto deploy"
msgstr "Pri la aÅ­tomata disponigado"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Aktiva"
@@ -84,6 +93,12 @@ msgstr "Aldoni novan dosierujon"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Arkivita projekto! La deponejo permesas nur legadon"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Alkroĉu dosieron per Åovmetado aÅ­ %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Branĉo"
@@ -137,6 +209,9 @@ msgstr "Elekti dosierojn"
msgid "ByAuthor|by"
msgstr "de"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "Agordoj de seninterrompa integrado"
@@ -164,6 +239,9 @@ msgstr "Listo de ÅanÄoj"
msgid "Charts"
msgstr "Diagramoj"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Precize elekti ĉi tiun kunmetadon"
@@ -259,12 +337,18 @@ msgstr "Enmetita de"
msgid "Compare"
msgstr "Kompari"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Gvidlinioj por kontribuado"
msgid "Contributors"
msgstr "Kontribuantoj"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Kopii la adreson en la kopibufron"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "Disponigado"
msgstr[1] "Disponigadoj"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "Priskribo"
@@ -399,6 +486,9 @@ msgstr "Redakti"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Redakti ĉenstablan planon %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "De la kreado de la problemo Äis la disponigado en la publika versio"
msgid "From merge request merge until deploy to production"
msgstr "De la kunfandado de la peto pri kunfando Äis la disponigado en la publika versio"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Al via disbranĉigo"
msgid "GoToYourFork|Fork"
msgstr "Disbranĉigo"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr "Hejmo"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "La refreÅigo komenciÄis sukcese"
@@ -515,14 +617,8 @@ msgstr "Ni prezentas al vi la ciklan analizon"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Taskoj po la lasta monato"
-
-msgid "Jobs for last week"
-msgstr "Taskoj po la lasta semajno"
-
-msgid "Jobs for last year"
-msgstr "Taskoj po la lasta jaro"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "MalÅaltita"
@@ -530,6 +626,9 @@ msgstr "MalÅaltita"
msgid "LFSStatus|Enabled"
msgstr "Åœaltita"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "La lasta %d tago"
@@ -562,20 +661,38 @@ msgstr "Forlasi la grupon"
msgid "Leave project"
msgstr "Forlasi la projekton"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limigita al montrado de ne pli ol %d evento"
msgstr[1] "Limigita al montrado de ne pli ol %d eventoj"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Mediano"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "aldonos SSH-Ålosilon"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "Partoprenado"
msgid "NotificationLevel|Watch"
msgstr "Rigardado"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Filtrilo"
@@ -686,9 +806,15 @@ msgstr "Malfermita"
msgid "Options"
msgstr "Opcioj"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "Posedanto"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "Ĉenstablo"
@@ -701,6 +827,9 @@ msgstr "Ĉenstabla plano"
msgid "Pipeline Schedules"
msgstr "Ĉenstablaj planoj"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Malsukcesaj:"
@@ -764,6 +893,15 @@ msgstr "Ĉenstabloj"
msgid "Pipelines charts"
msgstr "Ĉenstablaj diagramoj"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "ĉiuj"
@@ -776,6 +914,12 @@ msgstr "kun etapo"
msgid "Pipeline|with stages"
msgstr "kun etapoj"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "La elporto de la projekto komenciÄis. Vi ricevos ligilon per retpoÅto
msgid "Project home"
msgstr "Hejmo de la projekto"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Etapo"
msgid "ProjectNetworkGraph|Graph"
msgstr "Grafeo"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "Malfari ĉi tiun enmetadon"
msgid "Revert this merge request"
msgstr "Malfari ĉi tiun peton pri kunfando"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Konservi ĉenstablan planon"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Elektu celan branĉon"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuÅi per %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "Agordi aÅ­tomatan disponigadon"
msgid "SetPasswordToCloneLink|set a password"
msgstr "kreos pasvorton"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Estas montrata %d evento"
msgstr[1] "Estas montrataj %d eventoj"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Kodo"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto.
msgid "We don't have enough data to show this stage."
msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Nuligi la peton pri atingeblo"
@@ -1284,4 +1452,5 @@ msgstr "sciigoj per retpoÅto"
msgid "parent"
msgid_plural "parents"
msgstr[0] "patro"
-msgstr[1] "patroj" \ No newline at end of file
+msgstr[1] "patroj"
+
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 8158bd275bd..eee720d5ba2 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:37-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -57,9 +57,18 @@ msgstr "Una colección de gráficos sobre Integración Continua"
msgid "About auto deploy"
msgstr "Acerca del auto despliegue"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Activo"
@@ -84,6 +93,12 @@ msgstr "Agregar nuevo directorio"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "¡Proyecto archivado! El repositorio es de solo lectura"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Adjunte un archivo arrastrando &amp; soltando o %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Rama"
@@ -137,6 +209,9 @@ msgstr "Examinar archivos"
msgid "ByAuthor|by"
msgstr "por"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "Configuración de CI"
@@ -164,6 +239,9 @@ msgstr ""
msgid "Charts"
msgstr "Gráficos"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Escoger este cambio"
@@ -259,12 +337,18 @@ msgstr "Enviado por"
msgid "Compare"
msgstr "Comparar"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Guía de contribución"
msgid "Contributors"
msgstr "Contribuidores"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Copiar URL al portapapeles"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "Despliegue"
msgstr[1] "Despliegues"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "Descripción"
@@ -399,6 +486,9 @@ msgstr "Editar"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Editar Programación del Pipeline %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "Desde la creación de la incidencia hasta el despliegue a producción"
msgid "From merge request merge until deploy to production"
msgstr "Desde la integración de la solicitud de fusión hasta el despliegue a producción"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Ir a tu bifurcación"
msgid "GoToYourFork|Fork"
msgstr "Bifurcación"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr "Inicio"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Servicio de limpieza iniciado con éxito"
@@ -515,14 +617,8 @@ msgstr "Introducción a Cycle Analytics"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Trabajos del mes pasado"
-
-msgid "Jobs for last week"
-msgstr "Trabajos de la semana pasada"
-
-msgid "Jobs for last year"
-msgstr "Trabajos del año pasado"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Deshabilitado"
@@ -530,6 +626,9 @@ msgstr "Deshabilitado"
msgid "LFSStatus|Enabled"
msgstr "Habilitado"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Último %d día"
@@ -562,20 +661,38 @@ msgstr "Abandonar grupo"
msgid "Leave project"
msgstr "Abandonar proyecto"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limitado a mostrar máximo %d evento"
msgstr[1] "Limitado a mostrar máximo %d eventos"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Mediana"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "agregar una clave SSH"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "Participación"
msgid "NotificationLevel|Watch"
msgstr "Vigilancia"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Filtrar"
@@ -686,9 +806,15 @@ msgstr "Abierto"
msgid "Options"
msgstr "Opciones"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "Propietario"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -701,6 +827,9 @@ msgstr "Programación del Pipeline"
msgid "Pipeline Schedules"
msgstr "Programaciones de los Pipelines"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Fallidos:"
@@ -764,6 +893,15 @@ msgstr ""
msgid "Pipelines charts"
msgstr "Gráficos de los pipelines"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "todos"
@@ -776,6 +914,12 @@ msgstr "con etapa"
msgid "Pipeline|with stages"
msgstr "con etapas"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "Se inició la exportación del proyecto. Se enviará un enlace de descar
msgid "Project home"
msgstr "Inicio del proyecto"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Etapa"
msgid "ProjectNetworkGraph|Graph"
msgstr "Historial gráfico"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "Revertir este cambio"
msgid "Revert this merge request"
msgstr "Revertir esta solicitud de fusión"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Guardar programación del pipeline"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Selecciona una rama de destino"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "Configurar auto despliegue"
msgid "SetPasswordToCloneLink|set a password"
msgstr "establecer una contraseña"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Mostrando %d evento"
msgstr[1] "Mostrando %d eventos"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Código fuente"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador."
msgid "We don't have enough data to show this stage."
msgstr "No hay suficientes datos para mostrar en esta etapa."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Retirar Solicitud de Acceso"
@@ -1284,4 +1452,5 @@ msgstr "correos electrónicos de notificación"
msgid "parent"
msgid_plural "parents"
msgstr[0] "padre"
-msgstr[1] "padres" \ No newline at end of file
+msgstr[1] "padres"
+
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 3daff3f5c19..43e66d8dea4 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:53-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -57,9 +57,18 @@ msgstr "Un ensemble de graphiques concernant l’Intégration Continue (CI)"
msgid "About auto deploy"
msgstr "A propos de l'auto-déploiement"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Actif"
@@ -84,6 +93,12 @@ msgstr "Ajouter un nouveau dossier"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Projet archivé ! Le dépôt est en lecture seule"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Attachez un fichier par glisser &amp; déposer ou %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] ""
@@ -137,6 +209,9 @@ msgstr "Parcourir les fichiers"
msgid "ByAuthor|by"
msgstr "par"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "Configuration de l'intégration continue (CI)"
@@ -164,6 +239,9 @@ msgstr "Journal des modifications"
msgid "Charts"
msgstr "Graphiques"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Sélectionner cette validation"
@@ -259,12 +337,18 @@ msgstr "Validé par"
msgid "Compare"
msgstr "Comparer"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Guilde de contribution"
msgid "Contributors"
msgstr "Contributeurs"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Copier l'URL dans le presse-papier"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "Déploiement"
msgstr[1] "Déploiements"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr ""
@@ -399,6 +486,9 @@ msgstr "Éditer"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Éditer le pipeline programmé %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
msgid "From merge request merge until deploy to production"
msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Aller à votre fourche"
msgid "GoToYourFork|Fork"
msgstr "Fourche"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr "Accueil"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Maintenance démarrée avec succès"
@@ -515,14 +617,8 @@ msgstr "Introduction à l'analyseur de cycle"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Tâches pour le mois dernier"
-
-msgid "Jobs for last week"
-msgstr "Tâches pour la semaine dernière"
-
-msgid "Jobs for last year"
-msgstr "Tâches pour l'année dernière"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Désactivé"
@@ -530,6 +626,9 @@ msgstr "Désactivé"
msgid "LFSStatus|Enabled"
msgstr "Activé"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Le dernier %d jour"
@@ -562,20 +661,38 @@ msgstr "Quitter le groupe"
msgid "Leave project"
msgstr "Quitter le projet"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limiter l'affichage au plus à %d évènement"
msgstr[1] "Limiter l'affichage au plus à %d évènements"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Médian"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "ajouter une clef SSH"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "Participation"
msgid "NotificationLevel|Watch"
msgstr "Surveillé"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Filtre"
@@ -686,9 +806,15 @@ msgstr "Ouvert"
msgid "Options"
msgstr ""
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "Propriétaire"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -701,6 +827,9 @@ msgstr "Programmation de pipeline"
msgid "Pipeline Schedules"
msgstr "Programmations de pipeline"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Échecs : "
@@ -764,6 +893,15 @@ msgstr ""
msgid "Pipelines charts"
msgstr "Graphique des pipelines"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "Tous"
@@ -776,6 +914,12 @@ msgstr "avec l'étape"
msgid "Pipeline|with stages"
msgstr "avec les étapes"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "L'export du projet a débuté. Un lien de téléchargement sera envoyé
msgid "Project home"
msgstr "Accueil du projet"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Étape"
msgid "ProjectNetworkGraph|Graph"
msgstr "Graphique "
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "Annuler cette validation"
msgid "Revert this merge request"
msgstr "Annuler cette demande de fusion"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Sauvegarder le pipeline programmé"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Sélectionnez une branche cible"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "Mettre en place l’auto-déploiement"
msgid "SetPasswordToCloneLink|set a password"
msgstr "définir un mot de passe"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Affichage de %d évènement"
msgstr[1] "Affichage de %d évènements"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Code source"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pou
msgid "We don't have enough data to show this stage."
msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Retirer la demande d'accès"
@@ -1284,4 +1452,5 @@ msgstr "courriels de notification"
msgid "parent"
msgid_plural "parents"
msgstr[0] ""
-msgstr[1] "" \ No newline at end of file
+msgstr[1] ""
+
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2b7c6f7ad33..e5cf2aeb513 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-24 09:29+0200\n"
-"PO-Revision-Date: 2017-08-24 09:29+0200\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 08:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -58,9 +58,18 @@ msgstr ""
msgid "About auto deploy"
msgstr ""
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -85,6 +94,12 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr ""
@@ -106,6 +121,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] ""
@@ -138,6 +210,9 @@ msgstr ""
msgid "ByAuthor|by"
msgstr ""
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr ""
@@ -165,6 +240,9 @@ msgstr ""
msgid "Charts"
msgstr ""
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr ""
@@ -260,12 +338,18 @@ msgstr ""
msgid "Compare"
msgstr ""
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr ""
msgid "Contributors"
msgstr ""
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr ""
@@ -352,6 +436,9 @@ msgid_plural "Deploys"
msgstr[0] ""
msgstr[1] ""
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr ""
@@ -400,6 +487,9 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -465,6 +555,12 @@ msgstr ""
msgid "From merge request merge until deploy to production"
msgstr ""
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -477,6 +573,9 @@ msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -498,6 +597,9 @@ msgstr ""
msgid "Home"
msgstr ""
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -516,12 +618,18 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issues"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
msgid "LFSStatus|Enabled"
msgstr ""
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] ""
@@ -554,20 +662,38 @@ msgstr ""
msgid "Leave project"
msgstr ""
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
msgstr[1] ""
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr ""
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -669,6 +795,9 @@ msgstr ""
msgid "NotificationLevel|Watch"
msgstr ""
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr ""
@@ -678,9 +807,15 @@ msgstr ""
msgid "Options"
msgstr ""
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr ""
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -693,6 +828,9 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -777,6 +915,12 @@ msgstr ""
msgid "Pipeline|with stages"
msgstr ""
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -813,6 +957,9 @@ msgstr ""
msgid "Project home"
msgstr ""
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -837,6 +984,9 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph"
msgstr ""
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -897,6 +1047,9 @@ msgstr ""
msgid "Revert this merge request"
msgstr ""
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
@@ -921,6 +1074,9 @@ msgstr ""
msgid "Select target branch"
msgstr ""
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -936,14 +1092,23 @@ msgstr ""
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] ""
msgstr[1] ""
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr ""
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1220,6 +1385,9 @@ msgstr ""
msgid "We don't have enough data to show this stage."
msgstr ""
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr ""
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 7b8bea46e26..46b3e12f97c 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 10:25-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -57,9 +57,18 @@ msgstr "Un insieme di grafici riguardo la Continuous Integration"
msgid "About auto deploy"
msgstr "Riguardo il rilascio automatico"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Attivo"
@@ -84,6 +93,12 @@ msgstr "Aggiungi una directory (cartella)"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Progetto archiviato! La Repository è sola-lettura"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Aggiungi un file tramite trascina &amp; rilascia ( drag &amp; drop) o %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] ""
@@ -137,6 +209,9 @@ msgstr "Guarda i files"
msgid "ByAuthor|by"
msgstr "per"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "Configurazione CI (Integrazione Continua)"
@@ -164,6 +239,9 @@ msgstr ""
msgid "Charts"
msgstr "Grafici"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr ""
@@ -259,12 +337,18 @@ msgstr "Committato da "
msgid "Compare"
msgstr "Confronta"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Guida per contribuire"
msgid "Contributors"
msgstr "Collaboratori"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Copia URL negli appunti"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "Rilascio"
msgstr[1] "Rilasci"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "Descrizione"
@@ -399,6 +486,9 @@ msgstr "Modifica"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Cambia programmazione della pipeline %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "Dalla creazione di un issue fino al rilascio in produzione"
msgid "From merge request merge until deploy to production"
msgstr "Dalla richiesta di merge fino effettua il merge fino al rilascio in produzione"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Vai il tuo fork"
msgid "GoToYourFork|Fork"
msgstr "Fork"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr ""
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Housekeeping iniziato con successo"
@@ -515,14 +617,8 @@ msgstr "Introduzione delle Analisi Cicliche"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Jobs dell'ultimo mese"
-
-msgid "Jobs for last week"
-msgstr "Jobs dell'ultima settimana"
-
-msgid "Jobs for last year"
-msgstr "Jobs dell'ultimo anno"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Disabilitato"
@@ -530,6 +626,9 @@ msgstr "Disabilitato"
msgid "LFSStatus|Enabled"
msgstr "Abilitato"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "L'ultimo %d giorno"
@@ -562,20 +661,38 @@ msgstr "Abbandona il gruppo"
msgid "Leave project"
msgstr "Abbandona il progetto"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limita visualizzazione %d d'evento"
msgstr[1] "Limita visualizzazione %d di eventi"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Mediano"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "aggiungi una chiave SSH"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "Partecipa"
msgid "NotificationLevel|Watch"
msgstr "Osserva"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Filtra"
@@ -686,9 +806,15 @@ msgstr "Aperto"
msgid "Options"
msgstr "Opzioni"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr ""
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -701,6 +827,9 @@ msgstr "Pianificazione Pipeline"
msgid "Pipeline Schedules"
msgstr "Pianificazione multipla Pipeline"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Fallita:"
@@ -764,6 +893,15 @@ msgstr "Pipeline"
msgid "Pipelines charts"
msgstr "Grafici pipeline"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "tutto"
@@ -776,6 +914,12 @@ msgstr "con stadio"
msgid "Pipeline|with stages"
msgstr "con più stadi"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "Esportazione del progetto iniziata. Un link di download sarà inviato vi
msgid "Project home"
msgstr "Home di progetto"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Stadio"
msgid "ProjectNetworkGraph|Graph"
msgstr "Grafico"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "Ripristina questo commit"
msgid "Revert this merge request"
msgstr "Ripristina questa richiesta di merge"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Salva pianificazione pipeline"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Seleziona una branch di destinazione"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "Configura il rilascio automatico"
msgid "SetPasswordToCloneLink|set a password"
msgstr "imposta una password"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Visualizza %d evento"
msgstr[1] "Visualizza %d eventi"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Codice Sorgente"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazi
msgid "We don't have enough data to show this stage."
msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Ritira richiesta d'accesso"
@@ -1284,4 +1452,5 @@ msgstr "Notifiche via email"
msgid "parent"
msgid_plural "parents"
msgstr[0] ""
-msgstr[1] "" \ No newline at end of file
+msgstr[1] ""
+
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 670ac2d9684..bc25b69c80a 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 10:14-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
@@ -53,9 +53,18 @@ msgstr "CIã«ã¤ã„ã¦ã®ã‚°ãƒ©ãƒ•"
msgid "About auto deploy"
msgstr "自動デプロイã«ã¤ã„ã¦"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "有効"
@@ -80,6 +89,12 @@ msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’追加"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "アーカイブ済ã¿ãƒ—ロジェクトï¼ï¼ˆãƒ¬ãƒã‚¸ãƒˆãƒªãƒ¼ã¯èª­ã¿å–り専用ã§ã™ï¼‰"
@@ -101,6 +116,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "ドラッグ&ドロップã¾ãŸã¯ %{upload_link} ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’添付"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "ブランãƒ"
@@ -132,6 +204,9 @@ msgstr "ファイルを表示"
msgid "ByAuthor|by"
msgstr "作者"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "CI 設定"
@@ -159,6 +234,9 @@ msgstr "変更履歴"
msgid "Charts"
msgstr "ãƒãƒ£ãƒ¼ãƒˆ"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’ãƒã‚§ãƒªãƒ¼ãƒ”ック"
@@ -253,12 +331,18 @@ msgstr "コミット担当者: "
msgid "Compare"
msgstr "比較"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "貢献者å‘ã‘ガイド"
msgid "Contributors"
msgstr "貢献者"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "クリップボードã«URLをコピー"
@@ -344,6 +428,9 @@ msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "デプロイ"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "説明"
@@ -392,6 +479,9 @@ msgstr "編集"
msgid "Edit Pipeline Schedule %{id}"
msgstr "パイプラインスケジュール %{id} を編集"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -456,6 +546,12 @@ msgstr "課題ãŒç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒ—ロダクションã«ãƒ‡ãƒ—ロイã•ã‚Œ
msgid "From merge request merge until deploy to production"
msgstr "マージリクエストãŒãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã‹ã‚‰ãƒ—ロダクションã«ãƒ‡ãƒ—ロイã•ã‚Œã‚‹ã¾ã§"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -468,6 +564,9 @@ msgstr "自分ã®ãƒ•ã‚©ãƒ¼ã‚¯ã¸ç§»å‹•"
msgid "GoToYourFork|Fork"
msgstr "フォーク"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -489,6 +588,9 @@ msgstr ""
msgid "Home"
msgstr "ホーム"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ãƒã‚¦ã‚¹ã‚­ãƒ¼ãƒ”ングã¯æ­£å¸¸ã«èµ·å‹•ã—ã¾ã—ãŸã€‚"
@@ -507,14 +609,8 @@ msgstr "サイクル分æžã®ã”紹介"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "先月ã®ã‚¸ãƒ§ãƒ–"
-
-msgid "Jobs for last week"
-msgstr "先週ã®ã‚¸ãƒ§ãƒ–"
-
-msgid "Jobs for last year"
-msgstr "昨年ã®ã‚¸ãƒ§ãƒ–"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "無効"
@@ -522,6 +618,9 @@ msgstr "無効"
msgid "LFSStatus|Enabled"
msgstr "有効"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "éŽåŽ»%d日間"
@@ -553,19 +652,37 @@ msgstr "グループを離脱"
msgid "Leave project"
msgstr "プロジェクトを離脱"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "イベント表示数を最大 %d 個ã«åˆ¶é™"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "中央値"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "SSH éµã‚’追加"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -666,6 +783,9 @@ msgstr "å‚加"
msgid "NotificationLevel|Watch"
msgstr "ã™ã¹ã¦é€šçŸ¥"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "フィルター"
@@ -675,9 +795,15 @@ msgstr "オープンã•ã‚ŒãŸã®ã¯"
msgid "Options"
msgstr "オプション"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "オーナー"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "パイプライン"
@@ -690,6 +816,9 @@ msgstr "パイプラインスケジュール"
msgid "Pipeline Schedules"
msgstr "パイプラインスケジュール"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失敗:"
@@ -753,6 +882,15 @@ msgstr "パイプライン"
msgid "Pipelines charts"
msgstr "パイプラインãƒãƒ£ãƒ¼ãƒˆ"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "全件"
@@ -765,6 +903,12 @@ msgstr "ステージã‚ã‚Š"
msgid "Pipeline|with stages"
msgstr "ステージã‚ã‚Š"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -801,6 +945,9 @@ msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’開始ã—ã¾ã—ãŸã€‚ダウン
msgid "Project home"
msgstr "プロジェクトホーム"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -825,6 +972,9 @@ msgstr "ステージ"
msgid "ProjectNetworkGraph|Graph"
msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚°ãƒ©ãƒ•"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -885,6 +1035,9 @@ msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’リãƒãƒ¼ãƒˆ"
msgid "Revert this merge request"
msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’リãƒãƒ¼ãƒˆ"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "パイプラインスケジュールをä¿å­˜"
@@ -909,6 +1062,9 @@ msgstr ""
msgid "Select target branch"
msgstr "ターゲットブランãƒã‚’é¸æŠž"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "%{protocol} プロコトル経由ã§ãƒ—ルã€ãƒ—ッシュã™ã‚‹ãŸã‚ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードを設定。"
@@ -924,13 +1080,22 @@ msgstr "自動デプロイを設定"
msgid "SetPasswordToCloneLink|set a password"
msgstr "パスワードを設定"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "%d ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’表示中"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "ソースコード"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1204,6 +1369,9 @@ msgstr "ã“ã®ãƒ‡ãƒ¼ã‚¿ã‚’å‚ç…§ã—ãŸã„ã§ã™ã‹ï¼Ÿã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯ç®¡
msgid "We don't have enough data to show this stage."
msgstr "データä¸è¶³ã®ãŸã‚ã€ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã®è¡¨ç¤ºã¯ã§ãã¾ã›ã‚“。"
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "アクセスリクエストをå–り消ã™"
@@ -1267,4 +1435,5 @@ msgstr "メール通知"
msgid "parent"
msgid_plural "parents"
-msgstr[0] "親" \ No newline at end of file
+msgstr[0] "親"
+
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index df850115222..4baefdb9a3e 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 10:05-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Korean\n"
"Language: ko_KR\n"
@@ -28,20 +28,20 @@ msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_timeago} ì— %{commit_author_link} ë‹˜ì´ ì»¤ë°‹í•˜ì˜€ìŠµë‹ˆë‹¤. "
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
+msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ ë‹¤ìŒ ì‹œë„ì—ì„œ 성공하면 ì ‘ê·¼ì„ í—ˆìš©í•  것입니다."
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
+msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ %{number_of_seconds} ì´ˆ ê°„ ì ‘ê·¼ì„ ì œí•œí•˜ê² ìŠµë‹ˆë‹¤."
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
-msgstr ""
+msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab ì€ ìžë™ìœ¼ë¡œ 다시 ì‹œë„하지 않습니다. 문제가 í•´ê²°ë˜ë©´ 저장 공간 정보를 초기화 해주세요. "
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
msgid "(checkout the %{link} for information on how to install it)."
-msgstr ""
+msgstr "설치 ë°©ë²•ì— ëŒ€í•œ 정보를 얻기 위해 %{link} 를 ì²´í¬ì•„웃하세요."
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -53,7 +53,16 @@ msgstr "지ì†ì ì¸ í†µí•©ì— ê´€í•œ 그래프 모ìŒ"
msgid "About auto deploy"
msgstr "ìžë™ ë°°í¬ ì •ë³´"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr "오ë™ìž‘ì¤‘ì¸ ì €ìž¥ê³µê°„ì— ëŒ€í•œ ì ‘ê·¼ì´ ë³µêµ¬ ìž‘ì—…ì„ ìœ„í•´ 마운트할 수 있ë„ë¡ ìž„ì‹œë¡œ 허용ë˜ì—ˆìŠµë‹ˆë‹¤. 문제가 í•´ê²°ëœ í›„ 다시 ì ‘ê·¼ì„ í—ˆìš©í•  수 있게 저장공간 정보를 리셋 해주세요."
+
+msgid "Account"
msgstr ""
msgid "Active"
@@ -78,6 +87,12 @@ msgid "Add new directory"
msgstr "새 디렉토리 추가"
msgid "All"
+msgstr "ì „ì²´"
+
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
msgstr ""
msgid "Archived project! Repository is read-only"
@@ -87,20 +102,77 @@ msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "ì´ íŒŒì´í”„ë¼ì¸ ìŠ¤ì¼€ì¥´ì„ ì‚­ì œ 하시겠습니까?"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "변경 ë‚´ìš©ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?"
msgid "Are you sure you want to reset registration token?"
-msgstr ""
+msgstr "ë“±ë¡ í† í°ì„ 초기화 하시겠습니까?"
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "헬스 ì²´í¬ í† í°ì„ 초기화 하시겠습니까?"
msgid "Are you sure?"
-msgstr ""
+msgstr "확실합니까?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "드래그 &amp; 드롭 ë˜ëŠ” %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "브랜치"
@@ -132,6 +204,9 @@ msgstr "íŒŒì¼ ì°¾ì•„ë³´ê¸°"
msgid "ByAuthor|by"
msgstr "작성ìž"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "CI 설정"
@@ -159,6 +234,9 @@ msgstr "변경사항"
msgid "Charts"
msgstr "차트"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "ì´ ì»¤ë°‹ì„ Cherry-pick"
@@ -253,12 +331,18 @@ msgstr "커밋한 사용ìž"
msgid "Compare"
msgstr "비êµ"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "ê¸°ì—¬ì— ëŒ€í•œ 안내"
msgid "Contributors"
msgstr "기여해 주신 분들"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "URLì„ í´ë¦½ë³´ë“œì— 복사"
@@ -269,7 +353,7 @@ msgid "Create New Directory"
msgstr "새 디렉토리 만들기"
msgid "Create a new branch"
-msgstr ""
+msgstr "새 브랜치 ìƒì„±"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "%{protocol}ì„ (를) 통해 Pull 하거나 Push í•  ê°œì¸ ì•¡ì„¸ìŠ¤ 토í°ì„ 만드십시오."
@@ -344,17 +428,20 @@ msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "ë°°í¬"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "설명"
msgid "Details"
-msgstr ""
+msgstr "ìƒì„¸"
msgid "Directory name"
msgstr "디렉토리 ì´ë¦„"
msgid "Discard changes"
-msgstr ""
+msgstr "변경 내용 취소"
msgid "Don't show again"
msgstr "다시 표시하지 ì•ŠìŒ"
@@ -392,23 +479,26 @@ msgstr "편집"
msgid "Edit Pipeline Schedule %{id}"
msgstr "파ì´í”„ë¼ì¸ 스케줄 편집 %{id}"
-msgid "EventFilterBy|Filter by all"
+msgid "Emails"
msgstr ""
+msgid "EventFilterBy|Filter by all"
+msgstr "모든 ê°’ì„ ê¸°ì¤€ìœ¼ë¡œ í•„í„°"
+
msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "댓글 기준으로 필터"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "ì´ìŠˆ ì´ë²¤íŠ¸ 기준으로 í•„í„°"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "머지 ì´ë²¤íŠ¸ 기준으로 í•„í„°"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "푸쉬 ì´ë²¤íŠ¸ 기준으로 í•„í„°"
msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "팀 기준으로 필터"
msgid "Every day (at 4:00am)"
msgstr "ë§¤ì¼ (오전 4ì‹œì—)"
@@ -456,39 +546,51 @@ msgstr "ì´ìŠˆ ìƒì„±ì—ì„œ 프로ë•ì…˜ ë°°í¬ê¹Œì§€"
msgid "From merge request merge until deploy to production"
msgstr "머지 리퀘스트 머지ì—ì„œ 프로ë•ì…˜ í™˜ê²½ì— ë°°í¬ê¹Œì§€"
-msgid "Git storage health information has been reset"
+msgid "GPG Keys"
msgstr ""
-msgid "GitLab Runner section"
+msgid "Geo Nodes"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr "git storage ìƒíƒœ ì •ë³´ê°€ 초기화ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "GitLab Runner section"
+msgstr "GitLab Runner 섹션"
+
msgid "Go to your fork"
msgstr "ë‹¹ì‹ ì˜ í¬í¬ë¡œ ì´ë™í•˜ì„¸ìš”"
msgid "GoToYourFork|Fork"
msgstr "í¬í¬"
-msgid "Health Check"
+msgid "Group overview"
msgstr ""
+msgid "Health Check"
+msgstr "헬스 ì²´í¬"
+
msgid "Health information can be retrieved from the following endpoints. More information is available"
-msgstr ""
+msgstr "헬스 정보는 다ìŒì˜ 경로를 통해 조회할 수 있습니다. ë” ë§Žì€ ì •ë³´ë¥¼ ì´ìš©í•  수 있습니다."
msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "엑세스 토í°: "
msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "ê±´ê°•ë„"
msgid "HealthCheck|No Health Problems Detected"
-msgstr ""
+msgstr " 헬스 문제가 발견ë˜ì§€ 않았습니다."
msgid "HealthCheck|Unhealthy"
-msgstr ""
+msgstr "비정ìƒ"
msgid "Home"
msgstr "홈"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Housekeepingì´ ì„±ê³µì ìœ¼ë¡œ 시작ë˜ì—ˆìŠµë‹ˆë‹¤"
@@ -496,7 +598,7 @@ msgid "Import repository"
msgstr "저장소 가져 오기"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "GitLab CI 와 호환ë˜ëŠ” Runner 설치"
msgid "Interval Pattern"
msgstr "주기 패턴"
@@ -505,16 +607,10 @@ msgid "Introducing Cycle Analytics"
msgstr "Cycle Analytics 소개"
msgid "Issue events"
-msgstr ""
-
-msgid "Jobs for last month"
-msgstr "지난달 Jobs"
+msgstr "ì´ìŠˆ ì´ë²¤íŠ¸"
-msgid "Jobs for last week"
-msgstr "지난주 Jobs"
-
-msgid "Jobs for last year"
-msgstr "지난해 Jobs"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Disabled"
@@ -522,6 +618,9 @@ msgstr "Disabled"
msgid "LFSStatus|Enabled"
msgstr "Enabled"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "최근 %d ì¼"
@@ -530,16 +629,16 @@ msgid "Last Pipeline"
msgstr "최근 파ì´í”„ë¼ì¸"
msgid "Last Update"
-msgstr "최근 ì—…ë°ì´íŠ¸:"
+msgstr "최근 ì—…ë°ì´íŠ¸"
msgid "Last commit"
msgstr "최근 커밋"
msgid "LastPushEvent|You pushed to"
-msgstr ""
+msgstr "푸쉬: "
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "at"
msgid "Learn more in the"
msgstr "ë” ìžì„¸ížˆ 알아보기"
@@ -553,22 +652,40 @@ msgstr "그룹 떠나기"
msgid "Leave project"
msgstr "프로ì íŠ¸ì—ì„œ 나가기"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "최대 %d ì´ë²¤íŠ¸ 만 표시하는 것으로 제한ë©ë‹ˆë‹¤."
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "중앙값"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
+msgstr "머지 ì´ë²¤íŠ¸"
+
+msgid "Messages"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "SSH 키 추가"
-msgid "More information is available|here"
+msgid "Monitoring"
msgstr ""
+msgid "More information is available|here"
+msgstr "여기"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "새 ì´ìŠˆ"
@@ -649,7 +766,7 @@ msgid "NotificationEvent|Successful pipeline"
msgstr "성공ì ì¸ 파ì´í”„ë¼ì¸"
msgid "NotificationLevel|Custom"
-msgstr "커스텀"
+msgstr "ì‚¬ìš©ìž ì •ì˜"
msgid "NotificationLevel|Disabled"
msgstr "사용 안 함"
@@ -666,6 +783,9 @@ msgstr "참여"
msgid "NotificationLevel|Watch"
msgstr "Watch"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "í•„í„°"
@@ -675,9 +795,15 @@ msgstr "열린"
msgid "Options"
msgstr "옵션 "
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "소유ìž"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "파ì´í”„ë¼ì¸"
@@ -690,6 +816,9 @@ msgstr "파ì´í”„ë¼ì¸ 스케쥴"
msgid "Pipeline Schedules"
msgstr "파ì´í”„ë¼ì¸ 스케쥴"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "실패 :"
@@ -753,6 +882,15 @@ msgstr "파ì´í”„ë¼ì¸"
msgid "Pipelines charts"
msgstr "파ì´í”„ë¼ì¸ 차트"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "모ë‘"
@@ -765,6 +903,12 @@ msgstr "스테ì´ì§•"
msgid "Pipeline|with stages"
msgstr "스테ì´ì§•"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -784,7 +928,7 @@ msgid "Project access must be granted explicitly to each user."
msgstr "프로ì íŠ¸ 액세스는 ê° ì‚¬ìš©ìžì—게 명시ì ìœ¼ë¡œ 부여ë˜ì–´ì•¼í•©ë‹ˆë‹¤."
msgid "Project details"
-msgstr ""
+msgstr "프로ì íŠ¸ ìƒì„¸"
msgid "Project export could not be deleted."
msgstr "프로ì íŠ¸ 내보내기를 삭제할 수 없습니다."
@@ -801,9 +945,12 @@ msgstr "프로ì íŠ¸ 내보내기가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. 다운로드 ë§í¬ë
msgid "Project home"
msgstr "프로ì íŠ¸ 홈"
-msgid "ProjectActivityRSS|Subscribe"
+msgid "Project overview"
msgstr ""
+msgid "ProjectActivityRSS|Subscribe"
+msgstr "구ë…"
+
msgid "ProjectFeature|Disabled"
msgstr "사용 안 함"
@@ -825,9 +972,12 @@ msgstr "스테ì´ì§•"
msgid "ProjectNetworkGraph|Graph"
msgstr "그래프"
-msgid "Push events"
+msgid "Push Rules"
msgstr ""
+msgid "Push events"
+msgstr "푸쉬 ì´ë²¤íŠ¸"
+
msgid "Read more"
msgstr "ë” ì½ê¸°"
@@ -871,13 +1021,13 @@ msgid "Request Access"
msgstr "액세스 요청"
msgid "Reset git storage health information"
-msgstr ""
+msgstr "git storage 헬스 정보 초기화"
msgid "Reset health check access token"
-msgstr ""
+msgstr "헬스 ì²´í¬ ì ‘ê·¼ í† í° ì´ˆê¸°í™”"
msgid "Reset runners registration token"
-msgstr ""
+msgstr "runner ë“±ë¡ í† í° ì´ˆê¸°í™”"
msgid "Revert this commit"
msgstr "ì´ ì»¤ë°‹ ë˜ëŒë¦¬ê¸°"
@@ -885,6 +1035,9 @@ msgstr "ì´ ì»¤ë°‹ ë˜ëŒë¦¬ê¸°"
msgid "Revert this merge request"
msgstr "ì´ ë¨¸ì§€ 리퀘스트 ë˜ëŒë¦¬ê¸°"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "파ì´í”„ë¼ì¸ 스케줄 저장"
@@ -909,6 +1062,9 @@ msgstr ""
msgid "Select target branch"
msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜ ì„ íƒ"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "%{protocol} í”„ë¡œí† ì½œì„ í†µí•´ Pull 하거나 Push하려면 ê³„ì •ì— íŒ¨ìŠ¤ì›Œë“œë¥¼ 설정하십시오."
@@ -924,16 +1080,25 @@ msgstr "ìžë™ ë°°í¬ ì„¤ì •"
msgid "SetPasswordToCloneLink|set a password"
msgstr "패스워드 설정"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "%d ê°œì˜ ì´ë²¤íŠ¸ 표시 중"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "소스 코드"
-msgid "Specify the following URL during the Runner setup:"
+msgid "Spam Logs"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr "Runner 설정 중 ë‹¤ìŒ URLì„ ì§€ì •í•˜ì„¸ìš”."
+
msgid "StarProject|Star"
msgstr "별표"
@@ -941,7 +1106,7 @@ msgid "Start a %{new_merge_request} with these changes"
msgstr "ì´ ë³€ê²½ 사항으로 %{new_merge_request} ì„ ì‹œìž‘í•˜ì‹­ì‹œì˜¤."
msgid "Start the Runner!"
-msgstr ""
+msgstr "Runner 시작!"
msgid "Switch branch/tag"
msgstr "스위치 브랜치/태그"
@@ -1008,7 +1173,7 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "ê°’ì€ ì¼ë ¨ì˜ 관측 ê°’ 중ì ì— 있습니다. 예를 들어, 3, 5, 9 사ì´ì˜ 중간 ê°’ì€ 5입니다. 3, 5, 7, 8 사ì´ì˜ 중간 ê°’ì€ (5 + 7) / 2 = 6입니다."
msgid "There are problems accessing Git storage: "
-msgstr ""
+msgstr "git storageì— ì ‘ê·¼í•˜ëŠ”ë° ë¬¸ì œê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. "
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "즉, 빈 저장소를 만들거나 기존 저장소를 가져올 때까지 코드를 Push 할 수 없습니다."
@@ -1178,7 +1343,7 @@ msgid "UploadLink|click to upload"
msgstr "업로드하려면 í´ë¦­í•˜ì‹­ì‹œì˜¤."
msgid "Use the following registration token during setup:"
-msgstr ""
+msgstr "설정 ì¤‘ì— ë‹¤ìŒ ë“±ë¡ í† í° ì´ìš© : "
msgid "Use your global notification setting"
msgstr "전체 알림 설정 사용"
@@ -1204,14 +1369,17 @@ msgstr "ì´ ë°ì´í„°ë¥¼ ë³´ê³  싶ì€ê°€ìš”? 관리ìžì—게 액세스 권한ì
msgid "We don't have enough data to show this stage."
msgstr "ì´ ë‹¨ê³„ë¥¼ ë³´ì—¬ì£¼ê¸°ì— ì¶©ë¶„í•œ ë°ì´í„°ê°€ 없습니다."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "액세스 요청 철회"
msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "%{group_name} ê·¸ë£¹ì„ ì œê±°í•˜ë ¤ê³ í•©ë‹ˆë‹¤. \"ì •ë§ë¡œ\" 확실합니까?"
+msgstr "%{group_name} ê·¸ë£¹ì„ ì œê±°í•˜ë ¤ê³ í•©ë‹ˆë‹¤. \\\"ì •ë§ë¡œ\\\" 확실합니까?"
msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "%{project_name_with_namespace} 프로ì íŠ¸ë¥¼ 삭제하려고합니다. ì‚­ì œëœ í”„ë¡œì íŠ¸ë¥¼ ë³µì› í•  수 없습니다! \"ì •ë§ë¡œ\" 확실합니까?"
+msgstr "%{project_name_with_namespace} 프로ì íŠ¸ë¥¼ 삭제하려고합니다. \"ì‚­ì œëœ í”„ë¡œì íŠ¸ë¥¼ ë³µì› í•  수 없습니다! \\\"ì •ë§ë¡œ\\\" 확실합니까?"
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr "í¬í¬ 관계를 소스 프로ì íŠ¸ %{forked_from_project}ì— ëŒ€í•´ 제거하려고합니다. \"ì •ë§ë¡œ\" 확실합니까?"
@@ -1267,4 +1435,5 @@ msgstr "알림 ì´ë©”ì¼"
msgid "parent"
msgid_plural "parents"
-msgstr[0] "부모" \ No newline at end of file
+msgstr[0] "부모"
+
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index d8887110867..88ca25dbb3b 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 10:14-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
@@ -57,9 +57,18 @@ msgstr "Uma coleção de gráficos sobre Integração Contínua"
msgid "About auto deploy"
msgstr "Sobre o deploy automático"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Ativo"
@@ -84,6 +93,12 @@ msgstr "Adicionar novo diretório"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Projeto arquivado! O repositório é somente leitura"
@@ -105,6 +120,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] ""
@@ -137,6 +209,9 @@ msgstr "Navegar pelos arquivos"
msgid "ByAuthor|by"
msgstr "por"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "Configuração da IC"
@@ -164,6 +239,9 @@ msgstr "Registro de mudanças"
msgid "Charts"
msgstr "Gráficos"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Cherry-pick esse commit"
@@ -259,12 +337,18 @@ msgstr "Commit feito por"
msgid "Compare"
msgstr "Comparar"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Guia de contribuição"
msgid "Contributors"
msgstr "Contribuidores"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Copiar URL para área de transferência"
@@ -351,6 +435,9 @@ msgid_plural "Deploys"
msgstr[0] "Implantação"
msgstr[1] "Implantações"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "Descrição"
@@ -399,6 +486,9 @@ msgstr "Alterar"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Alterar Agendamento do Pipeline %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -464,6 +554,12 @@ msgstr "Da abertura de tarefas até a implantação para a produção"
msgid "From merge request merge until deploy to production"
msgstr "Do merge request até a implantação em produção"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -476,6 +572,9 @@ msgstr "Ir para seu fork"
msgid "GoToYourFork|Fork"
msgstr "Fork"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -497,6 +596,9 @@ msgstr ""
msgid "Home"
msgstr "Início"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Manutenção iniciada com sucesso"
@@ -515,14 +617,8 @@ msgstr "Apresentando a Análise de Ciclo"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Jobs no último mês"
-
-msgid "Jobs for last week"
-msgstr "Jobs na última semana"
-
-msgid "Jobs for last year"
-msgstr "Jobs no último ano"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Desabilitado"
@@ -530,6 +626,9 @@ msgstr "Desabilitado"
msgid "LFSStatus|Enabled"
msgstr "Habilitado"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Último %d dia"
@@ -562,20 +661,38 @@ msgstr "Sair do grupo"
msgid "Leave project"
msgstr "Sair do projeto"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limitado a mostrar %d evento, no máximo"
msgstr[1] "Limitado a mostrar %d eventos, no máximo"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Mediana"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "adicione uma chave SSH"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -677,6 +794,9 @@ msgstr "Participar"
msgid "NotificationLevel|Watch"
msgstr "Observar"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Filtrar"
@@ -686,9 +806,15 @@ msgstr "Aberto"
msgid "Options"
msgstr "Opções"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "Proprietário"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -701,6 +827,9 @@ msgstr "Agendamento da Pipeline"
msgid "Pipeline Schedules"
msgstr "Agendamentos da Pipeline"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Falhou:"
@@ -764,6 +893,15 @@ msgstr ""
msgid "Pipelines charts"
msgstr "Gráficos de pipelines"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "todos"
@@ -776,6 +914,12 @@ msgstr "com etapa"
msgid "Pipeline|with stages"
msgstr "com etapas"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -812,6 +956,9 @@ msgstr "Exportação do projeto iniciada. Um link para baixá-la será enviado p
msgid "Project home"
msgstr "Página inicial do projeto"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -836,6 +983,9 @@ msgstr "Etapa"
msgid "ProjectNetworkGraph|Graph"
msgstr "Ãrvore"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -896,6 +1046,9 @@ msgstr "Reverter este commit"
msgid "Revert this merge request"
msgstr "Reverter esse merge request"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Salvar agendamento da pipeline"
@@ -920,6 +1073,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Selecionar branch de destino"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Defina uma senha para sua conta para aceitar ou entregar código via %{protocol}."
@@ -935,14 +1091,23 @@ msgstr "Configurar implantação automática"
msgid "SetPasswordToCloneLink|set a password"
msgstr "defina uma senha"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Mostrando %d evento"
msgstr[1] "Mostrando %d eventos"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Código-fonte"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1219,6 +1384,9 @@ msgstr "Precisa visualizar os dados? Solicite acesso ao administrador."
msgid "We don't have enough data to show this stage."
msgstr "Esta etapa não possui dados suficientes para exibição."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Remover Requisição de Acesso"
@@ -1284,4 +1452,5 @@ msgstr "emails de notificação"
msgid "parent"
msgid_plural "parents"
msgstr[0] "pai"
-msgstr[1] "pais" \ No newline at end of file
+msgstr[1] "pais"
+
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index 926995d1f91..96e6c8a8d3f 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:41-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -61,9 +61,18 @@ msgstr "Графики отноÑительно непрерывной интеÐ
msgid "About auto deploy"
msgstr "ÐвтоматичеÑкое развертывание"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивный"
@@ -88,6 +97,12 @@ msgstr "Добавить каталог"
msgid "All"
msgstr ""
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Ðрхивный проект! Репозиторий доÑтупен только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ"
@@ -95,7 +110,7 @@ msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Ð’Ñ‹ дейÑтвительно хотите удалить Ñто раÑпиÑание конвейера?"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "Ð’Ñ‹ уверены, что Ð’Ñ‹ хотите отменить Ваши изменениÑ?"
msgid "Are you sure you want to reset registration token?"
msgstr ""
@@ -104,11 +119,68 @@ msgid "Are you sure you want to reset the health check token?"
msgstr ""
msgid "Are you sure?"
-msgstr ""
+msgstr "Вы уверены?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Приложить файл через drag &amp; drop или %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Ветка"
@@ -142,6 +214,9 @@ msgstr "ПроÑмотр файлов"
msgid "ByAuthor|by"
msgstr "по автору"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "ÐаÑтройка CI"
@@ -149,7 +224,7 @@ msgid "Cancel"
msgstr "Отмена"
msgid "Cancel edit"
-msgstr ""
+msgstr "Отменить редактирование"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "Выбрать в ветке"
@@ -169,6 +244,9 @@ msgstr "Журнал изменений"
msgid "Charts"
msgstr "Диаграммы"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Подобрать в Ñтом коммите"
@@ -230,7 +308,7 @@ msgid "CiStatus|running"
msgstr "выполнÑетÑÑ"
msgid "Comments"
-msgstr ""
+msgstr "Комментарии"
msgid "Commit"
msgid_plural "Commits"
@@ -265,12 +343,18 @@ msgstr "ФикÑировано"
msgid "Compare"
msgstr "Сравнить"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "РуководÑтво учаÑтника"
msgid "Contributors"
msgstr "УчаÑтники"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Копировать URL в буфер обмена"
@@ -281,7 +365,7 @@ msgid "Create New Directory"
msgstr "Создать директорию"
msgid "Create a new branch"
-msgstr ""
+msgstr "Создать новую ветку"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Создать личный токен на аккаунте Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ отправки через %{protocol}."
@@ -358,6 +442,9 @@ msgstr[0] "РазмеÑтить"
msgstr[1] "Размещение"
msgstr[2] "Размещение"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "ОпиÑание"
@@ -368,7 +455,7 @@ msgid "Directory name"
msgstr "Каталог"
msgid "Discard changes"
-msgstr ""
+msgstr "Отменить изменениÑ"
msgid "Don't show again"
msgstr "Ðе показывать Ñнова"
@@ -406,6 +493,9 @@ msgstr "Редактировать"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Изменить раÑпиÑание конвейера %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -472,11 +562,17 @@ msgstr "От ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹ до Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð
msgid "From merge request merge until deploy to production"
msgstr "От запроÑа на ÑлиÑние до Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð² рабочей Ñреде"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
msgid "GitLab Runner section"
-msgstr ""
+msgstr "Ð¡ÐµÐºÑ†Ð¸Ñ Gitlab Runner"
msgid "Go to your fork"
msgstr "Перейти к вашему форку"
@@ -484,6 +580,9 @@ msgstr "Перейти к вашему форку"
msgid "GoToYourFork|Fork"
msgstr "Форк"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -505,6 +604,9 @@ msgstr ""
msgid "Home"
msgstr "ГлавнаÑ"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ОчиÑтка уÑпешно запущена"
@@ -512,7 +614,7 @@ msgid "Import repository"
msgstr "Импорт репозиториÑ"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "УÑтановите Gitlab Runner ÑовмеÑтимый Ñ Gitlab CI"
msgid "Interval Pattern"
msgstr "Шаблон интервала"
@@ -523,14 +625,8 @@ msgstr "Внедрение Цикла Ðналитик"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "Работы за прошлый меÑÑц"
-
-msgid "Jobs for last week"
-msgstr "Работы за прошлую неделю"
-
-msgid "Jobs for last year"
-msgstr "Работы за прошлый год"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Отключено"
@@ -538,6 +634,9 @@ msgstr "Отключено"
msgid "LFSStatus|Enabled"
msgstr "Включено"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "ПоÑледний %d день"
@@ -571,21 +670,39 @@ msgstr "Покинуть группу"
msgid "Leave project"
msgstr "Покинуть проект"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Ограничение %d ÑобытиÑ"
msgstr[1] "Ограничение %d Ñобытий"
msgstr[2] "Ограничение %d Ñобытий"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Среднее"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "добавить ключ SSH"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -688,6 +805,9 @@ msgstr "УчаÑтие"
msgid "NotificationLevel|Watch"
msgstr "ОтÑлеживать"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Фильтр"
@@ -697,9 +817,15 @@ msgstr "Открыто"
msgid "Options"
msgstr "ÐаÑтройки"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "Владелец"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "Конвейер"
@@ -712,6 +838,9 @@ msgstr "РаÑпиÑание конвейера"
msgid "Pipeline Schedules"
msgstr "РаÑпиÑÐ°Ð½Ð¸Ñ ÐºÐ¾Ð½Ð²ÐµÐ¹ÐµÑ€Ð¾Ð²"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Ðеудача:"
@@ -775,6 +904,15 @@ msgstr "Конвейер"
msgid "Pipelines charts"
msgstr "Диаграмма конвейера"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "вÑе"
@@ -787,6 +925,12 @@ msgstr "Ñо Ñтадией"
msgid "Pipeline|with stages"
msgstr "Ñо ÑтадиÑми"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -823,6 +967,9 @@ msgstr "Ðачат ÑкÑпорт проекта. СÑылка Ð´Ð»Ñ Ñкачи
msgid "Project home"
msgstr "ДомашнÑÑ Ñтраница"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -847,6 +994,9 @@ msgstr "Этап"
msgid "ProjectNetworkGraph|Graph"
msgstr "Граф"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -907,6 +1057,9 @@ msgstr "Отменить Ñто изменение"
msgid "Revert this merge request"
msgstr "Отменить Ñтот Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Сохранить раÑпиÑание конвейра"
@@ -931,6 +1084,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Выбор целевой ветки"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "УÑтановите пароль в Ñвоем аккаунте, чтобы отправлÑÑ‚ÑŒ или получать код через %{protocol}."
@@ -946,15 +1102,24 @@ msgstr "ÐаÑтройка автоматичеÑкого развертыван
msgid "SetPasswordToCloneLink|set a password"
msgstr "уÑтановить пароль"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Показано %d Ñобытие"
msgstr[1] "Показано %d Ñобытий"
msgstr[2] "Показано %d Ñобытий"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "ИÑходный код"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1234,6 +1399,9 @@ msgstr "Хотите увидеть данные? ОбратитеÑÑŒ к адм
msgid "We don't have enough data to show this stage."
msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñтапу отÑутÑтвует."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
@@ -1301,4 +1469,5 @@ msgid "parent"
msgid_plural "parents"
msgstr[0] "иÑточник"
msgstr[1] "иÑточники"
-msgstr[2] "иÑточники" \ No newline at end of file
+msgstr[2] "иÑточники"
+
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 5f9f087ff64..4d24140f3dc 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:49-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
@@ -61,9 +61,18 @@ msgstr "Це набір графічних елементів Ð´Ð»Ñ Ð±ÐµÐ·Ð¿ÐµÑ
msgid "About auto deploy"
msgstr "Про авто розгортаннÑ"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
+msgid "Account"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивний"
@@ -88,6 +97,12 @@ msgstr "Додати новий каталог"
msgid "All"
msgstr "Ð’ÑÑ–"
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
msgid "Archived project! Repository is read-only"
msgstr "Заархівований проект! Репозиторій доÑтупний лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
@@ -109,6 +124,63 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Прикріпити файл за допомогою перетÑÐ³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Гілка"
@@ -142,6 +214,9 @@ msgstr "ПереглÑд файлів"
msgid "ByAuthor|by"
msgstr "від"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI"
@@ -169,6 +244,9 @@ msgstr "СпиÑок змін (Changelog)"
msgid "Charts"
msgstr "Графіки"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "Cherry-pick в цьому комміті"
@@ -265,12 +343,18 @@ msgstr "Комміт від"
msgid "Compare"
msgstr "ПорівнÑти"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "Керівництво контриб’юторів"
msgid "Contributors"
msgstr "Контриб’ютори"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "Скопіювати URL в буфер обміну"
@@ -358,6 +442,9 @@ msgstr[0] "РозгортаннÑ"
msgstr[1] "РозгортаннÑ"
msgstr[2] "Розгортань"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "ОпиÑ"
@@ -406,6 +493,9 @@ msgstr "Редагувати"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Редагувати Розклад Конвеєра %{id}"
+msgid "Emails"
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -472,6 +562,12 @@ msgstr "З моменту ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ до розгорÑ
msgid "From merge request merge until deploy to production"
msgstr "З об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° ПРОД"
+msgid "GPG Keys"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
@@ -484,6 +580,9 @@ msgstr "Перейти до вашого форку"
msgid "GoToYourFork|Fork"
msgstr "Форк"
+msgid "Group overview"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -505,6 +604,9 @@ msgstr ""
msgid "Home"
msgstr "Головна"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ ÑƒÑпішно розпочато"
@@ -523,14 +625,8 @@ msgstr "ПредÑтавлÑємо аналітику циклу"
msgid "Issue events"
msgstr ""
-msgid "Jobs for last month"
-msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній міÑÑць"
-
-msgid "Jobs for last week"
-msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній тиждень"
-
-msgid "Jobs for last year"
-msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній рік"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "Вимкнено"
@@ -538,6 +634,9 @@ msgstr "Вимкнено"
msgid "LFSStatus|Enabled"
msgstr "Увімкнено"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "ОÑтанній %d день"
@@ -571,21 +670,39 @@ msgstr "Залишити групу"
msgid "Leave project"
msgstr "Залишити проект"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d події"
msgstr[1] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій"
msgstr[2] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "Медіана"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Messages"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "не додаÑте SSH ключ"
+msgid "Monitoring"
+msgstr ""
+
msgid "More information is available|here"
msgstr ""
@@ -688,6 +805,9 @@ msgstr "Берете учаÑÑ‚ÑŒ"
msgid "NotificationLevel|Watch"
msgstr "ВідÑтежувати"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "Фільтр"
@@ -697,9 +817,15 @@ msgstr "Відкрито"
msgid "Options"
msgstr "Параметри"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "ВлаÑник"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "Конвеєр"
@@ -712,6 +838,9 @@ msgstr "Розклад Конвеєра"
msgid "Pipeline Schedules"
msgstr "Розклади Конвеєрів"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Ðе вдалоÑÑ:"
@@ -775,6 +904,15 @@ msgstr "Конвеєри"
msgid "Pipelines charts"
msgstr "Чарти Конвеєрів"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "вÑÑ–"
@@ -787,6 +925,12 @@ msgstr "зі Ñтадією"
msgid "Pipeline|with stages"
msgstr "зі ÑтадіÑми"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
msgid "Project"
msgstr ""
@@ -823,6 +967,9 @@ msgstr "Розпочато екÑпорт проекту. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð
msgid "Project home"
msgstr "Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка проекту"
+msgid "Project overview"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -847,6 +994,9 @@ msgstr "Етап"
msgid "ProjectNetworkGraph|Graph"
msgstr "ІÑторіÑ"
+msgid "Push Rules"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -907,6 +1057,9 @@ msgstr "СкаÑувати цей комміт"
msgid "Revert this merge request"
msgstr "СкаÑувати цей запит на злиттÑ"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "Зберегти Розклад Конвеєра"
@@ -931,6 +1084,9 @@ msgstr ""
msgid "Select target branch"
msgstr "Вибір цільової гілки"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Ð’Ñтановіть пароль Ñвого облікового запиÑу, щоб відправлÑти або отримувати код через %{protocol}."
@@ -946,15 +1102,24 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ðµ розгортаннÑ"
msgid "SetPasswordToCloneLink|set a password"
msgstr "вÑтановити пароль"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Показано %d подію"
msgstr[1] "Показано %d події"
msgstr[2] "Показано %d подій"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "Код"
+msgid "Spam Logs"
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -1234,6 +1399,9 @@ msgstr "Хочете побачити дані? Будь лаÑка, попроÑ
msgid "We don't have enough data to show this stage."
msgstr "Ми не маємо доÑтатньо даних Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ цього етапу."
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "СкаÑувати запит доÑтупу"
@@ -1301,4 +1469,5 @@ msgid "parent"
msgid_plural "parents"
msgstr[0] "джерело"
msgstr[1] "джерела"
-msgstr[2] "джерел" \ No newline at end of file
+msgstr[2] "джерел"
+
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index eb607acf1f4..47de28209df 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:59-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -28,20 +28,20 @@ msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
+msgstr "已失败 %{number_of_failures} 次/最多å…许失败失败 %{maximum_failures} 次,GitLab 将继续é‡è¯•ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
+msgstr "已失败 %{number_of_failures} 次/最多å…许失败 %{maximum_failures} 次,GitLab 将在 %{number_of_seconds} 秒åŽé‡è¯•ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
-msgstr ""
+msgstr "已失败 %{number_of_failures} 次/最多å…许失败 %{maximum_failures} 次,GitLab ä¸ä¼šç»§ç»­è‡ªåŠ¨é‡è¯•ã€‚请在问题解决åŽé‡ç½®å­˜å‚¨å¥åº·ä¿¡æ¯ã€‚"
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
-msgstr[0] ""
+msgstr[0] "%{storage_name}:已 %{failed_attempts} 次å°è¯•è®¿é—®å­˜å‚¨å¤±è´¥ï¼š"
msgid "(checkout the %{link} for information on how to install it)."
-msgstr ""
+msgstr "(如需了解更多的安装信æ¯ï¼Œè¯·æŸ¥çœ‹ %{link})"
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -53,7 +53,16 @@ msgstr "æŒç»­é›†æˆæ•°æ®å›¾"
msgid "About auto deploy"
msgstr "关于自动部署"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr "为方便修å¤æŒ‚载问题,访问故障存储已被暂时ç¦ç”¨ã€‚在问题解决åŽè¯·é‡ç½®å­˜å‚¨å¥åº·ä¿¡æ¯ï¼Œä»¥å…许å†æ¬¡è®¿é—®ã€‚"
+
+msgid "Account"
msgstr ""
msgid "Active"
@@ -78,6 +87,12 @@ msgid "Add new directory"
msgstr "添加目录"
msgid "All"
+msgstr "全部"
+
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
msgstr ""
msgid "Archived project! Repository is read-only"
@@ -87,20 +102,77 @@ msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "确定è¦åˆ é™¤æ­¤æµæ°´çº¿è®¡åˆ’å—?"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "确定è¦æ”¾å¼ƒä¿®æ”¹å—?"
msgid "Are you sure you want to reset registration token?"
-msgstr ""
+msgstr "确定è¦é‡ç½®æ³¨å†Œä»¤ç‰Œå—?"
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "确定è¦é‡ç½®å¥åº·æ£€æŸ¥ä»¤ç‰Œå—?"
msgid "Are you sure?"
-msgstr ""
+msgstr "确定å—?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "拖放文件到此处或者 %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "分支"
@@ -132,6 +204,9 @@ msgstr "æµè§ˆæ–‡ä»¶"
msgid "ByAuthor|by"
msgstr "作者:"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "CI é…ç½®"
@@ -139,7 +214,7 @@ msgid "Cancel"
msgstr "å–消"
msgid "Cancel edit"
-msgstr ""
+msgstr "å–消编辑"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "选择分支"
@@ -159,6 +234,9 @@ msgstr "更新日志"
msgid "Charts"
msgstr "统计图"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "优选此æ交"
@@ -220,7 +298,7 @@ msgid "CiStatus|running"
msgstr "è¿è¡Œä¸­"
msgid "Comments"
-msgstr ""
+msgstr "评论"
msgid "Commit"
msgid_plural "Commits"
@@ -253,12 +331,18 @@ msgstr "æ交者:"
msgid "Compare"
msgstr "比较"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "贡献指å—"
msgid "Contributors"
msgstr "贡献者"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "å¤åˆ¶ URL 到剪贴æ¿"
@@ -269,7 +353,7 @@ msgid "Create New Directory"
msgstr "创建新目录"
msgid "Create a new branch"
-msgstr ""
+msgstr "创建一个新分支"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ %{protocol} æ¥æ‹‰å–或推é€ã€‚"
@@ -344,17 +428,20 @@ msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "部署"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "æè¿°"
msgid "Details"
-msgstr ""
+msgstr "详情"
msgid "Directory name"
msgstr "目录å称"
msgid "Discard changes"
-msgstr ""
+msgstr "放弃更改"
msgid "Don't show again"
msgstr "ä¸å†æ˜¾ç¤º"
@@ -392,23 +479,26 @@ msgstr "编辑"
msgid "Edit Pipeline Schedule %{id}"
msgstr "编辑 %{id} æµæ°´çº¿è®¡åˆ’"
-msgid "EventFilterBy|Filter by all"
+msgid "Emails"
msgstr ""
+msgid "EventFilterBy|Filter by all"
+msgstr "全部"
+
msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "åªæ˜¾ç¤ºè¯„论事件"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "åªæ˜¾ç¤ºè®®é¢˜äº‹ä»¶"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "åªæ˜¾ç¤ºåˆå¹¶äº‹ä»¶"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "åªæ˜¾ç¤ºæŽ¨é€äº‹ä»¶"
msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "åªæ˜¾ç¤ºå›¢é˜Ÿäº‹ä»¶"
msgid "Every day (at 4:00am)"
msgstr "æ¯æ—¥æ‰§è¡Œï¼ˆå‡Œæ™¨ 4 点)"
@@ -456,39 +546,51 @@ msgstr "从创建议题到部署至生产环境"
msgid "From merge request merge until deploy to production"
msgstr "从åˆå¹¶è¯·æ±‚被åˆå¹¶åŽåˆ°éƒ¨ç½²è‡³ç”Ÿäº§çŽ¯å¢ƒ"
-msgid "Git storage health information has been reset"
+msgid "GPG Keys"
msgstr ""
-msgid "GitLab Runner section"
+msgid "Geo Nodes"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr "Git 存储å¥åº·ä¿¡æ¯å·²é‡ç½®"
+
+msgid "GitLab Runner section"
+msgstr "GitLab Runner"
+
msgid "Go to your fork"
msgstr "跳转到派生项目"
msgid "GoToYourFork|Fork"
msgstr "跳转到派生项目"
-msgid "Health Check"
+msgid "Group overview"
msgstr ""
+msgid "Health Check"
+msgstr "å¥åº·æ£€æŸ¥"
+
msgid "Health information can be retrieved from the following endpoints. More information is available"
-msgstr ""
+msgstr "å¥åº·ä¿¡æ¯å¯ä»¥ä»Žä»¥ä¸‹API路径获å–。如需了解更多信æ¯ï¼Œè¯·æŸ¥çœ‹"
msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "访问令牌是"
msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "å¥åº·"
msgid "HealthCheck|No Health Problems Detected"
-msgstr ""
+msgstr "没有检测到å¥åº·é—®é¢˜"
msgid "HealthCheck|Unhealthy"
-msgstr ""
+msgstr "éžå¥åº·"
msgid "Home"
msgstr "首页"
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已开始维护"
@@ -496,7 +598,7 @@ msgid "Import repository"
msgstr "导入存储库"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "安装一个与 GitLab CI 兼容的 Runner"
msgid "Interval Pattern"
msgstr "循环周期"
@@ -505,16 +607,10 @@ msgid "Introducing Cycle Analytics"
msgstr "周期分æžç®€ä»‹"
msgid "Issue events"
-msgstr ""
-
-msgid "Jobs for last month"
-msgstr "上个月的作业"
+msgstr "议题事件"
-msgid "Jobs for last week"
-msgstr "上个星期的作业"
-
-msgid "Jobs for last year"
-msgstr "去年的作业"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -522,6 +618,9 @@ msgstr "åœç”¨"
msgid "LFSStatus|Enabled"
msgstr "å¯ç”¨"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "最近 %d 天"
@@ -536,10 +635,10 @@ msgid "Last commit"
msgstr "最åŽæ交"
msgid "LastPushEvent|You pushed to"
-msgstr ""
+msgstr "您推é€äº†"
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "于"
msgid "Learn more in the"
msgstr "了解更多"
@@ -553,22 +652,40 @@ msgstr "退出群组"
msgid "Leave project"
msgstr "退出项目"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "最多显示 %d 个事件"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "中ä½æ•°"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
+msgstr "åˆå¹¶äº‹ä»¶"
+
+msgid "Messages"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "新建 SSH 公钥"
-msgid "More information is available|here"
+msgid "Monitoring"
msgstr ""
+msgid "More information is available|here"
+msgstr "帮助文档"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "新建议题"
@@ -666,6 +783,9 @@ msgstr "å‚与"
msgid "NotificationLevel|Watch"
msgstr "关注"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "筛选"
@@ -675,9 +795,15 @@ msgstr "开始于"
msgid "Options"
msgstr "æ“作"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "所有者"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "æµæ°´çº¿"
@@ -690,6 +816,9 @@ msgstr "æµæ°´çº¿è®¡åˆ’"
msgid "Pipeline Schedules"
msgstr "æµæ°´çº¿è®¡åˆ’"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失败:"
@@ -753,6 +882,15 @@ msgstr "æµæ°´çº¿"
msgid "Pipelines charts"
msgstr "æµæ°´çº¿ç»Ÿè®¡å›¾"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "所有"
@@ -765,9 +903,15 @@ msgstr "于阶段"
msgid "Pipeline|with stages"
msgstr "于阶段"
-msgid "Project"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
msgstr ""
+msgid "Project"
+msgstr "项目"
+
msgid "Project '%{project_name}' queued for deletion."
msgstr "项目 '%{project_name}' 已进入删除队列。"
@@ -784,7 +928,7 @@ msgid "Project access must be granted explicitly to each user."
msgstr "项目访问æƒé™å¿…须明确授æƒç»™æ¯ä¸ªç”¨æˆ·ã€‚"
msgid "Project details"
-msgstr ""
+msgstr "项目详情"
msgid "Project export could not be deleted."
msgstr "无法删除项目导出。"
@@ -801,9 +945,12 @@ msgstr "项目导出已开始。下载链接将通过电å­é‚®ä»¶å‘é€ã€‚"
msgid "Project home"
msgstr "项目首页"
-msgid "ProjectActivityRSS|Subscribe"
+msgid "Project overview"
msgstr ""
+msgid "ProjectActivityRSS|Subscribe"
+msgstr "订阅"
+
msgid "ProjectFeature|Disabled"
msgstr "åœç”¨"
@@ -825,9 +972,12 @@ msgstr "阶段"
msgid "ProjectNetworkGraph|Graph"
msgstr "分支图"
-msgid "Push events"
+msgid "Push Rules"
msgstr ""
+msgid "Push events"
+msgstr "推é€äº‹ä»¶"
+
msgid "Read more"
msgstr "了解更多"
@@ -865,19 +1015,19 @@ msgid "Remove project"
msgstr "删除项目"
msgid "Repository"
-msgstr ""
+msgstr "存储库"
msgid "Request Access"
msgstr "申请æƒé™"
msgid "Reset git storage health information"
-msgstr ""
+msgstr "é‡ç½® Git 存储的å¥åº·ä¿¡æ¯"
msgid "Reset health check access token"
-msgstr ""
+msgstr "é‡ç½®å¥åº·æ£€æŸ¥è®¿é—®ä»¤ç‰Œ"
msgid "Reset runners registration token"
-msgstr ""
+msgstr "é‡ç½® Runner 注册令牌"
msgid "Revert this commit"
msgstr "还原此æ交"
@@ -885,6 +1035,9 @@ msgstr "还原此æ交"
msgid "Revert this merge request"
msgstr "还原此åˆå¹¶è¯·æ±‚"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "ä¿å­˜æµæ°´çº¿è®¡åˆ’"
@@ -904,11 +1057,14 @@ msgid "Select a timezone"
msgstr "选择时区"
msgid "Select existing branch"
-msgstr ""
+msgstr "选择现有分支"
msgid "Select target branch"
msgstr "选择目标分支"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "为账å·åˆ›å»ºä¸€ä¸ªç”¨äºŽæŽ¨é€æˆ–拉å–çš„ %{protocol} 密ç ã€‚"
@@ -924,16 +1080,25 @@ msgstr "设置自动部署"
msgid "SetPasswordToCloneLink|set a password"
msgstr "设置密ç "
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "显示 %d 个事件"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "æºä»£ç "
-msgid "Specify the following URL during the Runner setup:"
+msgid "Spam Logs"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr "在 Runner 设置时指定以下 URL:"
+
msgid "StarProject|Star"
msgstr "星标"
@@ -941,7 +1106,7 @@ msgid "Start a %{new_merge_request} with these changes"
msgstr "由此更改 %{new_merge_request}"
msgid "Start the Runner!"
-msgstr ""
+msgstr "å¯åŠ¨ Runner!"
msgid "Switch branch/tag"
msgstr "切æ¢åˆ†æ”¯/标签"
@@ -957,7 +1122,7 @@ msgid "Target Branch"
msgstr "目标分支"
msgid "Team"
-msgstr ""
+msgstr "团队"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "ç¼–ç é˜¶æ®µæ¦‚述了从第一次æ交到创建åˆå¹¶è¯·æ±‚的时间。创建第一个åˆå¹¶è¯·æ±‚åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ åˆ°æ­¤å¤„。"
@@ -1008,7 +1173,7 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "中ä½æ•°æ˜¯ä¸€ä¸ªæ•°åˆ—中最中间的值。例如在 3ã€5ã€9 之间,中ä½æ•°æ˜¯ 5。在 3ã€5ã€7ã€8 之间,中ä½æ•°æ˜¯ (5 + 7)/ 2 = 6。"
msgid "There are problems accessing Git storage: "
-msgstr ""
+msgstr "访问 Git 存储时出现问题:"
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "在创建一个空的存储库或导入现有存储库之å‰ï¼Œå°†æ— æ³•æŽ¨é€ä»£ç ã€‚"
@@ -1178,7 +1343,7 @@ msgid "UploadLink|click to upload"
msgstr "点击上传"
msgid "Use the following registration token during setup:"
-msgstr ""
+msgstr "在安装过程中使用以下注册令牌:"
msgid "Use your global notification setting"
msgstr "使用全局通知设置"
@@ -1204,6 +1369,9 @@ msgstr "æƒé™ä¸è¶³ã€‚如需查看相关数æ®ï¼Œè¯·å‘管ç†å‘˜ç”³è¯·æƒé™ã€‚
msgid "We don't have enough data to show this stage."
msgstr "该阶段的数æ®ä¸è¶³ï¼Œæ— æ³•æ˜¾ç¤ºã€‚"
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "å–消æƒé™ç”³è¯·"
@@ -1267,4 +1435,5 @@ msgstr "通知邮件"
msgid "parent"
msgid_plural "parents"
-msgstr[0] "父级" \ No newline at end of file
+msgstr[0] "父级"
+
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 74c7b464091..fee0d661c7a 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:59-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:21-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Chinese Traditional, Hong Kong\n"
"Language: zh_HK\n"
@@ -28,20 +28,20 @@ msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
+msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLab å°‡é‡è©¦ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
+msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLab 將在 %{number_of_seconds} 秒後é‡è©¦ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
-msgstr ""
+msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLabä¸æœƒé‡è©¦ã€‚當å•é¡Œè§£æ±ºæ™‚é‡ç½®å­˜å„²ä¿¡æ¯ã€‚"
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
-msgstr[0] ""
+msgstr[0] "%{storage_name}:已訪å•æ­¤ä¸»æ©Ÿå¤±æ•— %{failed_attempts} 次"
msgid "(checkout the %{link} for information on how to install it)."
-msgstr ""
+msgstr "(想了解更多的安è£è¨Šæ¯è«‹æŸ¥çœ‹ %{link})"
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -53,7 +53,16 @@ msgstr "相關æŒçºŒé›†æˆçš„圖åƒé›†åˆ"
msgid "About auto deploy"
msgstr "關於自動部署"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr "å› æ¢å¾©å®‰è£ï¼Œè¨ªå•æ•…障存儲已被暫時ç¦ç”¨ã€‚在å•é¡Œè§£æ±ºå¾Œå°‡é‡ç½®å­˜å„²ä¿¡æ¯ï¼Œä»¥ä¾¿å†æ¬¡è¨ªå•ã€‚"
+
+msgid "Account"
msgstr ""
msgid "Active"
@@ -78,6 +87,12 @@ msgid "Add new directory"
msgstr "添加新目錄"
msgid "All"
+msgstr "全部"
+
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
msgstr ""
msgid "Archived project! Repository is read-only"
@@ -87,20 +102,77 @@ msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "確定è¦åˆªé™¤æ­¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "確定è¦æ”¾æ£„修改嗎?"
msgid "Are you sure you want to reset registration token?"
-msgstr ""
+msgstr "確定è¦é‡ç½®è¨»å†Šä»¤ç‰Œå—Žï¼Ÿ"
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "確定è¦é‡ç½®å¥åº·æª¢æŸ¥ä»¤ç‰Œå—Žï¼Ÿ"
msgid "Are you sure?"
-msgstr ""
+msgstr "確定嗎?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "拖放文件到此處或者 %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "分支"
@@ -132,6 +204,9 @@ msgstr "ç€è¦½æ–‡ä»¶"
msgid "ByAuthor|by"
msgstr "作者:"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "CI é…ç½®"
@@ -139,7 +214,7 @@ msgid "Cancel"
msgstr "å–消"
msgid "Cancel edit"
-msgstr ""
+msgstr "å–消编辑"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "挑é¸åˆ°åˆ†æ”¯"
@@ -159,6 +234,9 @@ msgstr "更新日誌"
msgid "Charts"
msgstr "統計圖"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "優é¸æ­¤æ交"
@@ -220,7 +298,7 @@ msgid "CiStatus|running"
msgstr "é‹è¡Œä¸­"
msgid "Comments"
-msgstr ""
+msgstr "è©•è«– (Comment)"
msgid "Commit"
msgid_plural "Commits"
@@ -253,12 +331,18 @@ msgstr "æ交者:"
msgid "Compare"
msgstr "比較"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "è²¢ç»æŒ‡å—"
msgid "Contributors"
msgstr "è²¢ç»è€…"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "複製URL到剪貼æ¿"
@@ -269,7 +353,7 @@ msgid "Create New Directory"
msgstr "創建新目錄"
msgid "Create a new branch"
-msgstr ""
+msgstr "創建壹個新分支 (branch)"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在帳戶上創建個人訪å•ä»¤ç‰Œï¼Œä»¥é€šéŽ %{protocol} 來拉å–或推é€ã€‚"
@@ -344,17 +428,20 @@ msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "部署"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "æè¿°"
msgid "Details"
-msgstr ""
+msgstr "詳情"
msgid "Directory name"
msgstr "目錄å稱"
msgid "Discard changes"
-msgstr ""
+msgstr "放棄更改"
msgid "Don't show again"
msgstr "ä¸å†é¡¯ç¤º"
@@ -392,23 +479,26 @@ msgstr "編輯"
msgid "Edit Pipeline Schedule %{id}"
msgstr "編輯 %{id} æµæ°´ç·šè¨ˆåŠƒ"
-msgid "EventFilterBy|Filter by all"
+msgid "Emails"
msgstr ""
+msgid "EventFilterBy|Filter by all"
+msgstr "全部"
+
msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "按評論 (comment) éŽæ¿¾"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "按議題事件 (issue event) éŽæ¿¾"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "按åˆä½µäº‹ä»¶ (merge event) éŽæ¿¾"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "按推é€äº‹ä»¶ (push event) éŽæ¿¾"
msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "按團隊éŽæ¿¾"
msgid "Every day (at 4:00am)"
msgstr "æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨ 4 點)"
@@ -456,39 +546,51 @@ msgstr "從創建議題到部署到生產環境"
msgid "From merge request merge until deploy to production"
msgstr "從åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²è‡³ç”Ÿç”¢ç’°å¢ƒ"
-msgid "Git storage health information has been reset"
+msgid "GPG Keys"
msgstr ""
-msgid "GitLab Runner section"
+msgid "Geo Nodes"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr "Git 存儲å¥åº·ä¿¡æ¯å·²é‡ç½®"
+
+msgid "GitLab Runner section"
+msgstr "GitLab Runner 介紹"
+
msgid "Go to your fork"
msgstr "跳轉到派生項目"
msgid "GoToYourFork|Fork"
msgstr "跳轉到派生項目"
-msgid "Health Check"
+msgid "Group overview"
msgstr ""
+msgid "Health Check"
+msgstr "å¥åº·æª¢æŸ¥ (Health Check)"
+
msgid "Health information can be retrieved from the following endpoints. More information is available"
-msgstr ""
+msgstr "å¥åº·ä¿¡æ¯å¯ä»¥å¾žä»¥ä¸‹ç«¯é»žæª¢ç´¢ã€‚想了解更多信æ¯è«‹æŸ¥çœ‹"
msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "訪å•ä»¤ç‰Œæ˜¯"
msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "å¥åº·"
msgid "HealthCheck|No Health Problems Detected"
-msgstr ""
+msgstr "沒有檢測到å¥åº·å•é¡Œ"
msgid "HealthCheck|Unhealthy"
-msgstr ""
+msgstr "ä¸è‰¯"
msgid "Home"
msgstr "首é "
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已開始維護"
@@ -496,7 +598,7 @@ msgid "Import repository"
msgstr "導入存儲庫"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "安è£å£¹å€‹èˆ‡ GitLab CI 兼容的 Runner"
msgid "Interval Pattern"
msgstr "循環週期"
@@ -505,16 +607,10 @@ msgid "Introducing Cycle Analytics"
msgstr "週期分æžç°¡ä»‹"
msgid "Issue events"
-msgstr ""
-
-msgid "Jobs for last month"
-msgstr "上個月的作業"
+msgstr "議題事件 (issue event)"
-msgid "Jobs for last week"
-msgstr "上個星期的作業"
-
-msgid "Jobs for last year"
-msgstr "去年的作業"
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -522,6 +618,9 @@ msgstr "åœç”¨"
msgid "LFSStatus|Enabled"
msgstr "啟用"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "最近 %d 天"
@@ -536,10 +635,10 @@ msgid "Last commit"
msgstr "最後æ交"
msgid "LastPushEvent|You pushed to"
-msgstr ""
+msgstr "您推é€äº†"
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "在"
msgid "Learn more in the"
msgstr "了解更多"
@@ -553,22 +652,40 @@ msgstr "退出群組"
msgid "Leave project"
msgstr "退出項目"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "最多顯示 %d 個事件"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "中ä½æ•¸"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
+msgstr "åˆä½µäº‹ä»¶ (merge event)"
+
+msgid "Messages"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "添加壹個 SSH 公鑰"
-msgid "More information is available|here"
+msgid "Monitoring"
msgstr ""
+msgid "More information is available|here"
+msgstr "幫助文檔"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "新建議題"
@@ -666,6 +783,9 @@ msgstr "åƒèˆ‡"
msgid "NotificationLevel|Watch"
msgstr "關注"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "篩é¸"
@@ -675,9 +795,15 @@ msgstr "開始於"
msgid "Options"
msgstr "æ“作"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "所有者"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "æµæ°´ç·š"
@@ -690,6 +816,9 @@ msgstr "æµæ°´ç·šè¨ˆåŠƒ"
msgid "Pipeline Schedules"
msgstr "æµæ°´ç·šè¨ˆåŠƒ"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失敗:"
@@ -753,6 +882,15 @@ msgstr "æµæ°´ç·š"
msgid "Pipelines charts"
msgstr "æµæ°´ç·šåœ–表"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "所有"
@@ -765,9 +903,15 @@ msgstr "於階段"
msgid "Pipeline|with stages"
msgstr "於階段"
-msgid "Project"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
msgstr ""
+msgid "Project"
+msgstr "專案"
+
msgid "Project '%{project_name}' queued for deletion."
msgstr "項目 '%{project_name}' 已進入刪除隊列。"
@@ -784,7 +928,7 @@ msgid "Project access must be granted explicitly to each user."
msgstr "項目訪å•æ¬Šé™å¿…須明確授權給æ¯å€‹ç”¨æˆ¶ã€‚"
msgid "Project details"
-msgstr ""
+msgstr "專案詳情"
msgid "Project export could not be deleted."
msgstr "無法刪除項目導出。"
@@ -801,9 +945,12 @@ msgstr "項目導出已開始。下載éˆæŽ¥å°‡é€šéŽé›»å­éƒµä»¶ç™¼é€ã€‚"
msgid "Project home"
msgstr "項目首é "
-msgid "ProjectActivityRSS|Subscribe"
+msgid "Project overview"
msgstr ""
+msgid "ProjectActivityRSS|Subscribe"
+msgstr "訂閱"
+
msgid "ProjectFeature|Disabled"
msgstr "åœç”¨"
@@ -825,9 +972,12 @@ msgstr "階段"
msgid "ProjectNetworkGraph|Graph"
msgstr "分支圖"
-msgid "Push events"
+msgid "Push Rules"
msgstr ""
+msgid "Push events"
+msgstr "推é€äº‹ä»¶ (push event) "
+
msgid "Read more"
msgstr "了解更多"
@@ -865,19 +1015,19 @@ msgid "Remove project"
msgstr "刪除項目"
msgid "Repository"
-msgstr ""
+msgstr "存儲庫"
msgid "Request Access"
msgstr "申請權é™"
msgid "Reset git storage health information"
-msgstr ""
+msgstr "é‡ç½® Git 存儲的å¥åº·ä¿¡æ¯"
msgid "Reset health check access token"
-msgstr ""
+msgstr "é‡ç½®å¥åº·æª¢æŸ¥è¨ªå•ä»¤ç‰Œ"
msgid "Reset runners registration token"
-msgstr ""
+msgstr "é‡ç½® Runner 註冊令牌"
msgid "Revert this commit"
msgstr "還原此æ交"
@@ -885,6 +1035,9 @@ msgstr "還原此æ交"
msgid "Revert this merge request"
msgstr "還原此åˆä½µè«‹æ±‚"
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "ä¿å­˜æµæ°´ç·šè¨ˆåŠƒ"
@@ -904,11 +1057,14 @@ msgid "Select a timezone"
msgstr "é¸æ“‡æ™‚å€"
msgid "Select existing branch"
-msgstr ""
+msgstr "é¸æ“‡ç¾æœ‰åˆ†æ”¯ (branch)"
msgid "Select target branch"
msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯"
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "為賬號添加壹個用於推é€æˆ–拉å–çš„ %{protocol} 密碼。"
@@ -924,16 +1080,25 @@ msgstr "設置自動部署"
msgid "SetPasswordToCloneLink|set a password"
msgstr "設置密碼"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "顯示 %d 個事件"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "æºä»£ç¢¼"
-msgid "Specify the following URL during the Runner setup:"
+msgid "Spam Logs"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr "在 Runner 設置時指定以下 URL:"
+
msgid "StarProject|Star"
msgstr "星標"
@@ -941,7 +1106,7 @@ msgid "Start a %{new_merge_request} with these changes"
msgstr "由此更改 %{new_merge_request}"
msgid "Start the Runner!"
-msgstr ""
+msgstr "é‹ä½œ Runner!"
msgid "Switch branch/tag"
msgstr "切æ›åˆ†æ”¯/標籤"
@@ -957,7 +1122,7 @@ msgid "Target Branch"
msgstr "目標分支"
msgid "Team"
-msgstr ""
+msgstr "團隊"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "編碼階段概述了從第壹次æ交到創建åˆä½µè«‹æ±‚的時間。創建第壹個åˆä½µè«‹æ±‚後,數據將自動添加到此處。"
@@ -1008,7 +1173,7 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "中ä½æ•¸æ˜¯å£¹å€‹æ•¸åˆ—中最中間的值。例如在 3ã€5ã€9 之間,中ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,中ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。"
msgid "There are problems accessing Git storage: "
-msgstr ""
+msgstr "è¨ªå• Git 存儲時出ç¾å•é¡Œï¼š"
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "在創建壹個空的存儲庫或導入ç¾æœ‰å­˜å„²åº«ä¹‹å‰ï¼Œæ‚¨å°‡ç„¡æ³•æŽ¨é€ä»£ç¢¼ã€‚"
@@ -1178,7 +1343,7 @@ msgid "UploadLink|click to upload"
msgstr "點擊上傳"
msgid "Use the following registration token during setup:"
-msgstr ""
+msgstr "在安è£éŽç¨‹ä¸­ä½¿ç”¨ä»¥ä¸‹è¨»å†Šä»¤ç‰Œï¼š"
msgid "Use your global notification setting"
msgstr "使用全局通知設置"
@@ -1204,6 +1369,9 @@ msgstr "權é™ä¸è¶³ã€‚如需查看相關數據,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚
msgid "We don't have enough data to show this stage."
msgstr "該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚"
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è¯·"
@@ -1267,4 +1435,5 @@ msgstr "通知郵件"
msgid "parent"
msgid_plural "parents"
-msgstr[0] "父級" \ No newline at end of file
+msgstr[0] "父級"
+
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 1fc6b79187f..09c07a83d34 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-18 14:15+0530\n"
-"PO-Revision-Date: 2017-08-23 09:59-0400\n"
+"POT-Creation-Date: 2017-09-06 08:32+0200\n"
+"PO-Revision-Date: 2017-09-06 06:20-0400\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
@@ -28,20 +28,20 @@ msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} 在 %{commit_timeago} é€äº¤"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
+msgstr "已失敗 %{number_of_failures} 次,在失敗 %{maximum_failures} æ¬¡å‰ GitLab 會é‡è©¦ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
+msgstr "已失敗 %{number_of_failures} 次,在失敗 %{maximum_failures} æ¬¡å‰ GitLab 會在 %{number_of_seconds} 秒後é‡è©¦ã€‚"
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
msgstr ""
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
-msgstr[0] ""
+msgstr[0] "%{storage_name}:已存å–此主機失敗 %{failed_attempts} 次"
msgid "(checkout the %{link} for information on how to install it)."
-msgstr ""
+msgstr "(如何安è£è«‹åƒé–± %{link})"
msgid "1 pipeline"
msgid_plural "%d pipelines"
@@ -53,7 +53,16 @@ msgstr "æŒçºŒæ•´åˆ (CI) 相關的圖表"
msgid "About auto deploy"
msgstr "關於自動部署"
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr "已暫時åœç”¨å¤±æ•—çš„ Git 儲存空間。當儲存空間æ¢å¾©æ­£å¸¸å¾Œï¼Œè«‹é‡ç½®å„²å­˜ç©ºé–“å¥åº·æŒ‡æ•¸ã€‚"
+
+msgid "Account"
msgstr ""
msgid "Active"
@@ -78,6 +87,12 @@ msgid "Add new directory"
msgstr "新增目錄"
msgid "All"
+msgstr "全部"
+
+msgid "Appearances"
+msgstr ""
+
+msgid "Applications"
msgstr ""
msgid "Archived project! Repository is read-only"
@@ -87,20 +102,77 @@ msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "確定è¦åˆªé™¤æ­¤æµæ°´ç·š (pipeline) 排程嗎?"
msgid "Are you sure you want to discard your changes?"
-msgstr ""
+msgstr "確定è¦æ”¾æ£„修改嗎?"
msgid "Are you sure you want to reset registration token?"
-msgstr ""
+msgstr "確定è¦é‡ç½®è¨»å†Šæ†‘è­‰ (registration token) 嗎?"
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "確定è¦é‡ç½®å¥åº·æª¢æŸ¥å­˜å–憑證 (access token) 嗎?"
msgid "Are you sure?"
-msgstr ""
+msgstr "確定嗎?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "拖放檔案到此處或者 %{upload_link}"
+msgid "Authentication log"
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Billinglans|Downgrade"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "分支 (branch) "
@@ -132,6 +204,9 @@ msgstr "ç€è¦½æª”案"
msgid "ByAuthor|by"
msgstr "作者:"
+msgid "CI / CD"
+msgstr ""
+
msgid "CI configuration"
msgstr "CI 組態"
@@ -139,7 +214,7 @@ msgid "Cancel"
msgstr "å–消"
msgid "Cancel edit"
-msgstr ""
+msgstr "å–消編輯"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "挑é¸åˆ°åˆ†æ”¯ (branch) "
@@ -159,6 +234,9 @@ msgstr "更新日誌"
msgid "Charts"
msgstr "統計圖"
+msgid "Chat"
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr "挑é¸æ­¤æ›´å‹•è¨˜éŒ„ (commit) "
@@ -220,7 +298,7 @@ msgid "CiStatus|running"
msgstr "執行中"
msgid "Comments"
-msgstr ""
+msgstr "留言"
msgid "Commit"
msgid_plural "Commits"
@@ -253,12 +331,18 @@ msgstr "é€äº¤è€…為 "
msgid "Compare"
msgstr "比較"
+msgid "Container Registry"
+msgstr ""
+
msgid "Contribution guide"
msgstr "å”作指å—"
msgid "Contributors"
msgstr "å”作者"
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
msgid "Copy URL to clipboard"
msgstr "複製網å€åˆ°å‰ªè²¼ç°¿"
@@ -269,7 +353,7 @@ msgid "Create New Directory"
msgstr "建立新目錄"
msgid "Create a new branch"
-msgstr ""
+msgstr "建立新分支 (branch)"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "建立個人存å–憑證 (access token) 以使用 %{protocol} 來上傳 (push) 或下載 (pull) 。"
@@ -344,17 +428,20 @@ msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "部署"
+msgid "Deploy Keys"
+msgstr ""
+
msgid "Description"
msgstr "æè¿°"
msgid "Details"
-msgstr ""
+msgstr "細節"
msgid "Directory name"
msgstr "目錄å稱"
msgid "Discard changes"
-msgstr ""
+msgstr "放棄修改"
msgid "Don't show again"
msgstr "ä¸å†é¡¯ç¤º"
@@ -392,23 +479,26 @@ msgstr "編輯"
msgid "Edit Pipeline Schedule %{id}"
msgstr "編輯 %{id} æµæ°´ç·š (pipeline) 排程"
-msgid "EventFilterBy|Filter by all"
+msgid "Emails"
msgstr ""
+msgid "EventFilterBy|Filter by all"
+msgstr "顯示全部"
+
msgid "EventFilterBy|Filter by comments"
-msgstr ""
+msgstr "以留言篩é¸"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "以議題 (issue) 事件篩é¸"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "以åˆä½µ (merge) 事件篩é¸"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "ä»¥æŽ¨é€ (push) 事件篩é¸"
msgid "EventFilterBy|Filter by team"
-msgstr ""
+msgstr "以團隊篩é¸"
msgid "Every day (at 4:00am)"
msgstr "æ¯æ—¥åŸ·è¡Œï¼ˆæ·©æ™¨å››é»žï¼‰"
@@ -456,39 +546,51 @@ msgstr "從議題 (issue) 建立直到部署至營é‹ç’°å¢ƒ"
msgid "From merge request merge until deploy to production"
msgstr "從請求被åˆä½µå¾Œ (merge request merged) 直到部署至營é‹ç’°å¢ƒ"
-msgid "Git storage health information has been reset"
+msgid "GPG Keys"
msgstr ""
-msgid "GitLab Runner section"
+msgid "Geo Nodes"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr "Git 儲存空間å¥åº·æŒ‡æ•¸å·²é‡ç½®"
+
+msgid "GitLab Runner section"
+msgstr "GitLab Runner"
+
msgid "Go to your fork"
msgstr "å‰å¾€æ‚¨çš„分支 (fork) "
msgid "GoToYourFork|Fork"
msgstr "å‰å¾€æ‚¨çš„分支 (fork) "
-msgid "Health Check"
+msgid "Group overview"
msgstr ""
+msgid "Health Check"
+msgstr "å¥åº·æª¢æŸ¥"
+
msgid "Health information can be retrieved from the following endpoints. More information is available"
-msgstr ""
+msgstr "å¥åº·è³‡è¨Šå¯å¾žä»¥ä¸‹é€£çµå–得。想了解更多請åƒé–±"
msgid "HealthCheck|Access token is"
-msgstr ""
+msgstr "å­˜å–憑證 (access token) 是"
msgid "HealthCheck|Healthy"
-msgstr ""
+msgstr "å¥åº·"
msgid "HealthCheck|No Health Problems Detected"
-msgstr ""
+msgstr "沒有檢測到å¥åº·å•é¡Œ"
msgid "HealthCheck|Unhealthy"
-msgstr ""
+msgstr "ä¸è‰¯"
msgid "Home"
msgstr "首é "
+msgid "Hooks"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已開始維護"
@@ -496,7 +598,7 @@ msgid "Import repository"
msgstr "匯入檔案庫 (repository)"
msgid "Install a Runner compatible with GitLab CI"
-msgstr ""
+msgstr "安è£èˆ‡ GitLab CI 相容的 Runner"
msgid "Interval Pattern"
msgstr "循環週期"
@@ -505,16 +607,10 @@ msgid "Introducing Cycle Analytics"
msgstr "週期分æžç°¡ä»‹"
msgid "Issue events"
-msgstr ""
-
-msgid "Jobs for last month"
-msgstr "上個月的任務 (job) "
+msgstr "議題 (issue) 事件"
-msgid "Jobs for last week"
-msgstr "上個星期的任務 (job) "
-
-msgid "Jobs for last year"
-msgstr "去年的任務 (job) "
+msgid "Issues"
+msgstr ""
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -522,6 +618,9 @@ msgstr "åœç”¨"
msgid "LFSStatus|Enabled"
msgstr "啟用"
+msgid "Labels"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "最近 %d 天"
@@ -536,10 +635,10 @@ msgid "Last commit"
msgstr "最後更動記錄 (commit) "
msgid "LastPushEvent|You pushed to"
-msgstr ""
+msgstr "您上傳 (push) 了"
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "æ–¼"
msgid "Learn more in the"
msgstr "了解更多"
@@ -553,22 +652,40 @@ msgstr "退出群組"
msgid "Leave project"
msgstr "退出專案"
+msgid "License"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "é™åˆ¶æœ€å¤šé¡¯ç¤º %d 個事件"
+msgid "Locked Files"
+msgstr ""
+
msgid "Median"
msgstr "中ä½æ•¸"
+msgid "Members"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
msgid "Merge events"
+msgstr "åˆä½µ (merge) 事件"
+
+msgid "Messages"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "新增 SSH 金鑰"
-msgid "More information is available|here"
+msgid "Monitoring"
msgstr ""
+msgid "More information is available|here"
+msgstr "å¥åº·æª¢æŸ¥"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "建立議題 (issue) "
@@ -666,6 +783,9 @@ msgstr "åƒèˆ‡"
msgid "NotificationLevel|Watch"
msgstr "關注"
+msgid "Notifications"
+msgstr ""
+
msgid "OfSearchInADropdown|Filter"
msgstr "篩é¸"
@@ -675,9 +795,15 @@ msgstr "開始於"
msgid "Options"
msgstr "é¸é …"
+msgid "Overview"
+msgstr ""
+
msgid "Owner"
msgstr "所有權"
+msgid "Password"
+msgstr ""
+
msgid "Pipeline"
msgstr "æµæ°´ç·š (pipeline) "
@@ -690,6 +816,9 @@ msgstr "æµæ°´ç·š (pipeline) 排程"
msgid "Pipeline Schedules"
msgstr "æµæ°´ç·š (pipeline) 排程"
+msgid "Pipeline quota"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失敗:"
@@ -753,6 +882,15 @@ msgstr "æµæ°´ç·š (pipeline) "
msgid "Pipelines charts"
msgstr "æµæ°´ç·š (pipeline) 圖表"
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
msgid "Pipeline|all"
msgstr "所有"
@@ -765,9 +903,15 @@ msgstr "於階段"
msgid "Pipeline|with stages"
msgstr "於階段"
-msgid "Project"
+msgid "Preferences"
+msgstr ""
+
+msgid "Profile Settings"
msgstr ""
+msgid "Project"
+msgstr "專案"
+
msgid "Project '%{project_name}' queued for deletion."
msgstr "專案 '%{project_name}' 已加入刪除佇列。"
@@ -784,7 +928,7 @@ msgid "Project access must be granted explicitly to each user."
msgstr "專案權é™å¿…須一一指派給æ¯å€‹ä½¿ç”¨è€…。"
msgid "Project details"
-msgstr ""
+msgstr "專案細節"
msgid "Project export could not be deleted."
msgstr "匯出的專案無法被刪除。"
@@ -801,9 +945,12 @@ msgstr "專案導出已開始。完æˆå¾Œä¸‹è¼‰é€£çµæœƒé€åˆ°æ‚¨çš„信箱。"
msgid "Project home"
msgstr "專案首é "
-msgid "ProjectActivityRSS|Subscribe"
+msgid "Project overview"
msgstr ""
+msgid "ProjectActivityRSS|Subscribe"
+msgstr "訂閱"
+
msgid "ProjectFeature|Disabled"
msgstr "åœç”¨"
@@ -825,9 +972,12 @@ msgstr "階段"
msgid "ProjectNetworkGraph|Graph"
msgstr "分支圖"
-msgid "Push events"
+msgid "Push Rules"
msgstr ""
+msgid "Push events"
+msgstr "æŽ¨é€ (push) 事件"
+
msgid "Read more"
msgstr "瞭解更多"
@@ -865,19 +1015,19 @@ msgid "Remove project"
msgstr "刪除專案"
msgid "Repository"
-msgstr ""
+msgstr "檔案庫 (repository)"
msgid "Request Access"
msgstr "申請權é™"
msgid "Reset git storage health information"
-msgstr ""
+msgstr "é‡ç½® Git 儲存空間å¥åº·æŒ‡æ•¸"
msgid "Reset health check access token"
-msgstr ""
+msgstr "é‡ç½®å¥åº·æª¢æŸ¥å­˜å–憑證 (access token)"
msgid "Reset runners registration token"
-msgstr ""
+msgstr "é‡ç½® Runner 註冊憑證 (registration token)"
msgid "Revert this commit"
msgstr "還原此更動記錄 (commit)"
@@ -885,6 +1035,9 @@ msgstr "還原此更動記錄 (commit)"
msgid "Revert this merge request"
msgstr "還原此åˆä½µè«‹æ±‚ (merge request) "
+msgid "SSH Keys"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr "儲存æµæ°´ç·š (pipeline) 排程"
@@ -904,11 +1057,14 @@ msgid "Select a timezone"
msgstr "é¸æ“‡æ™‚å€"
msgid "Select existing branch"
-msgstr ""
+msgstr "é¸æ“‡ç¾æœ‰åˆ†æ”¯ (branch)"
msgid "Select target branch"
msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯ (branch) "
+msgid "Service Templates"
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "請先設定密碼,æ‰èƒ½ä½¿ç”¨ %{protocol} 來上傳 (push) 或下載 (pull) 。"
@@ -924,16 +1080,25 @@ msgstr "設定自動部署"
msgid "SetPasswordToCloneLink|set a password"
msgstr "設定密碼"
+msgid "Settings"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "顯示 %d 個事件"
+msgid "Snippets"
+msgstr ""
+
msgid "Source code"
msgstr "原始碼"
-msgid "Specify the following URL during the Runner setup:"
+msgid "Spam Logs"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr "åœ¨å®‰è£ Runner 時指定以下 URL:"
+
msgid "StarProject|Star"
msgstr "收è—"
@@ -941,7 +1106,7 @@ msgid "Start a %{new_merge_request} with these changes"
msgstr "以這些改動建立一個新的 %{new_merge_request} "
msgid "Start the Runner!"
-msgstr ""
+msgstr "å•Ÿå‹• Runner!"
msgid "Switch branch/tag"
msgstr "切æ›åˆ†æ”¯ (branch) 或標籤"
@@ -957,7 +1122,7 @@ msgid "Target Branch"
msgstr "目標分支 (branch) "
msgid "Team"
-msgstr ""
+msgstr "團隊"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "程å¼é–‹ç™¼éšŽæ®µé¡¯ç¤ºå¾žç¬¬ä¸€æ¬¡æ›´å‹•è¨˜éŒ„ (commit) 到建立åˆä½µè«‹æ±‚ (merge request) 的時間。建立第一個åˆä½µè«‹æ±‚後,資料將自動填入。"
@@ -1008,7 +1173,7 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "中ä½æ•¸æ˜¯ä¸€å€‹æ•¸åˆ—中最中間的值。例如在 3ã€5ã€9 之間,中ä½æ•¸æ˜¯ 5。在 3ã€5ã€7ã€8 之間,中ä½æ•¸æ˜¯ (5 + 7)/ 2 = 6。"
msgid "There are problems accessing Git storage: "
-msgstr ""
+msgstr "å­˜å– Git 儲存空間時出ç¾å•é¡Œï¼š"
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個ç¾å­˜çš„檔案庫之å‰ï¼Œæ‚¨å°‡ç„¡æ³•ä¸Šå‚³æ›´æ–° (push) 。"
@@ -1178,7 +1343,7 @@ msgid "UploadLink|click to upload"
msgstr "點擊上傳"
msgid "Use the following registration token during setup:"
-msgstr ""
+msgstr "在安è£éŽç¨‹ä¸­ä½¿ç”¨æ­¤è¨»å†Šæ†‘è­‰ (registration token):"
msgid "Use your global notification setting"
msgstr "使用全域通知設定"
@@ -1204,14 +1369,17 @@ msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚
msgid "We don't have enough data to show this stage."
msgstr "因該階段的資料ä¸è¶³è€Œç„¡æ³•é¡¯ç¤ºç›¸é—œè³‡è¨Š"
+msgid "Wiki"
+msgstr ""
+
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è«‹"
msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "å³å°‡è¦åˆªé™¤ %{group_name}。被刪除的群組無法復原ï¼çœŸçš„「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+msgstr "å°‡è¦åˆªé™¤ %{group_name}。被刪除的群組無法復原ï¼çœŸçš„「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
-msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。被刪除的專案無法復原ï¼çœŸçš„「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+msgstr "å°‡è¦åˆªé™¤ %{project_name_with_namespace}。被刪除的專案無法復原ï¼çœŸçš„「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr "å°‡è¦åˆªé™¤æœ¬åˆ†æ”¯å°ˆæ¡ˆèˆ‡ä¸»å¹¹ %{forked_from_project} 的所有關è¯ã€‚ 真的「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
@@ -1267,4 +1435,5 @@ msgstr "通知信"
msgid "parent"
msgid_plural "parents"
-msgstr[0] "上層" \ No newline at end of file
+msgstr[0] "上層"
+
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
new file mode 100644
index 00000000000..c9687af4dd2
--- /dev/null
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe IssuableCollections do
+ let(:user) { create(:user) }
+
+ let(:controller) do
+ klass = Class.new do
+ def self.helper_method(name); end
+
+ include IssuableCollections
+ end
+
+ controller = klass.new
+
+ allow(controller).to receive(:params).and_return(state: 'opened')
+
+ controller
+ end
+
+ describe '#redirect_out_of_range' do
+ before do
+ allow(controller).to receive(:url_for)
+ end
+
+ it 'returns true and redirects if the offset is out of range' do
+ relation = double(:relation, current_page: 10)
+
+ expect(controller).to receive(:redirect_to)
+ expect(controller.send(:redirect_out_of_range, relation, 2)).to eq(true)
+ end
+
+ it 'returns false if the offset is not out of range' do
+ relation = double(:relation, current_page: 1)
+
+ expect(controller).not_to receive(:redirect_to)
+ expect(controller.send(:redirect_out_of_range, relation, 2)).to eq(false)
+ end
+ end
+
+ describe '#issues_page_count' do
+ it 'returns the number of issue pages' do
+ project = create(:project, :public)
+
+ create(:issue, project: project)
+
+ finder = IssuesFinder.new(user)
+ issues = finder.execute
+
+ allow(controller).to receive(:issues_finder)
+ .and_return(finder)
+
+ expect(controller.send(:issues_page_count, issues)).to eq(1)
+ end
+ end
+
+ describe '#merge_requests_page_count' do
+ it 'returns the number of merge request pages' do
+ project = create(:project, :public)
+
+ create(:merge_request, source_project: project, target_project: project)
+
+ finder = MergeRequestsFinder.new(user)
+ merge_requests = finder.execute
+
+ allow(controller).to receive(:merge_requests_finder)
+ .and_return(finder)
+
+ pages = controller.send(:merge_requests_page_count, merge_requests)
+
+ expect(pages).to eq(1)
+ end
+ end
+
+ describe '#page_count_for_relation' do
+ it 'returns the number of pages' do
+ relation = double(:relation, limit_value: 20)
+ pages = controller.send(:page_count_for_relation, relation, 28)
+
+ expect(pages).to eq(2)
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 25ec63de94a..c2b59239af9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -107,7 +107,7 @@ FactoryGirl.define do
end
trait :triggered do
- trigger_request factory: :ci_trigger_request_with_variables
+ trigger_request factory: :ci_trigger_request
end
after(:build) do |build, evaluator|
diff --git a/spec/factories/ci/pipeline_variable_variables.rb b/spec/factories/ci/pipeline_variables.rb
index 7c1a7faec08..7c1a7faec08 100644
--- a/spec/factories/ci/pipeline_variable_variables.rb
+++ b/spec/factories/ci/pipeline_variables.rb
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 10e0ab4fd3c..40b8848920e 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,14 +1,5 @@
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
trigger factory: :ci_trigger
-
- factory :ci_trigger_request_with_variables do
- variables do
- {
- TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
- TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
- }
- end
- end
end
end
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
index a5aeffbe12d..c0beecf0bea 100644
--- a/spec/factories/gpg_signature.rb
+++ b/spec/factories/gpg_signature.rb
@@ -6,6 +6,6 @@ FactoryGirl.define do
project
gpg_key
gpg_key_primary_keyid { gpg_key.primary_keyid }
- valid_signature true
+ verification_status :verified
end
end
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index a6ad5981f8f..c480b5b7e34 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -8,8 +8,8 @@ describe 'Issue Boards add issue modal', :js do
let!(:label) { create(:label, project: project) }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: label, position: 1) }
- let!(:issue) { create(:issue, project: project) }
- let!(:issue2) { create(:issue, project: project) }
+ let!(:issue) { create(:issue, project: project, title: 'abc', description: 'def') }
+ let!(:issue2) { create(:issue, project: project, title: 'hij', description: 'klm') }
before do
project.team << [user, :master]
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 913258ca40f..e010b5f3444 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -73,15 +73,15 @@ describe 'Issue Boards', js: true do
let!(:list2) { create(:list, board: board, label: development, position: 1) }
let!(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
- let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], labels: [planning], relative_position: 8) }
- let!(:issue2) { create(:labeled_issue, project: project, author: user2, labels: [planning], relative_position: 7) }
- let!(:issue3) { create(:labeled_issue, project: project, labels: [planning], relative_position: 6) }
- let!(:issue4) { create(:labeled_issue, project: project, labels: [planning], relative_position: 5) }
- let!(:issue5) { create(:labeled_issue, project: project, labels: [planning], milestone: milestone, relative_position: 4) }
- let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development], relative_position: 3) }
- let!(:issue7) { create(:labeled_issue, project: project, labels: [development], relative_position: 2) }
- let!(:issue8) { create(:closed_issue, project: project) }
- let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let!(:issue1) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
+ let!(:issue2) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
+ let!(:issue3) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
+ let!(:issue4) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
+ let!(:issue5) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
+ let!(:issue6) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
+ let!(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
+ let!(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
+ let!(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
before do
visit project_board_path(project, board)
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 0c9fcc60d30..479fb713297 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -203,105 +203,4 @@ describe 'Commits' do
end
end
end
-
- describe 'GPG signed commits', :js do
- 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.team << [user, :master]
-
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- sign_in(user)
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
-
- # user changes his email which makes the gpg key verified
- Sidekiq::Testing.inline! do
- user.skip_reconfirmation!
- user.update_attributes!(email: GpgHelpers::User1.emails.first)
- end
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
- 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.team << [user, :master]
-
- sign_in(user)
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
-
- # user adds the gpg key which makes the signature valid
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
- end
-
- it 'shows popover badges' do
- gpg_user = create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: gpg_user
- end
-
- user = create :user
- project.team << [user, :master]
-
- sign_in(user)
- visit project_commits_path(project, :'signed-commits')
-
- # unverified signature
- click_on 'Unverified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with an unverified signature.'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
- end
-
- # verified and the gpg user has a gitlab profile
- click_on 'Verified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with a verified signature.'
- expect(page).to have_content 'Nannie Bernhard'
- expect(page).to have_content '@nannie.bernhard'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
- end
-
- # verified and the gpg user's profile doesn't exist anymore
- gpg_user.destroy!
-
- visit project_commits_path(project, :'signed-commits')
-
- click_on 'Verified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with a verified signature.'
- expect(page).to have_content 'Nannie Bernhard'
- expect(page).to have_content 'nannie.bernhard@example.com'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
- end
- end
- end
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 4297bfff3d9..2db6f9a2982 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -166,12 +166,10 @@ describe 'New/edit issue', :js do
end
end
- page.within '.issuable-meta' do
+ page.within '.breadcrumbs' do
issue = Issue.find_by(title: 'title')
- expect(page).to have_text("Issue #{issue.to_reference}")
- # compare paths because the host differ in test
- expect(find_link(issue.to_reference)[:href]).to end_with(issue_path(issue))
+ expect(page).to have_text("Issues #{issue.to_reference}")
end
end
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index c470cb7c716..28b636f9359 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -40,18 +40,4 @@ feature 'Issue Detail', :js do
end
end
end
-
- context 'when authored by a user who is later deleted' do
- before do
- issue.update_attribute(:author_id, nil)
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'shows the issue' do
- page.within('.issuable-details') do
- expect(find('h2')).to have_content(issue.title)
- end
- end
- end
end
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 89410b0e90f..de98b147d04 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -84,13 +84,10 @@ describe 'New/edit merge request', :js do
end
end
- page.within '.issuable-meta' do
+ page.within '.breadcrumbs' do
merge_request = MergeRequest.find_by(source_branch: 'fix')
- expect(page).to have_text("Merge request #{merge_request.to_reference}")
- # compare paths because the host differ in test
- expect(find_link(merge_request.to_reference)[:href])
- .to end_with(merge_request_path(merge_request))
+ expect(page).to have_text("Merge Requests #{merge_request.to_reference}")
end
end
diff --git a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
new file mode 100644
index 00000000000..55a82bdf2b9
--- /dev/null
+++ b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+feature 'Resolve outdated diff discussions', js: true do
+ let(:project) { create(:project, :repository, :public) }
+
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: 'csv', target_branch: 'master')
+ end
+
+ let(:outdated_diff_refs) { project.commit('926c6595b263b2a40da6b17f3e3b7ea08344fad6').diff_refs }
+ let(:current_diff_refs) { merge_request.diff_refs }
+
+ let(:outdated_position) do
+ Gitlab::Diff::Position.new(
+ old_path: 'files/csv/Book1.csv',
+ new_path: 'files/csv/Book1.csv',
+ old_line: nil,
+ new_line: 9,
+ diff_refs: outdated_diff_refs
+ )
+ end
+
+ let(:current_position) do
+ Gitlab::Diff::Position.new(
+ old_path: 'files/csv/Book1.csv',
+ new_path: 'files/csv/Book1.csv',
+ old_line: nil,
+ new_line: 1,
+ diff_refs: current_diff_refs
+ )
+ end
+
+ let!(:outdated_discussion) do
+ create(:diff_note_on_merge_request,
+ project: project,
+ noteable: merge_request,
+ position: outdated_position).to_discussion
+ end
+
+ let!(:current_discussion) do
+ create(:diff_note_on_merge_request,
+ noteable: merge_request,
+ project: project,
+ position: current_position).to_discussion
+ end
+
+ before do
+ sign_in(merge_request.author)
+ end
+
+ context 'when a discussion was resolved by a push' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+
+ merge_request.update_diff_discussion_positions(
+ old_diff_refs: outdated_diff_refs,
+ new_diff_refs: current_diff_refs,
+ current_user: merge_request.author
+ )
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows that as automatically resolved' do
+ within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
+ expect(page).to have_css('.discussion-body', visible: false)
+ expect(page).to have_content('Automatically resolved')
+ end
+ end
+
+ it 'does not show that for active discussions' do
+ within(".discussion[data-discussion-id='#{current_discussion.id}']") do
+ expect(page).to have_css('.discussion-body', visible: true)
+ expect(page).not_to have_content('Automatically resolved')
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 877f305120e..442ce14eb7e 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -97,6 +97,16 @@ feature 'Merge requests > User posts diff notes', :js do
visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
+ context 'after deleteing a note' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+
+ first('.js-note-delete', visible: false).trigger('click')
+
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
context 'with a new line' do
it 'allows commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index 6edc482b47e..623e4f341c5 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -42,7 +42,7 @@ feature 'Profile > GPG Keys' do
scenario 'User revokes a key via the key index' do
gpg_key = create :gpg_key, user: user, key: GpgHelpers::User2.public_key
- gpg_signature = create :gpg_signature, gpg_key: gpg_key, valid_signature: true
+ gpg_signature = create :gpg_signature, gpg_key: gpg_key, verification_status: :verified
visit profile_gpg_keys_path
@@ -51,7 +51,7 @@ feature 'Profile > GPG Keys' do
expect(page).to have_content('Your GPG keys (0)')
expect(gpg_signature.reload).to have_attributes(
- valid_signature: false,
+ verification_status: 'unknown_key',
gpg_key: nil
)
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 2eb6fab129d..ad2db1a34f4 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -18,23 +18,25 @@ feature 'Import/Export - project import integration test', js: true do
context 'when selecting the namespace' do
let(:user) { create(:admin) }
- let!(:namespace) { create(:namespace, name: "asd", owner: user) }
+ let!(:namespace) { create(:namespace, name: 'asd', owner: user) }
+ let(:project_path) { 'test-project-path' + SecureRandom.hex }
context 'prefilled the path' do
scenario 'user imports an exported project successfully' do
visit new_project_path
select2(namespace.id, from: '#project_namespace_id')
- fill_in :project_path, with: 'test-project-path', visible: true
+ fill_in :project_path, with: project_path, visible: true
click_link 'GitLab export'
expect(page).to have_content('Import an exported GitLab project')
- expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path")
- expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original
+ expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
+ expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
attach_file('file', file)
+ click_on 'Import project'
- expect { click_on 'Import project' }.to change { Project.count }.by(1)
+ expect(Project.count).to eq(1)
project = Project.last
expect(project).not_to be_nil
@@ -64,7 +66,7 @@ feature 'Import/Export - project import integration test', js: true do
end
scenario 'invalid project' do
- namespace = create(:namespace, name: "asd", owner: user)
+ namespace = create(:namespace, name: 'asdf', owner: user)
project = create(:project, namespace: namespace)
visit new_project_path
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 037ac00d39f..3b5c6966287 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -292,26 +292,44 @@ feature 'Jobs' do
end
feature 'Variables' do
- let(:trigger_request) { create(:ci_trigger_request_with_variables) }
+ let(:trigger_request) { create(:ci_trigger_request) }
let(:job) do
create :ci_build, pipeline: pipeline, trigger_request: trigger_request
end
- before do
- visit project_job_path(project, job)
+ shared_examples 'expected variables behavior' do
+ it 'shows variable key and value after click', js: true do
+ expect(page).to have_css('.reveal-variables')
+ expect(page).not_to have_css('.js-build-variable')
+ expect(page).not_to have_css('.js-build-value')
+
+ click_button 'Reveal Variables'
+
+ expect(page).not_to have_css('.reveal-variables')
+ expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+ expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ end
end
- it 'shows variable key and value after click', js: true do
- expect(page).to have_css('.reveal-variables')
- expect(page).not_to have_css('.js-build-variable')
- expect(page).not_to have_css('.js-build-value')
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
- click_button 'Reveal Variables'
+ visit project_job_path(project, job)
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+
+ visit project_job_path(project, job)
+ end
- expect(page).not_to have_css('.reveal-variables')
- expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
- expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ it_behaves_like 'expected variables behavior'
end
end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index b2b39dbd24c..eb2d3ff50a0 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -26,7 +26,6 @@ describe 'Subgroup Issuables', :js, :nested_groups do
def expect_to_have_full_subgroup_title
title = find('.breadcrumbs-links')
- expect(title).not_to have_selector '.initializing'
- expect(title).to have_content 'group / subgroup / project'
+ expect(title).to have_content 'group subgroup project'
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index baf3d29e6c5..81f7ab80a04 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -95,49 +95,6 @@ feature 'Project' do
end
end
- describe 'project title' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
-
- before do
- sign_in(user)
- project.add_user(user, Gitlab::Access::MASTER)
- visit project_path(project)
- end
-
- it 'clicks toggle and shows dropdown', js: true do
- find('.js-projects-dropdown-toggle').click
- expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 1)
- end
- end
-
- describe 'project title' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
- let(:project2) { create(:project, namespace: user.namespace, path: 'test') }
- let(:issue) { create(:issue, project: project) }
-
- context 'on issues page', js: true do
- before do
- sign_in(user)
- project.add_user(user, Gitlab::Access::MASTER)
- project2.add_user(user, Gitlab::Access::MASTER)
- visit project_issue_path(project, issue)
- end
-
- it 'clicks toggle and shows dropdown' do
- find('.js-projects-dropdown-toggle').click
- expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 2)
-
- page.within '.dropdown-menu-projects' do
- click_link project.name_with_namespace
- end
-
- expect(page).to have_content project.name
- end
- end
- end
-
describe 'tree view (default view is set to Files)' do
let(:user) { create(:user, project_view: 'files') }
let(:project) { create(:forked_project_with_submodules) }
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
new file mode 100644
index 00000000000..8efa5b58141
--- /dev/null
+++ b/spec/features/signed_commits_spec.rb
@@ -0,0 +1,179 @@
+require 'spec_helper'
+
+describe 'GPG signed commits', :js do
+ let(:project) { create(:project, :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.team << [user, :master]
+
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user changes his email which makes the gpg key verified
+ Sidekiq::Testing.inline! do
+ user.skip_reconfirmation!
+ user.update_attributes!(email: GpgHelpers::User1.emails.first)
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ 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.team << [user, :master]
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user adds the gpg key which makes the signature valid
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ end
+
+ context 'shows popover badges' do
+ let(:user_1) do
+ create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
+ end
+
+ let(:user_1_key) do
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user_1
+ end
+ end
+
+ let(:user_2) do
+ create(:user, email: GpgHelpers::User2.emails.first, username: 'bette.cartwright', name: 'Bette Cartwright').tap do |user|
+ # secondary, unverified email
+ create :email, user: user, email: GpgHelpers::User2.emails.last
+ end
+ end
+
+ let(:user_2_key) do
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User2.public_key, user: user_2
+ end
+ end
+
+ before do
+ user = create :user
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ it 'unverified signature' do
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed commit by bette cartwright')) do
+ click_on 'Unverified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with an unverified signature.'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ 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, :'signed-commits')
+
+ within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do
+ 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.'
+ expect(page).to have_content 'Bette Cartwright'
+ expect(page).to have_content '@bette.cartwright'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ it 'unverified signature: user email does not match the committer email' do
+ user_2_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed commit by bette cartwright')) do
+ click_on 'Unverified'
+ within '.popover' do
+ expect(page).to have_content "This commit was signed with a different user's verified signature."
+ expect(page).to have_content 'Bette Cartwright'
+ expect(page).to have_content '@bette.cartwright'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ it 'verified and the gpg user has a gitlab profile' do
+ user_1_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
+ 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.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content '@nannie.bernhard'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+ end
+ end
+
+ it "verified and the gpg user's profile doesn't exist anymore" do
+ user_1_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ # 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
+
+ user_1.destroy!
+
+ refresh
+
+ within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
+ 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.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content 'nannie.bernhard@example.com'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 0e80df94e18..47b173dea0a 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -15,8 +15,8 @@ describe IssuesFinder do
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
describe '#execute' do
- set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
- set(:label_link) { create(:label_link, label: label, target: issue2) }
+ let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
+ let!(:label_link) { create(:label_link, label: label, target: issue2) }
let(:search_user) { user }
let(:params) { {} }
let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
@@ -347,6 +347,20 @@ describe IssuesFinder do
end
end
+ describe '#row_count', :request_store do
+ it 'returns the number of rows for the default state' do
+ finder = described_class.new(user)
+
+ expect(finder.row_count).to eq(3)
+ end
+
+ it 'returns the number of rows for a given state' do
+ finder = described_class.new(user, state: 'closed')
+
+ expect(finder.row_count).to be_zero
+ end
+ end
+
describe '#with_confidentiality_access_check' do
let(:guest) { create(:user) }
set(:authorized_user) { create(:user) }
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index b54155a6704..95f445e7905 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -108,4 +108,18 @@ describe MergeRequestsFinder do
end
end
end
+
+ describe '#row_count', :request_store do
+ it 'returns the number of rows for the default state' do
+ finder = described_class.new(user)
+
+ expect(finder.row_count).to eq(3)
+ end
+
+ it 'returns the number of rows for a given state' do
+ finder = described_class.new(user, state: 'closed')
+
+ expect(finder.row_count).to eq(1)
+ end
+ end
end
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
index f6346bd0fb6..c76c6945117 100644
--- a/spec/fixtures/api/schemas/pipeline_schedule.json
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -31,6 +31,10 @@
"web_url": { "type": "uri" }
},
"additionalProperties": false
+ },
+ "variables": {
+ "type": ["array", "null"],
+ "items": { "$ref": "pipeline_schedule_variable.json" }
}
},
"required": [
diff --git a/spec/fixtures/api/schemas/pipeline_schedule_variable.json b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
new file mode 100644
index 00000000000..f7ccb2d44a0
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
@@ -0,0 +1,8 @@
+{
+ "type": ["object", "null"],
+ "properties": {
+ "key": { "type": "string" },
+ "value": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 9d6e03e3868..05f969904f5 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -91,7 +91,8 @@ describe GroupsHelper do
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'outputs the groups in the correct order' do
- expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
+ expect(helper.group_title(very_deep_nested_group))
+ .to match(/<li style="text-indent: 16px;"><a.*>#{deep_nested_group.name}.*<\/li>.*<a.*>#{very_deep_nested_group.name}<\/a>/m)
end
end
end
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 9921ca1af33..68540dd4e59 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -231,7 +231,7 @@ describe NotesHelper do
end
end
- describe '#form_resurces' do
+ describe '#form_resources' do
it 'returns note for personal snippet' do
@snippet = create(:personal_snippet)
@note = create(:note_on_personal_snippet)
@@ -266,4 +266,22 @@ describe NotesHelper do
expect(noteable_note_url(note)).to match("/#{project.namespace.path}/#{project.path}/issues/#{issue.iid}##{dom_id(note)}")
end
end
+
+ describe '#discussion_resolved_intro' do
+ context 'when the discussion was resolved by a push' do
+ let(:discussion) { double(:discussion, resolved_by_push?: true) }
+
+ it 'returns "Automatically resolved"' do
+ expect(discussion_resolved_intro(discussion)).to eq('Automatically resolved')
+ end
+ end
+
+ context 'when the discussion was not resolved by a push' do
+ let(:discussion) { double(:discussion, resolved_by_push?: false) }
+
+ it 'returns "Resolved"' do
+ expect(discussion_resolved_intro(discussion)).to eq('Resolved')
+ end
+ end
+ end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 463af15930d..ab647401e14 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -17,7 +17,7 @@ describe SearchHelper do
end
end
- context "with a user" do
+ context "with a standard user" do
let(:user) { create(:user) }
before do
@@ -29,7 +29,11 @@ describe SearchHelper do
end
it "includes default sections" do
- expect(search_autocomplete_opts("adm").size).to eq(1)
+ expect(search_autocomplete_opts("dash").size).to eq(1)
+ end
+
+ it "does not include admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(0)
end
it "does not allow regular expression in search term" do
@@ -67,6 +71,18 @@ describe SearchHelper do
end
end
end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(admin)
+ end
+
+ it "includes admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(1)
+ end
+ end
end
describe 'search_filter_input_options' do
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 8c68ceff914..2aa4fb1f6c6 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -101,12 +101,13 @@ describe('Api', () => {
it('fetches projects with membership when logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
window.gon.current_user_id = 1;
const expectedData = Object.assign({
search: query,
per_page: 20,
membership: true,
+ simple: true,
}, options);
spyOn(jQuery, 'ajax').and.callFake((request) => {
expect(request.url).toEqual(expectedUrl);
@@ -124,10 +125,11 @@ describe('Api', () => {
it('fetches projects without membership when not logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
const expectedData = Object.assign({
search: query,
per_page: 20,
+ simple: true,
}, options);
spyOn(jQuery, 'ajax').and.callFake((request) => {
expect(request.url).toEqual(expectedUrl);
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 10fcc590c89..dcb8dbce178 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -4,7 +4,10 @@ import '~/gl_dropdown';
import '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
-(() => {
+describe('glDropdown', function describeDropdown() {
+ preloadFixtures('static/gl_dropdown.html.raw');
+ loadJSONFixtures('projects.json');
+
const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
const SEARCH_INPUT_SELECTOR = '.dropdown-input-field';
const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`;
@@ -39,187 +42,217 @@ import '~/lib/utils/url_utility';
remoteCallback = callback.bind({}, data);
};
- describe('Dropdown', function describeDropdown() {
- preloadFixtures('static/gl_dropdown.html.raw');
- loadJSONFixtures('projects.json');
-
- function initDropDown(hasRemote, isFilterable, extraOpts = {}) {
- const options = Object.assign({
- selectable: true,
- filterable: isFilterable,
- data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData,
- search: {
- fields: ['name']
- },
- text: project => (project.name_with_namespace || project.name),
- id: project => project.id,
- }, extraOpts);
- this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options);
- }
+ function initDropDown(hasRemote, isFilterable, extraOpts = {}) {
+ const options = Object.assign({
+ selectable: true,
+ filterable: isFilterable,
+ data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData,
+ search: {
+ fields: ['name']
+ },
+ text: project => (project.name_with_namespace || project.name),
+ id: project => project.id,
+ }, extraOpts);
+ this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options);
+ }
+
+ beforeEach(() => {
+ loadFixtures('static/gl_dropdown.html.raw');
+ this.dropdownContainerElement = $('.dropdown.inline');
+ this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
+ this.projectsData = getJSONFixture('projects.json');
+ });
- beforeEach(() => {
- loadFixtures('static/gl_dropdown.html.raw');
- this.dropdownContainerElement = $('.dropdown.inline');
- this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
- this.projectsData = getJSONFixture('projects.json');
- });
+ afterEach(() => {
+ $('body').unbind('keydown');
+ this.dropdownContainerElement.unbind('keyup');
+ });
- afterEach(() => {
- $('body').unbind('keydown');
- this.dropdownContainerElement.unbind('keyup');
- });
+ it('should open on click', () => {
+ initDropDown.call(this, false);
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ this.dropdownButtonElement.click();
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ });
- it('should open on click', () => {
- initDropDown.call(this, false);
- expect(this.dropdownContainerElement).not.toHaveClass('open');
- this.dropdownButtonElement.click();
- expect(this.dropdownContainerElement).toHaveClass('open');
- });
+ it('escapes HTML as text', () => {
+ this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>';
- it('escapes HTML as text', () => {
- this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>';
+ initDropDown.call(this, false);
- initDropDown.call(this, false);
+ this.dropdownButtonElement.click();
- this.dropdownButtonElement.click();
+ expect(
+ $('.dropdown-content li:first-child').text(),
+ ).toBe('<script>alert("testing");</script>');
+ });
- expect(
- $('.dropdown-content li:first-child').text(),
- ).toBe('<script>alert("testing");</script>');
- });
+ it('should output HTML when highlighting', () => {
+ this.projectsData[0].name_with_namespace = 'testing';
+ $('.dropdown-input .dropdown-input-field').val('test');
- it('should output HTML when highlighting', () => {
- this.projectsData[0].name_with_namespace = 'testing';
- $('.dropdown-input .dropdown-input-field').val('test');
+ initDropDown.call(this, false, true, {
+ highlight: true,
+ });
- initDropDown.call(this, false, true, {
- highlight: true,
- });
+ this.dropdownButtonElement.click();
- this.dropdownButtonElement.click();
+ expect(
+ $('.dropdown-content li:first-child').text(),
+ ).toBe('testing');
- expect(
- $('.dropdown-content li:first-child').text(),
- ).toBe('testing');
+ expect(
+ $('.dropdown-content li:first-child a').html(),
+ ).toBe('<b>t</b><b>e</b><b>s</b><b>t</b>ing');
+ });
- expect(
- $('.dropdown-content li:first-child a').html(),
- ).toBe('<b>t</b><b>e</b><b>s</b><b>t</b>ing');
+ describe('that is open', () => {
+ beforeEach(() => {
+ initDropDown.call(this, false, false);
+ this.dropdownButtonElement.click();
});
- describe('that is open', () => {
- beforeEach(() => {
- initDropDown.call(this, false, false);
- this.dropdownButtonElement.click();
+ it('should select a following item on DOWN keypress', () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
+ navigateWithKeys('down', randomIndex, () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
+ expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
});
+ });
- it('should select a following item on DOWN keypress', () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
- const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
- navigateWithKeys('down', randomIndex, () => {
+ it('should select a previous item on UP keypress', () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
+ navigateWithKeys('down', (this.projectsData.length - 1), () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
+ navigateWithKeys('up', randomIndex, () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
+ expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
});
});
+ });
- it('should select a previous item on UP keypress', () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
- navigateWithKeys('down', (this.projectsData.length - 1), () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
- navigateWithKeys('up', randomIndex, () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
- });
+ it('should click the selected item on ENTER keypress', () => {
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
+ navigateWithKeys('down', randomIndex, () => {
+ spyOn(gl.utils, 'visitUrl').and.stub();
+ navigateWithKeys('enter', null, () => {
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
+ expect(link).toHaveClass('is-active');
+ const linkedLocation = link.attr('href');
+ if (linkedLocation && linkedLocation !== '#') expect(gl.utils.visitUrl).toHaveBeenCalledWith(linkedLocation);
});
});
+ });
- it('should click the selected item on ENTER keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
- const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
- navigateWithKeys('down', randomIndex, () => {
- spyOn(gl.utils, 'visitUrl').and.stub();
- navigateWithKeys('enter', null, () => {
- expect(this.dropdownContainerElement).not.toHaveClass('open');
- const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
- expect(link).toHaveClass('is-active');
- const linkedLocation = link.attr('href');
- if (linkedLocation && linkedLocation !== '#') expect(gl.utils.visitUrl).toHaveBeenCalledWith(linkedLocation);
- });
- });
+ it('should close on ESC keypress', () => {
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ this.dropdownContainerElement.trigger({
+ type: 'keyup',
+ which: ARROW_KEYS.ESC,
+ keyCode: ARROW_KEYS.ESC
});
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ });
+ });
- it('should close on ESC keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
- this.dropdownContainerElement.trigger({
- type: 'keyup',
- which: ARROW_KEYS.ESC,
- keyCode: ARROW_KEYS.ESC
- });
- expect(this.dropdownContainerElement).not.toHaveClass('open');
+ describe('opened and waiting for a remote callback', () => {
+ beforeEach(() => {
+ initDropDown.call(this, true, true);
+ this.dropdownButtonElement.click();
+ });
+
+ it('should show loading indicator while search results are being fetched by backend', () => {
+ const dropdownMenu = document.querySelector('.dropdown-menu');
+
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
+ remoteCallback();
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
+ });
+
+ it('should not focus search input while remote task is not complete', () => {
+ expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
+ remoteCallback();
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+
+ it('should focus search input after remote task is complete', () => {
+ remoteCallback();
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+
+ it('should focus on input when opening for the second time after transition', () => {
+ remoteCallback();
+ this.dropdownContainerElement.trigger({
+ type: 'keyup',
+ which: ARROW_KEYS.ESC,
+ keyCode: ARROW_KEYS.ESC
});
+ this.dropdownButtonElement.click();
+ this.dropdownContainerElement.trigger('transitionend');
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
+ });
+
+ describe('input focus with array data', () => {
+ it('should focus input when passing array data to drop down', () => {
+ initDropDown.call(this, false, true);
+ this.dropdownButtonElement.click();
+ this.dropdownContainerElement.trigger('transitionend');
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+ });
+
+ it('should still have input value on close and restore', () => {
+ const $searchInput = $(SEARCH_INPUT_SELECTOR);
+ initDropDown.call(this, false, true);
+ $searchInput
+ .trigger('focus')
+ .val('g')
+ .trigger('input');
+ expect($searchInput.val()).toEqual('g');
+ this.dropdownButtonElement.trigger('hidden.bs.dropdown');
+ $searchInput
+ .trigger('blur')
+ .trigger('focus');
+ expect($searchInput.val()).toEqual('g');
+ });
+
+ describe('renderItem', () => {
+ describe('without selected value', () => {
+ let dropdown;
- describe('opened and waiting for a remote callback', () => {
beforeEach(() => {
- initDropDown.call(this, true, true);
- this.dropdownButtonElement.click();
+ const dropdownOptions = {
+
+ };
+ const $dropdownDiv = $('<div />');
+ $dropdownDiv.glDropdown(dropdownOptions);
+ dropdown = $dropdownDiv.data('glDropdown');
});
- it('should show loading indicator while search results are being fetched by backend', () => {
- const dropdownMenu = document.querySelector('.dropdown-menu');
+ it('marks items without ID as active', () => {
+ const dummyData = { };
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
- remoteCallback();
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
- });
+ const html = dropdown.renderItem(dummyData, null, null);
- it('should not focus search input while remote task is not complete', () => {
- expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
- remoteCallback();
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ const link = html.querySelector('a');
+ expect(link).toHaveClass('is-active');
});
- it('should focus search input after remote task is complete', () => {
- remoteCallback();
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
- });
+ it('does not mark items with ID as active', () => {
+ const dummyData = {
+ id: 'ea'
+ };
- it('should focus on input when opening for the second time after transition', () => {
- remoteCallback();
- this.dropdownContainerElement.trigger({
- type: 'keyup',
- which: ARROW_KEYS.ESC,
- keyCode: ARROW_KEYS.ESC
- });
- this.dropdownButtonElement.click();
- this.dropdownContainerElement.trigger('transitionend');
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
- });
- });
+ const html = dropdown.renderItem(dummyData, null, null);
- describe('input focus with array data', () => {
- it('should focus input when passing array data to drop down', () => {
- initDropDown.call(this, false, true);
- this.dropdownButtonElement.click();
- this.dropdownContainerElement.trigger('transitionend');
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ const link = html.querySelector('a');
+ expect(link).not.toHaveClass('is-active');
});
});
-
- it('should still have input value on close and restore', () => {
- const $searchInput = $(SEARCH_INPUT_SELECTOR);
- initDropDown.call(this, false, true);
- $searchInput
- .trigger('focus')
- .val('g')
- .trigger('input');
- expect($searchInput.val()).toEqual('g');
- this.dropdownButtonElement.trigger('hidden.bs.dropdown');
- $searchInput
- .trigger('blur')
- .trigger('focus');
- expect($searchInput.val()).toEqual('g');
- });
});
-})();
+});
diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js
index 731076a7d2a..14794cbfd50 100644
--- a/spec/javascripts/monitoring/graph/flag_spec.js
+++ b/spec/javascripts/monitoring/graph/flag_spec.js
@@ -32,10 +32,6 @@ describe('GraphFlag', () => {
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.selected-metric-line', 'x2'))
.toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.circle-metric', 'cx'))
- .toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.circle-metric', 'cy'))
- .toEqual(component.currentYCoordinate);
});
it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js
index e877832dffd..da2fbd26e23 100644
--- a/spec/javascripts/monitoring/graph/legend_spec.js
+++ b/spec/javascripts/monitoring/graph/legend_spec.js
@@ -1,6 +1,8 @@
import Vue from 'vue';
import GraphLegend from '~/monitoring/components/graph/legend.vue';
import measurements from '~/monitoring/utils/measurements';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(GraphLegend);
@@ -10,6 +12,28 @@ const createComponent = (propsData) => {
}).$mount();
};
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
+const defaultValuesComponent = {
+ graphWidth: 500,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ unitOfDisplay: 'Req/Sec',
+ currentDataIndex: 0,
+};
+
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result,
+ defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
+ defaultValuesComponent.graphHeightOffset);
+
+defaultValuesComponent.timeSeries = timeSeries;
+
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
@@ -17,95 +41,67 @@ function getTextFromNode(component, selector) {
describe('GraphLegend', () => {
describe('Computed props', () => {
it('textTransform', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
});
it('xPosition', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.xPosition).toEqual(180);
});
it('yPosition', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.yPosition).toEqual(240);
});
it('rectTransform', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
});
});
- it('has 2 rect-axis-text rect svg elements', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
+ describe('methods', () => {
+ it('translateLegendGroup should only change Y direction', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ const translatedCoordinate = component.translateLegendGroup(1);
+ expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1);
});
+ it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ const formattedMetricUsage = component.formatMetricUsage(timeSeries[0]);
+ const valueFromSeries = timeSeries[0].values[component.currentDataIndex].value;
+ expect(formattedMetricUsage.indexOf(component.unitOfDisplay)).not.toEqual(-1);
+ expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
+ });
+ });
+
+ it('has 2 rect-axis-text rect svg elements', () => {
+ const component = createComponent(defaultValuesComponent);
+
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
});
it('contains text to signal the usage, title and time', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
+ const titles = component.$el.querySelectorAll('.legend-metric-title');
+
+ expect(getTextFromNode(component, '.legend-metric-title').indexOf(component.legendTitle)).not.toEqual(-1);
+ expect(titles[0].textContent.indexOf('Title')).not.toEqual(-1);
+ expect(titles[1].textContent.indexOf('Series')).not.toEqual(-1);
+ expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
+ });
+
+ it('should contain the same number of legend groups as the timeSeries length', () => {
+ const component = createComponent(defaultValuesComponent);
- expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle);
- expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage);
- expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
+ expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length);
});
});
diff --git a/spec/javascripts/monitoring/graph_row_spec.js b/spec/javascripts/monitoring/graph_row_spec.js
deleted file mode 100644
index dd485473ccf..00000000000
--- a/spec/javascripts/monitoring/graph_row_spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import Vue from 'vue';
-import GraphRow from '~/monitoring/components/graph_row.vue';
-import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
-import { deploymentData, singleRowMetrics } from './mock_data';
-
-const createComponent = (propsData) => {
- const Component = Vue.extend(GraphRow);
-
- return new Component({
- propsData,
- }).$mount();
-};
-
-describe('GraphRow', () => {
- beforeEach(() => {
- spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
- });
-
- describe('Computed props', () => {
- it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.bootstrapClass).toEqual('col-md-6');
- });
-
- it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
- const component = createComponent({
- rowData: [singleRowMetrics[0]],
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.bootstrapClass).toEqual('col-md-12');
- });
- });
-
- it('has one column', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.$el.querySelectorAll('.prometheus-svg-container').length)
- .toEqual(component.rowData.length);
- });
-
- it('has two columns', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.$el.querySelectorAll('.col-md-6').length)
- .toEqual(component.rowData.length);
- });
-});
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index 6d6fe410113..7d8b0744af1 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -1,9 +1,8 @@
import Vue from 'vue';
-import _ from 'underscore';
import Graph from '~/monitoring/components/graph.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub';
-import { deploymentData, singleRowMetrics } from './mock_data';
+import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(Graph);
@@ -13,6 +12,8 @@ const createComponent = (propsData) => {
}).$mount();
};
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
describe('Graph', () => {
beforeEach(() => {
spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
@@ -20,7 +21,7 @@ describe('Graph', () => {
it('has a title', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -29,29 +30,10 @@ describe('Graph', () => {
expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title);
});
- it('creates a path for the line and area of the graph', (done) => {
- const component = createComponent({
- graphData: singleRowMetrics[0],
- classType: 'col-md-6',
- updateAspectRatio: false,
- deploymentData,
- });
-
- Vue.nextTick(() => {
- expect(component.area).toBeDefined();
- expect(component.line).toBeDefined();
- expect(typeof component.area).toEqual('string');
- expect(typeof component.line).toEqual('string');
- expect(_.isFunction(component.xScale)).toBe(true);
- expect(_.isFunction(component.yScale)).toBe(true);
- done();
- });
- });
-
describe('Computed props', () => {
it('axisTransform translates an element Y position depending of its height', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -64,7 +46,7 @@ describe('Graph', () => {
it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -79,7 +61,7 @@ describe('Graph', () => {
it('sends an event to the eventhub when it has finished resizing', (done) => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -95,7 +77,7 @@ describe('Graph', () => {
it('has a title for the y-axis and the chart legend that comes from the backend', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index b69f4eddffc..3d399f2bb95 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -2473,1754 +2473,5848 @@ export const statePaths = {
documentationPath: '/help/administration/monitoring/prometheus/index.md',
};
-export const singleRowMetrics = [
- {
- 'title': 'CPU usage',
- 'weight': 1,
- 'y_label': 'Memory',
- 'queries': [
- {
- 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
- 'label': 'Container CPU',
- 'result': [
- {
- 'metric': {
-
- },
- 'values': [
- {
- 'time': '2017-06-04T21:22:59.508Z',
- 'value': '0.06335544298150002'
- },
- {
- 'time': '2017-06-04T21:23:59.508Z',
- 'value': '0.0420347312480917'
- },
- {
- 'time': '2017-06-04T21:24:59.508Z',
- 'value': '0.0023175131665412706'
- },
- {
- 'time': '2017-06-04T21:25:59.508Z',
- 'value': '0.002315870476190476'
- },
- {
- 'time': '2017-06-04T21:26:59.508Z',
- 'value': '0.0025005961904761894'
- },
- {
- 'time': '2017-06-04T21:27:59.508Z',
- 'value': '0.0024612605834341264'
- },
- {
- 'time': '2017-06-04T21:28:59.508Z',
- 'value': '0.002313129398767631'
- },
- {
- 'time': '2017-06-04T21:29:59.508Z',
- 'value': '0.002411067353663882'
- },
- {
- 'time': '2017-06-04T21:30:59.508Z',
- 'value': '0.002577309263721303'
- },
- {
- 'time': '2017-06-04T21:31:59.508Z',
- 'value': '0.00242688307730403'
- },
- {
- 'time': '2017-06-04T21:32:59.508Z',
- 'value': '0.0024168360301330457'
- },
- {
- 'time': '2017-06-04T21:33:59.508Z',
- 'value': '0.0020449528090743714'
- },
- {
- 'time': '2017-06-04T21:34:59.508Z',
- 'value': '0.0019149619047619036'
- },
- {
- 'time': '2017-06-04T21:35:59.508Z',
- 'value': '0.0024491714364625094'
- },
- {
- 'time': '2017-06-04T21:36:59.508Z',
- 'value': '0.002728773131172677'
- },
- {
- 'time': '2017-06-04T21:37:59.508Z',
- 'value': '0.0028439119047618997'
- },
- {
- 'time': '2017-06-04T21:38:59.508Z',
- 'value': '0.0026307480952380917'
- },
- {
- 'time': '2017-06-04T21:39:59.508Z',
- 'value': '0.0025024842620546446'
- },
- {
- 'time': '2017-06-04T21:40:59.508Z',
- 'value': '0.002300662387260825'
- },
- {
- 'time': '2017-06-04T21:41:59.508Z',
- 'value': '0.002052890924848337'
- },
- {
- 'time': '2017-06-04T21:42:59.508Z',
- 'value': '0.0023711195238095275'
- },
- {
- 'time': '2017-06-04T21:43:59.508Z',
- 'value': '0.002513477619047618'
- },
- {
- 'time': '2017-06-04T21:44:59.508Z',
- 'value': '0.0023489776287844897'
- },
- {
- 'time': '2017-06-04T21:45:59.508Z',
- 'value': '0.002542572310212481'
- },
- {
- 'time': '2017-06-04T21:46:59.508Z',
- 'value': '0.0024579470671707952'
- },
- {
- 'time': '2017-06-04T21:47:59.508Z',
- 'value': '0.0028725150236664403'
- },
- {
- 'time': '2017-06-04T21:48:59.508Z',
- 'value': '0.0024356089105610525'
- },
- {
- 'time': '2017-06-04T21:49:59.508Z',
- 'value': '0.002544015828269929'
- },
- {
- 'time': '2017-06-04T21:50:59.508Z',
- 'value': '0.0029595013380824906'
- },
- {
- 'time': '2017-06-04T21:51:59.508Z',
- 'value': '0.0023084015085858'
- },
- {
- 'time': '2017-06-04T21:52:59.508Z',
- 'value': '0.0021070500000000083'
- },
- {
- 'time': '2017-06-04T21:53:59.508Z',
- 'value': '0.0022950066191106617'
- },
- {
- 'time': '2017-06-04T21:54:59.508Z',
- 'value': '0.002492719454470995'
- },
- {
- 'time': '2017-06-04T21:55:59.508Z',
- 'value': '0.00244312761904762'
- },
- {
- 'time': '2017-06-04T21:56:59.508Z',
- 'value': '0.0023495500000000028'
- },
- {
- 'time': '2017-06-04T21:57:59.508Z',
- 'value': '0.0020597072353070005'
- },
- {
- 'time': '2017-06-04T21:58:59.508Z',
- 'value': '0.0021482352044800866'
- },
- {
- 'time': '2017-06-04T21:59:59.508Z',
- 'value': '0.002333490000000004'
- },
- {
- 'time': '2017-06-04T22:00:59.508Z',
- 'value': '0.0025899442857142815'
- },
- {
- 'time': '2017-06-04T22:01:59.508Z',
- 'value': '0.002430299999999999'
- },
- {
- 'time': '2017-06-04T22:02:59.508Z',
- 'value': '0.0023550328092113476'
- },
- {
- 'time': '2017-06-04T22:03:59.508Z',
- 'value': '0.0026521871636872793'
- },
- {
- 'time': '2017-06-04T22:04:59.508Z',
- 'value': '0.0023080671428571398'
- },
- {
- 'time': '2017-06-04T22:05:59.508Z',
- 'value': '0.0024108401032390896'
- },
- {
- 'time': '2017-06-04T22:06:59.508Z',
- 'value': '0.002433249366678738'
- },
- {
- 'time': '2017-06-04T22:07:59.508Z',
- 'value': '0.0023242202306688682'
- },
- {
- 'time': '2017-06-04T22:08:59.508Z',
- 'value': '0.002388222857142859'
- },
- {
- 'time': '2017-06-04T22:09:59.508Z',
- 'value': '0.002115974914046794'
- },
- {
- 'time': '2017-06-04T22:10:59.508Z',
- 'value': '0.0025090043331269917'
- },
- {
- 'time': '2017-06-04T22:11:59.508Z',
- 'value': '0.002445507057277277'
- },
- {
- 'time': '2017-06-04T22:12:59.508Z',
- 'value': '0.0026348773751130976'
- },
- {
- 'time': '2017-06-04T22:13:59.508Z',
- 'value': '0.0025616258583088104'
- },
- {
- 'time': '2017-06-04T22:14:59.508Z',
- 'value': '0.0021544093415751505'
- },
- {
- 'time': '2017-06-04T22:15:59.508Z',
- 'value': '0.002649394767668881'
- },
- {
- 'time': '2017-06-04T22:16:59.508Z',
- 'value': '0.0024023332666685705'
- },
- {
- 'time': '2017-06-04T22:17:59.508Z',
- 'value': '0.0025444105294235306'
- },
- {
- 'time': '2017-06-04T22:18:59.508Z',
- 'value': '0.0027298872305772806'
- },
- {
- 'time': '2017-06-04T22:19:59.508Z',
- 'value': '0.0022880104956379287'
- },
- {
- 'time': '2017-06-04T22:20:59.508Z',
- 'value': '0.002473246666666661'
- },
- {
- 'time': '2017-06-04T22:21:59.508Z',
- 'value': '0.002259948381935587'
- },
- {
- 'time': '2017-06-04T22:22:59.508Z',
- 'value': '0.0025778470886268835'
- },
- {
- 'time': '2017-06-04T22:23:59.508Z',
- 'value': '0.002246127910852894'
- },
- {
- 'time': '2017-06-04T22:24:59.508Z',
- 'value': '0.0020697466666666758'
- },
- {
- 'time': '2017-06-04T22:25:59.508Z',
- 'value': '0.00225859722473547'
- },
- {
- 'time': '2017-06-04T22:26:59.508Z',
- 'value': '0.0026466728254554814'
- },
- {
- 'time': '2017-06-04T22:27:59.508Z',
- 'value': '0.002151247619047619'
- },
- {
- 'time': '2017-06-04T22:28:59.508Z',
- 'value': '0.002324161444543914'
- },
- {
- 'time': '2017-06-04T22:29:59.508Z',
- 'value': '0.002476474313796452'
- },
- {
- 'time': '2017-06-04T22:30:59.508Z',
- 'value': '0.0023922184232080517'
- },
- {
- 'time': '2017-06-04T22:31:59.508Z',
- 'value': '0.0025094934237468933'
- },
- {
- 'time': '2017-06-04T22:32:59.508Z',
- 'value': '0.0025665311098200883'
- },
- {
- 'time': '2017-06-04T22:33:59.508Z',
- 'value': '0.0024154900681661374'
- },
- {
- 'time': '2017-06-04T22:34:59.508Z',
- 'value': '0.0023267450166192037'
- },
- {
- 'time': '2017-06-04T22:35:59.508Z',
- 'value': '0.002156521904761904'
- },
- {
- 'time': '2017-06-04T22:36:59.508Z',
- 'value': '0.0025474356898637007'
- },
- {
- 'time': '2017-06-04T22:37:59.508Z',
- 'value': '0.0025989409624670233'
- },
- {
- 'time': '2017-06-04T22:38:59.508Z',
- 'value': '0.002348336664762987'
- },
- {
- 'time': '2017-06-04T22:39:59.508Z',
- 'value': '0.002665888246554726'
- },
- {
- 'time': '2017-06-04T22:40:59.508Z',
- 'value': '0.002652684787474174'
- },
- {
- 'time': '2017-06-04T22:41:59.508Z',
- 'value': '0.002472620430865355'
- },
- {
- 'time': '2017-06-04T22:42:59.508Z',
- 'value': '0.0020616469210110247'
- },
- {
- 'time': '2017-06-04T22:43:59.508Z',
- 'value': '0.0022434546372311934'
- },
- {
- 'time': '2017-06-04T22:44:59.508Z',
- 'value': '0.0024469386784827982'
- },
- {
- 'time': '2017-06-04T22:45:59.508Z',
- 'value': '0.0026192823809523787'
- },
- {
- 'time': '2017-06-04T22:46:59.508Z',
- 'value': '0.003451999542852798'
- },
- {
- 'time': '2017-06-04T22:47:59.508Z',
- 'value': '0.0031780314285714288'
- },
- {
- 'time': '2017-06-04T22:48:59.508Z',
- 'value': '0.0024403352380952415'
- },
- {
- 'time': '2017-06-04T22:49:59.508Z',
- 'value': '0.001998824761904764'
- },
- {
- 'time': '2017-06-04T22:50:59.508Z',
- 'value': '0.0023792404761904806'
- },
- {
- 'time': '2017-06-04T22:51:59.508Z',
- 'value': '0.002725906190476185'
- },
- {
- 'time': '2017-06-04T22:52:59.508Z',
- 'value': '0.0020989528671155624'
- },
- {
- 'time': '2017-06-04T22:53:59.508Z',
- 'value': '0.00228808226745016'
- },
- {
- 'time': '2017-06-04T22:54:59.508Z',
- 'value': '0.0019860807413192147'
- },
- {
- 'time': '2017-06-04T22:55:59.508Z',
- 'value': '0.0022698085714285897'
- },
- {
- 'time': '2017-06-04T22:56:59.508Z',
- 'value': '0.0022839098467604415'
- },
- {
- 'time': '2017-06-04T22:57:59.508Z',
- 'value': '0.002531114761904749'
- },
- {
- 'time': '2017-06-04T22:58:59.508Z',
- 'value': '0.0028941072550999016'
- },
- {
- 'time': '2017-06-04T22:59:59.508Z',
- 'value': '0.002547169523809506'
- },
- {
- 'time': '2017-06-04T23:00:59.508Z',
- 'value': '0.0024062999999999958'
- },
- {
- 'time': '2017-06-04T23:01:59.508Z',
- 'value': '0.0026939518471604386'
- },
- {
- 'time': '2017-06-04T23:02:59.508Z',
- 'value': '0.002362901428571429'
- },
- {
- 'time': '2017-06-04T23:03:59.508Z',
- 'value': '0.002663927142857154'
- },
- {
- 'time': '2017-06-04T23:04:59.508Z',
- 'value': '0.0026173314285714354'
- },
- {
- 'time': '2017-06-04T23:05:59.508Z',
- 'value': '0.002326527366406044'
- },
- {
- 'time': '2017-06-04T23:06:59.508Z',
- 'value': '0.002035313809523809'
- },
- {
- 'time': '2017-06-04T23:07:59.508Z',
- 'value': '0.002421447414786533'
- },
- {
- 'time': '2017-06-04T23:08:59.508Z',
- 'value': '0.002898313809523804'
- },
- {
- 'time': '2017-06-04T23:09:59.508Z',
- 'value': '0.002544891856112907'
- },
- {
- 'time': '2017-06-04T23:10:59.508Z',
- 'value': '0.002290625356938882'
- },
- {
- 'time': '2017-06-04T23:11:59.508Z',
- 'value': '0.002483028095238096'
- },
- {
- 'time': '2017-06-04T23:12:59.508Z',
- 'value': '0.0023396832350784237'
- },
- {
- 'time': '2017-06-04T23:13:59.508Z',
- 'value': '0.002085529248176153'
- },
- {
- 'time': '2017-06-04T23:14:59.508Z',
- 'value': '0.0022417815068428012'
- },
- {
- 'time': '2017-06-04T23:15:59.508Z',
- 'value': '0.002660293333333341'
- },
- {
- 'time': '2017-06-04T23:16:59.508Z',
- 'value': '0.0029845149093818226'
- },
- {
- 'time': '2017-06-04T23:17:59.508Z',
- 'value': '0.0027716655079475464'
- },
- {
- 'time': '2017-06-04T23:18:59.508Z',
- 'value': '0.0025217708908741128'
- },
- {
- 'time': '2017-06-04T23:19:59.508Z',
- 'value': '0.0025811235131094055'
- },
- {
- 'time': '2017-06-04T23:20:59.508Z',
- 'value': '0.002209904761904762'
- },
- {
- 'time': '2017-06-04T23:21:59.508Z',
- 'value': '0.0025053322926383344'
- },
- {
- 'time': '2017-06-04T23:22:59.508Z',
- 'value': '0.002350917636526411'
- },
- {
- 'time': '2017-06-04T23:23:59.508Z',
- 'value': '0.0018477500000000078'
- },
- {
- 'time': '2017-06-04T23:24:59.508Z',
- 'value': '0.002427629523809527'
- },
- {
- 'time': '2017-06-04T23:25:59.508Z',
- 'value': '0.0019305498147601655'
- },
- {
- 'time': '2017-06-04T23:26:59.508Z',
- 'value': '0.002097250000000006'
- },
- {
- 'time': '2017-06-04T23:27:59.508Z',
- 'value': '0.002675020952780041'
- },
- {
- 'time': '2017-06-04T23:28:59.508Z',
- 'value': '0.0023142214285714374'
- },
- {
- 'time': '2017-06-04T23:29:59.508Z',
- 'value': '0.0023644723809523737'
- },
- {
- 'time': '2017-06-04T23:30:59.508Z',
- 'value': '0.002108696190476198'
- },
- {
- 'time': '2017-06-04T23:31:59.508Z',
- 'value': '0.0019918289697997194'
- },
- {
- 'time': '2017-06-04T23:32:59.508Z',
- 'value': '0.001583584285714283'
- },
- {
- 'time': '2017-06-04T23:33:59.508Z',
- 'value': '0.002073770226383112'
- },
- {
- 'time': '2017-06-04T23:34:59.508Z',
- 'value': '0.0025877664234966818'
- },
- {
- 'time': '2017-06-04T23:35:59.508Z',
- 'value': '0.0021138238095238147'
- },
- {
- 'time': '2017-06-04T23:36:59.508Z',
- 'value': '0.0022140838095238303'
- },
- {
- 'time': '2017-06-04T23:37:59.508Z',
- 'value': '0.0018592674425248847'
- },
- {
- 'time': '2017-06-04T23:38:59.508Z',
- 'value': '0.0020461969533657016'
- },
- {
- 'time': '2017-06-04T23:39:59.508Z',
- 'value': '0.0021593628571428543'
- },
- {
- 'time': '2017-06-04T23:40:59.508Z',
- 'value': '0.0024330682564928188'
- },
- {
- 'time': '2017-06-04T23:41:59.508Z',
- 'value': '0.0021501804779093174'
- },
- {
- 'time': '2017-06-04T23:42:59.508Z',
- 'value': '0.0025787493928397945'
- },
- {
- 'time': '2017-06-04T23:43:59.508Z',
- 'value': '0.002593657082448396'
- },
- {
- 'time': '2017-06-04T23:44:59.508Z',
- 'value': '0.0021316752380952306'
- },
- {
- 'time': '2017-06-04T23:45:59.508Z',
- 'value': '0.0026972905019952086'
- },
- {
- 'time': '2017-06-04T23:46:59.508Z',
- 'value': '0.002580250764292983'
- },
- {
- 'time': '2017-06-04T23:47:59.508Z',
- 'value': '0.00227103000000001'
- },
- {
- 'time': '2017-06-04T23:48:59.508Z',
- 'value': '0.0023678515647321146'
- },
- {
- 'time': '2017-06-04T23:49:59.508Z',
- 'value': '0.002371472857142866'
- },
- {
- 'time': '2017-06-04T23:50:59.508Z',
- 'value': '0.0026181353688500978'
- },
- {
- 'time': '2017-06-04T23:51:59.508Z',
- 'value': '0.0025609667711121217'
- },
- {
- 'time': '2017-06-04T23:52:59.508Z',
- 'value': '0.0027145308139922557'
- },
- {
- 'time': '2017-06-04T23:53:59.508Z',
- 'value': '0.0024249397613310512'
- },
- {
- 'time': '2017-06-04T23:54:59.508Z',
- 'value': '0.002399907142857147'
- },
- {
- 'time': '2017-06-04T23:55:59.508Z',
- 'value': '0.0024753357142857195'
- },
- {
- 'time': '2017-06-04T23:56:59.508Z',
- 'value': '0.0026179149325231575'
- },
- {
- 'time': '2017-06-04T23:57:59.508Z',
- 'value': '0.0024261340368186956'
- },
- {
- 'time': '2017-06-04T23:58:59.508Z',
- 'value': '0.0021061071428571517'
- },
- {
- 'time': '2017-06-04T23:59:59.508Z',
- 'value': '0.0024033971105037015'
- },
- {
- 'time': '2017-06-05T00:00:59.508Z',
- 'value': '0.0028287676190475956'
- },
- {
- 'time': '2017-06-05T00:01:59.508Z',
- 'value': '0.002499719050294778'
- },
- {
- 'time': '2017-06-05T00:02:59.508Z',
- 'value': '0.0026726102153353856'
- },
- {
- 'time': '2017-06-05T00:03:59.508Z',
- 'value': '0.00262582619047618'
- },
- {
- 'time': '2017-06-05T00:04:59.508Z',
- 'value': '0.002280473147363316'
- },
- {
- 'time': '2017-06-05T00:05:59.508Z',
- 'value': '0.002095581470652675'
- },
- {
- 'time': '2017-06-05T00:06:59.508Z',
- 'value': '0.002270768490828408'
- },
- {
- 'time': '2017-06-05T00:07:59.508Z',
- 'value': '0.002728577415023017'
- },
- {
- 'time': '2017-06-05T00:08:59.508Z',
- 'value': '0.002652512857142863'
- },
- {
- 'time': '2017-06-05T00:09:59.508Z',
- 'value': '0.0022781033924455674'
- },
- {
- 'time': '2017-06-05T00:10:59.508Z',
- 'value': '0.0025345038095238234'
- },
- {
- 'time': '2017-06-05T00:11:59.508Z',
- 'value': '0.002376050020000397'
- },
- {
- 'time': '2017-06-05T00:12:59.508Z',
- 'value': '0.002455068143506122'
- },
- {
- 'time': '2017-06-05T00:13:59.508Z',
- 'value': '0.002826705714285719'
- },
- {
- 'time': '2017-06-05T00:14:59.508Z',
- 'value': '0.002343833692070314'
- },
- {
- 'time': '2017-06-05T00:15:59.508Z',
- 'value': '0.00264853297122164'
- },
- {
- 'time': '2017-06-05T00:16:59.508Z',
- 'value': '0.0027656335117426257'
- },
- {
- 'time': '2017-06-05T00:17:59.508Z',
- 'value': '0.0025896543842439564'
- },
- {
- 'time': '2017-06-05T00:18:59.508Z',
- 'value': '0.002180053237081201'
- },
- {
- 'time': '2017-06-05T00:19:59.508Z',
- 'value': '0.002475245002333342'
- },
- {
- 'time': '2017-06-05T00:20:59.508Z',
- 'value': '0.0027559767805101065'
- },
- {
- 'time': '2017-06-05T00:21:59.508Z',
- 'value': '0.0022294836141296607'
- },
- {
- 'time': '2017-06-05T00:22:59.508Z',
- 'value': '0.0021383590476190643'
- },
- {
- 'time': '2017-06-05T00:23:59.508Z',
- 'value': '0.002085417956361494'
- },
- {
- 'time': '2017-06-05T00:24:59.508Z',
- 'value': '0.0024140319047619013'
- },
- {
- 'time': '2017-06-05T00:25:59.508Z',
- 'value': '0.0024513114285714304'
- },
- {
- 'time': '2017-06-05T00:26:59.508Z',
- 'value': '0.0026932152380952446'
- },
- {
- 'time': '2017-06-05T00:27:59.508Z',
- 'value': '0.0022656844350898517'
- },
- {
- 'time': '2017-06-05T00:28:59.508Z',
- 'value': '0.0024483785714285704'
- },
- {
- 'time': '2017-06-05T00:29:59.508Z',
- 'value': '0.002559505804817207'
- },
- {
- 'time': '2017-06-05T00:30:59.508Z',
- 'value': '0.0019485681088751649'
- },
- {
- 'time': '2017-06-05T00:31:59.508Z',
- 'value': '0.00228367984456996'
- },
- {
- 'time': '2017-06-05T00:32:59.508Z',
- 'value': '0.002522149047619049'
- },
- {
- 'time': '2017-06-05T00:33:59.508Z',
- 'value': '0.0026860117715406737'
- },
- {
- 'time': '2017-06-05T00:34:59.508Z',
- 'value': '0.002679669523809523'
- },
- {
- 'time': '2017-06-05T00:35:59.508Z',
- 'value': '0.0022201920970675937'
- },
- {
- 'time': '2017-06-05T00:36:59.508Z',
- 'value': '0.0022917647619047615'
- },
- {
- 'time': '2017-06-05T00:37:59.508Z',
- 'value': '0.0021774059294673576'
- },
- {
- 'time': '2017-06-05T00:38:59.508Z',
- 'value': '0.0024637766666666763'
- },
- {
- 'time': '2017-06-05T00:39:59.508Z',
- 'value': '0.002470468290174195'
- },
- {
- 'time': '2017-06-05T00:40:59.508Z',
- 'value': '0.0022188616082057812'
- },
- {
- 'time': '2017-06-05T00:41:59.508Z',
- 'value': '0.002421840744373875'
- },
- {
- 'time': '2017-06-05T00:42:59.508Z',
- 'value': '0.0023918266666666547'
- },
- {
- 'time': '2017-06-05T00:43:59.508Z',
- 'value': '0.002195743809523809'
- },
- {
- 'time': '2017-06-05T00:44:59.508Z',
- 'value': '0.0025514828571428687'
- },
- {
- 'time': '2017-06-05T00:45:59.508Z',
- 'value': '0.0027981709349612694'
- },
- {
- 'time': '2017-06-05T00:46:59.508Z',
- 'value': '0.002557977142857146'
- },
- {
- 'time': '2017-06-05T00:47:59.508Z',
- 'value': '0.002213244285714286'
- },
- {
- 'time': '2017-06-05T00:48:59.508Z',
- 'value': '0.0025706738095238046'
- },
- {
- 'time': '2017-06-05T00:49:59.508Z',
- 'value': '0.002210976666666671'
- },
- {
- 'time': '2017-06-05T00:50:59.508Z',
- 'value': '0.002055377091646749'
- },
- {
- 'time': '2017-06-05T00:51:59.508Z',
- 'value': '0.002308368095238119'
- },
- {
- 'time': '2017-06-05T00:52:59.508Z',
- 'value': '0.0024687939885141615'
- },
- {
- 'time': '2017-06-05T00:53:59.508Z',
- 'value': '0.002563018571428578'
- },
- {
- 'time': '2017-06-05T00:54:59.508Z',
- 'value': '0.00240563291078959'
- }
- ]
- }
+export const singleRowMetricsMultipleSeries = [
+ {
+ 'title': 'Multiple Time Series',
+ 'weight': 1,
+ 'y_label': 'Request Rates',
+ 'queries': [
+ {
+ 'query_range': 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
+ 'label': 'Requests',
+ 'unit': 'Req/sec',
+ 'result': [
+ {
+ 'metric': {
+ 'status_code': '1xx'
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '0'
+ }
+ ]
+ },
+ {
+ 'metric': {
+ 'status_code': '2xx'
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '1.3333460318669703'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '1.2952627669098458'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '1.3333079369916765'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '1.3333460318669703'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '1.3142982314117277'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '1.2952504309564854'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '1.580952380952381'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '1.7333333333333334'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '2.057142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '2.1904761904761902'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '1.8285714285714287'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '2.1142857142857143'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '1.619047619047619'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '1.2952504309564854'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '1.7142857142857142'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '1.7333333333333334'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '1.3904761904761904'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '1.5047619047619047'
+ }
+ ]
+ },
+ ]
+ }
]
- }
- ]
- },
- {
- 'title': 'Memory usage',
- 'weight': 1,
- 'y_label': 'Values',
- 'queries': [
- {
- 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
- 'label': 'Container memory',
- 'unit': 'MiB',
- 'result': [
- {
- 'metric': {
+ },
+ {
+ 'title': 'Throughput',
+ 'weight': 1,
+ 'y_label': 'Requests / Sec',
+ 'queries': [
+ {
+ 'query_range': 'sum(rate(nginx_requests_total{server_zone!=\'*\', server_zone!=\'_\', container_name!=\'POD\',environment=\'production\'}[2m]))',
+ 'label': 'Total',
+ 'unit': 'req / sec',
+ 'result': [
+ {
+ 'metric': {
- },
- 'values': [
- {
- 'time': '2017-06-04T21:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:54:59.508Z',
- 'value': '15.0859375'
- }
- ]
- }
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '0.49524281183630325'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '0.4761995466580315'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '0.4952286623111941'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '0.49524281183630325'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '0.485718911608682'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '0.47619501138106085'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '0.6190476190476191'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '0.6952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '0.857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '0.9238095238095239'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '0.7428571428571429'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '0.8857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '0.638095238095238'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '0.47619501138106085'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '0.6857142857142856'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '0.6952380952380952'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '0.5238095238095237'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '0.5904761904761905'
+ }
+ ]
+ }
+ ]
+ }
]
- }
- ]
- }
+ }
];
+export function convertDatesMultipleSeries(multipleSeries) {
+ const convertedMultiple = multipleSeries;
+ multipleSeries.forEach((column, index) => {
+ let convertedResult = [];
+ convertedResult = column.queries[0].result.map((resultObj) => {
+ const convertedMetrics = {};
+ convertedMetrics.values = resultObj.values.map(val => ({
+ time: new Date(val.time),
+ value: val.value,
+ }));
+ convertedMetrics.metric = resultObj.metric;
+ return convertedMetrics;
+ });
+ convertedMultiple[index].queries[0].result = convertedResult;
+ });
+ return convertedMultiple;
+}
+
export function MonitorMockInterceptor(request, next) {
const body = responseMockData[request.method.toUpperCase()][request.url];
diff --git a/spec/javascripts/monitoring/monitoring_paths_spec.js b/spec/javascripts/monitoring/monitoring_paths_spec.js
new file mode 100644
index 00000000000..d39db945e17
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_paths_spec.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import MonitoringPaths from '~/monitoring/components/monitoring_paths.vue';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringPaths);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
+
+describe('Monitoring Paths', () => {
+ it('renders two paths to represent a line and the area underneath it', () => {
+ const component = createComponent({
+ generatedLinePath: timeSeries[0].linePath,
+ generatedAreaPath: timeSeries[0].areaPath,
+ lineColor: '#ccc',
+ areaColor: '#fff',
+ });
+ const metricArea = component.$el.querySelector('.metric-area');
+ const metricLine = component.$el.querySelector('.metric-line');
+
+ expect(metricArea.getAttribute('fill')).toBe('#fff');
+ expect(metricArea.getAttribute('d')).toBe(timeSeries[0].areaPath);
+ expect(metricLine.getAttribute('stroke')).toBe('#ccc');
+ expect(metricLine.getAttribute('d')).toBe(timeSeries[0].linePath);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
index 20c1e6a0005..88aa7659275 100644
--- a/spec/javascripts/monitoring/monitoring_store_spec.js
+++ b/spec/javascripts/monitoring/monitoring_store_spec.js
@@ -5,10 +5,10 @@ describe('MonitoringStore', () => {
this.store = new MonitoringStore();
this.store.storeMetrics(MonitoringMock.data);
- it('contains one group that contains two queries sorted by priority in one row', () => {
+ it('contains one group that contains two queries sorted by priority', () => {
expect(this.store.groups).toBeDefined();
expect(this.store.groups.length).toEqual(1);
- expect(this.store.groups[0].metrics.length).toEqual(1);
+ expect(this.store.groups[0].metrics.length).toEqual(2);
});
it('gets the metrics count for every group', () => {
diff --git a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js
new file mode 100644
index 00000000000..3daf6bf82df
--- /dev/null
+++ b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js
@@ -0,0 +1,21 @@
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
+
+describe('Multiple time series', () => {
+ it('createTimeSeries returned array contains an object for each element', () => {
+ expect(typeof timeSeries[0].linePath).toEqual('string');
+ expect(typeof timeSeries[0].areaPath).toEqual('string');
+ expect(typeof timeSeries[0].timeSeriesScaleX).toEqual('function');
+ expect(typeof timeSeries[0].areaColor).toEqual('string');
+ expect(typeof timeSeries[0].lineColor).toEqual('string');
+ expect(timeSeries[0].values instanceof Array).toEqual(true);
+ });
+
+ it('createTimeSeries returns an array', () => {
+ expect(timeSeries instanceof Array).toEqual(true);
+ expect(timeSeries.length).toEqual(2);
+ });
+});
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
deleted file mode 100644
index 3d36bb3e4d4..00000000000
--- a/spec/javascripts/project_title_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/* global Project */
-
-import 'select2/select2';
-import '~/gl_dropdown';
-import '~/api';
-import '~/project_select';
-import '~/project';
-
-describe('Project Title', () => {
- const dummyApiVersion = 'v3000';
- preloadFixtures('issues/open-issue.html.raw');
- loadJSONFixtures('projects.json');
-
- beforeEach(() => {
- loadFixtures('issues/open-issue.html.raw');
-
- window.gon = {};
- window.gon.api_version = dummyApiVersion;
-
- // eslint-disable-next-line no-new
- new Project();
- });
-
- describe('project list', () => {
- let reqUrl;
- let reqData;
-
- beforeEach(() => {
- const fakeResponseData = getJSONFixture('projects.json');
- spyOn(jQuery, 'ajax').and.callFake((req) => {
- const def = $.Deferred();
- reqUrl = req.url;
- reqData = req.data;
- def.resolve(fakeResponseData);
- return def.promise();
- });
- });
-
- it('toggles dropdown', () => {
- const $menu = $('.js-dropdown-menu-projects');
- window.gon.current_user_id = 1;
- $('.js-projects-dropdown-toggle').click();
- expect($menu).toHaveClass('open');
- expect(reqUrl).toBe(`/api/${dummyApiVersion}/projects.json?simple=true`);
- expect(reqData).toEqual({
- search: '',
- order_by: 'last_activity_at',
- per_page: 20,
- membership: true,
- });
- $menu.find('.dropdown-menu-close-icon').click();
- expect($menu).not.toHaveClass('open');
- });
- });
-
- afterEach(() => {
- window.gon = {};
- });
-});
diff --git a/spec/javascripts/projects_dropdown/components/app_spec.js b/spec/javascripts/projects_dropdown/components/app_spec.js
new file mode 100644
index 00000000000..42f0f6fc1af
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/app_spec.js
@@ -0,0 +1,348 @@
+import Vue from 'vue';
+
+import bp from '~/breakpoints';
+import appComponent from '~/projects_dropdown/components/app.vue';
+import eventHub from '~/projects_dropdown/event_hub';
+import ProjectsStore from '~/projects_dropdown/store/projects_store';
+import ProjectsService from '~/projects_dropdown/service/projects_service';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { currentSession, mockProject, mockRawProject } from '../mock_data';
+
+const createComponent = () => {
+ gon.api_version = currentSession.apiVersion;
+ const Component = Vue.extend(appComponent);
+ const store = new ProjectsStore();
+ const service = new ProjectsService(currentSession.username);
+
+ return mountComponent(Component, {
+ store,
+ service,
+ currentUserName: currentSession.username,
+ currentProject: currentSession.project,
+ });
+};
+
+const returnServicePromise = (data, failed) => new Promise((resolve, reject) => {
+ if (failed) {
+ reject(data);
+ } else {
+ resolve({
+ json() {
+ return data;
+ },
+ });
+ }
+});
+
+describe('AppComponent', () => {
+ describe('computed', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('frequentProjects', () => {
+ it('should return list of frequently accessed projects from store', () => {
+ expect(vm.frequentProjects).toBeDefined();
+ expect(vm.frequentProjects.length).toBe(0);
+
+ vm.store.setFrequentProjects([mockProject]);
+ expect(vm.frequentProjects).toBeDefined();
+ expect(vm.frequentProjects.length).toBe(1);
+ });
+ });
+
+ describe('searchProjects', () => {
+ it('should return list of frequently accessed projects from store', () => {
+ expect(vm.searchProjects).toBeDefined();
+ expect(vm.searchProjects.length).toBe(0);
+
+ vm.store.setSearchedProjects([mockRawProject]);
+ expect(vm.searchProjects).toBeDefined();
+ expect(vm.searchProjects.length).toBe(1);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('toggleFrequentProjectsList', () => {
+ it('should toggle props which control visibility of Frequent Projects list from state passed', () => {
+ vm.toggleFrequentProjectsList(true);
+ expect(vm.isLoadingProjects).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+
+ vm.toggleFrequentProjectsList(false);
+ expect(vm.isLoadingProjects).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ });
+ });
+
+ describe('toggleSearchProjectsList', () => {
+ it('should toggle props which control visibility of Searched Projects list from state passed', () => {
+ vm.toggleSearchProjectsList(true);
+ expect(vm.isLoadingProjects).toBeFalsy();
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+
+ vm.toggleSearchProjectsList(false);
+ expect(vm.isLoadingProjects).toBeTruthy();
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ });
+ });
+
+ describe('toggleLoader', () => {
+ it('should toggle props which control visibility of list loading animation from state passed', () => {
+ vm.toggleLoader(true);
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ expect(vm.isLoadingProjects).toBeTruthy();
+
+ vm.toggleLoader(false);
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+ expect(vm.isLoadingProjects).toBeFalsy();
+ });
+ });
+
+ describe('fetchFrequentProjects', () => {
+ it('should set props for loading animation to `true` while frequent projects list is being loaded', () => {
+ spyOn(vm, 'toggleLoader');
+
+ vm.fetchFrequentProjects();
+ expect(vm.isLocalStorageFailed).toBeFalsy();
+ expect(vm.toggleLoader).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for loading animation to `false` and props for frequent projects list to `true` once data is loaded', () => {
+ const mockData = [mockProject];
+
+ spyOn(vm.service, 'getFrequentProjects').and.returnValue(mockData);
+ spyOn(vm.store, 'setFrequentProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.store.setFrequentProjects).toHaveBeenCalledWith(mockData);
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for failure message to `true` when method fails to fetch frequent projects list', () => {
+ spyOn(vm.service, 'getFrequentProjects').and.returnValue(null);
+ spyOn(vm.store, 'setFrequentProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ expect(vm.isLocalStorageFailed).toBeFalsy();
+
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.store.setFrequentProjects).toHaveBeenCalledWith([]);
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.isLocalStorageFailed).toBeTruthy();
+ });
+
+ it('should set props for search results list to `true` if search query was already made previously', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('md');
+ spyOn(vm.service, 'getFrequentProjects');
+ spyOn(vm, 'toggleSearchProjectsList');
+
+ vm.searchQuery = 'test';
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).not.toHaveBeenCalled();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for frequent projects list to `true` if search query was already made but screen size is less than 768px', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getFrequentProjects');
+
+ vm.searchQuery = 'test';
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.toggleSearchProjectsList).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('fetchSearchedProjects', () => {
+ const searchQuery = 'test';
+
+ it('should perform search with provided search query', (done) => {
+ const mockData = [mockRawProject];
+ spyOn(vm, 'toggleLoader');
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getSearchedProjects').and.returnValue(returnServicePromise(mockData));
+ spyOn(vm.store, 'setSearchedProjects');
+
+ vm.fetchSearchedProjects(searchQuery);
+ setTimeout(() => {
+ expect(vm.searchQuery).toBe(searchQuery);
+ expect(vm.toggleLoader).toHaveBeenCalledWith(true);
+ expect(vm.service.getSearchedProjects).toHaveBeenCalledWith(searchQuery);
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.store.setSearchedProjects).toHaveBeenCalledWith(mockData);
+ done();
+ }, 0);
+ });
+
+ it('should update props for showing search failure', (done) => {
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getSearchedProjects').and.returnValue(returnServicePromise({}, true));
+
+ vm.fetchSearchedProjects(searchQuery);
+ setTimeout(() => {
+ expect(vm.searchQuery).toBe(searchQuery);
+ expect(vm.service.getSearchedProjects).toHaveBeenCalledWith(searchQuery);
+ expect(vm.isSearchFailed).toBeTruthy();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ done();
+ }, 0);
+ });
+ });
+
+ describe('logCurrentProjectAccess', () => {
+ it('should log current project access via service', (done) => {
+ spyOn(vm.service, 'logProjectAccess');
+
+ vm.currentProject = mockProject;
+ vm.logCurrentProjectAccess();
+
+ setTimeout(() => {
+ expect(vm.service.logProjectAccess).toHaveBeenCalledWith(mockProject);
+ done();
+ }, 1);
+ });
+ });
+
+ describe('handleSearchClear', () => {
+ it('should show frequent projects list when search input is cleared', () => {
+ spyOn(vm.store, 'clearSearchedProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ vm.handleSearchClear();
+
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.store.clearSearchedProjects).toHaveBeenCalled();
+ expect(vm.searchQuery).toBe('');
+ });
+ });
+
+ describe('handleSearchFailure', () => {
+ it('should show failure message within dropdown', () => {
+ spyOn(vm, 'toggleSearchProjectsList');
+
+ vm.handleSearchFailure();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.isSearchFailed).toBeTruthy();
+ });
+ });
+ });
+
+ describe('created', () => {
+ it('should bind event listeners on eventHub', (done) => {
+ spyOn(eventHub, '$on');
+
+ createComponent().$mount();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$on).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchProjects', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchCleared', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchFailed', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('beforeDestroy', () => {
+ it('should unbind event listeners on eventHub', (done) => {
+ const vm = createComponent();
+ spyOn(eventHub, '$off');
+
+ vm.$mount();
+ vm.$destroy();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$off).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchProjects', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchCleared', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchFailed', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render search input', () => {
+ expect(vm.$el.querySelector('.search-input-container')).toBeDefined();
+ });
+
+ it('should render loading animation', (done) => {
+ vm.toggleLoader(true);
+ Vue.nextTick(() => {
+ const loadingEl = vm.$el.querySelector('.loading-animation');
+
+ expect(loadingEl).toBeDefined();
+ expect(loadingEl.classList.contains('prepend-top-20')).toBeTruthy();
+ expect(loadingEl.querySelector('i').getAttribute('aria-label')).toBe('Loading projects');
+ done();
+ });
+ });
+
+ it('should render frequent projects list header', (done) => {
+ vm.toggleFrequentProjectsList(true);
+ Vue.nextTick(() => {
+ const sectionHeaderEl = vm.$el.querySelector('.section-header');
+
+ expect(sectionHeaderEl).toBeDefined();
+ expect(sectionHeaderEl.innerText.trim()).toBe('Frequently visited');
+ done();
+ });
+ });
+
+ it('should render frequent projects list', (done) => {
+ vm.toggleFrequentProjectsList(true);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.projects-list-frequent-container')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should render searched projects list', (done) => {
+ vm.toggleSearchProjectsList(true);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.section-header')).toBe(null);
+ expect(vm.$el.querySelector('.projects-list-search-container')).toBeDefined();
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js
new file mode 100644
index 00000000000..fcd0f6a3630
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js
@@ -0,0 +1,72 @@
+import Vue from 'vue';
+
+import projectsListFrequentComponent from '~/projects_dropdown/components/projects_list_frequent.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockFrequents } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListFrequentComponent);
+
+ return mountComponent(Component, {
+ projects: mockFrequents,
+ localStorageFailed: false,
+ });
+};
+
+describe('ProjectsListFrequentComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isListEmpty', () => {
+ it('should return `true` or `false` representing whether if `projects` is empty of not', () => {
+ vm.projects = [];
+ expect(vm.isListEmpty).toBeTruthy();
+
+ vm.projects = mockFrequents;
+ expect(vm.isListEmpty).toBeFalsy();
+ });
+ });
+
+ describe('listEmptyMessage', () => {
+ it('should return appropriate empty list message based on value of `localStorageFailed` prop', () => {
+ vm.localStorageFailed = true;
+ expect(vm.listEmptyMessage).toBe('This feature requires browser localStorage support');
+
+ vm.localStorageFailed = false;
+ expect(vm.listEmptyMessage).toBe('Projects you visit often will appear here');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element with list of projects', (done) => {
+ vm.projects = mockFrequents;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.classList.contains('projects-list-frequent-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('ul.list-unstyled').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(5);
+ done();
+ });
+ });
+
+ it('should render component element with empty message', (done) => {
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
new file mode 100644
index 00000000000..171629fcd6b
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+
+import projectsListItemComponent from '~/projects_dropdown/components/projects_list_item.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockProject } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListItemComponent);
+
+ return mountComponent(Component, {
+ projectId: mockProject.id,
+ projectName: mockProject.name,
+ namespace: mockProject.namespace,
+ webUrl: mockProject.webUrl,
+ avatarUrl: mockProject.avatarUrl,
+ });
+};
+
+describe('ProjectsListItemComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('hasAvatar', () => {
+ it('should return `true` or `false` if whether avatar is present or not', () => {
+ vm.avatarUrl = 'path/to/avatar.png';
+ expect(vm.hasAvatar).toBeTruthy();
+
+ vm.avatarUrl = null;
+ expect(vm.hasAvatar).toBeFalsy();
+ });
+ });
+
+ describe('highlightedProjectName', () => {
+ it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => {
+ vm.matcher = 'lab';
+ expect(vm.highlightedProjectName).toContain('<b>Lab</b>');
+ });
+
+ it('should return project name as it is if `matcher` is not available', () => {
+ vm.matcher = null;
+ expect(vm.highlightedProjectName).toBe(mockProject.name);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element', () => {
+ expect(vm.$el.classList.contains('projects-list-item-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('a').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-item-avatar-container').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-item-metadata-container').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-title').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-namespace').length).toBe(1);
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js
new file mode 100644
index 00000000000..59fc2dedba5
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+
+import projectsListSearchComponent from '~/projects_dropdown/components/projects_list_search.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockProject } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListSearchComponent);
+
+ return mountComponent(Component, {
+ projects: [mockProject],
+ matcher: 'lab',
+ searchFailed: false,
+ });
+};
+
+describe('ProjectsListSearchComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isListEmpty', () => {
+ it('should return `true` or `false` representing whether if `projects` is empty of not', () => {
+ vm.projects = [];
+ expect(vm.isListEmpty).toBeTruthy();
+
+ vm.projects = [mockProject];
+ expect(vm.isListEmpty).toBeFalsy();
+ });
+ });
+
+ describe('listEmptyMessage', () => {
+ it('should return appropriate empty list message based on value of `searchFailed` prop', () => {
+ vm.searchFailed = true;
+ expect(vm.listEmptyMessage).toBe('Something went wrong on our end.');
+
+ vm.searchFailed = false;
+ expect(vm.listEmptyMessage).toBe('No projects matched your query');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element with list of projects', (done) => {
+ vm.projects = [mockProject];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.classList.contains('projects-list-search-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('ul.list-unstyled').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(1);
+ done();
+ });
+ });
+
+ it('should render component element with empty message', (done) => {
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+
+ it('should render component element with failure message', (done) => {
+ vm.searchFailed = true;
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty.section-failure').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/search_spec.js b/spec/javascripts/projects_dropdown/components/search_spec.js
new file mode 100644
index 00000000000..f2a23e33325
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/search_spec.js
@@ -0,0 +1,101 @@
+import Vue from 'vue';
+
+import searchComponent from '~/projects_dropdown/components/search.vue';
+import eventHub from '~/projects_dropdown/event_hub';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(searchComponent);
+
+ return mountComponent(Component);
+};
+
+describe('SearchComponent', () => {
+ describe('methods', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('setFocus', () => {
+ it('should set focus to search input', () => {
+ spyOn(vm.$refs.search, 'focus');
+
+ vm.setFocus();
+ expect(vm.$refs.search.focus).toHaveBeenCalled();
+ });
+ });
+
+ describe('emitSearchEvents', () => {
+ it('should emit `searchProjects` event via eventHub when `searchQuery` present', () => {
+ const searchQuery = 'test';
+ spyOn(eventHub, '$emit');
+ vm.searchQuery = searchQuery;
+ vm.emitSearchEvents();
+ expect(eventHub.$emit).toHaveBeenCalledWith('searchProjects', searchQuery);
+ });
+
+ it('should emit `searchCleared` event via eventHub when `searchQuery` is cleared', () => {
+ spyOn(eventHub, '$emit');
+ vm.searchQuery = '';
+ vm.emitSearchEvents();
+ expect(eventHub.$emit).toHaveBeenCalledWith('searchCleared');
+ });
+ });
+ });
+
+ describe('mounted', () => {
+ it('should listen `dropdownOpen` event', (done) => {
+ spyOn(eventHub, '$on');
+ createComponent();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$on).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('beforeDestroy', () => {
+ it('should unbind event listeners on eventHub', (done) => {
+ const vm = createComponent();
+ spyOn(eventHub, '$off');
+
+ vm.$mount();
+ vm.$destroy();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$off).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render component element', () => {
+ const inputEl = vm.$el.querySelector('input.form-control');
+
+ expect(vm.$el.classList.contains('search-input-container')).toBeTruthy();
+ expect(vm.$el.classList.contains('hidden-xs')).toBeTruthy();
+ expect(inputEl).not.toBe(null);
+ expect(inputEl.getAttribute('placeholder')).toBe('Search projects');
+ expect(vm.$el.querySelector('.search-icon')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/mock_data.js b/spec/javascripts/projects_dropdown/mock_data.js
new file mode 100644
index 00000000000..d6a79fb8ac1
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/mock_data.js
@@ -0,0 +1,96 @@
+export const currentSession = {
+ username: 'root',
+ storageKey: 'root/frequent-projects',
+ apiVersion: 'v4',
+ project: {
+ id: 1,
+ name: 'dummy-project',
+ namespace: 'SamepleGroup / Dummy-Project',
+ webUrl: 'http://127.0.0.1/samplegroup/dummy-project',
+ avatarUrl: null,
+ lastAccessedOn: Date.now(),
+ },
+};
+
+export const mockProject = {
+ id: 1,
+ name: 'GitLab Community Edition',
+ namespace: 'gitlab-org / gitlab-ce',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatarUrl: null,
+};
+
+export const mockRawProject = {
+ id: 1,
+ name: 'GitLab Community Edition',
+ name_with_namespace: 'gitlab-org / gitlab-ce',
+ web_url: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatar_url: null,
+};
+
+export const mockFrequents = [
+ {
+ id: 1,
+ name: 'GitLab Community Edition',
+ namespace: 'gitlab-org / gitlab-ce',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatarUrl: null,
+ },
+ {
+ id: 2,
+ name: 'GitLab CI',
+ namespace: 'gitlab-org / gitlab-ci',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ci',
+ avatarUrl: null,
+ },
+ {
+ id: 3,
+ name: 'Typeahead.Js',
+ namespace: 'twitter / typeahead-js',
+ webUrl: 'http://127.0.0.1:3000/twitter/typeahead-js',
+ avatarUrl: '/uploads/-/system/project/avatar/7/TWBS.png',
+ },
+ {
+ id: 4,
+ name: 'Intel',
+ namespace: 'platform / hardware / bsp / intel',
+ webUrl: 'http://127.0.0.1:3000/platform/hardware/bsp/intel',
+ avatarUrl: null,
+ },
+ {
+ id: 5,
+ name: 'v4.4',
+ namespace: 'platform / hardware / bsp / kernel / common / v4.4',
+ webUrl: 'http://localhost:3000/platform/hardware/bsp/kernel/common/v4.4',
+ avatarUrl: null,
+ },
+];
+
+export const unsortedFrequents = [
+ { id: 1, frequency: 12, lastAccessedOn: 1491400843391 },
+ { id: 2, frequency: 14, lastAccessedOn: 1488240890738 },
+ { id: 3, frequency: 44, lastAccessedOn: 1497675908472 },
+ { id: 4, frequency: 8, lastAccessedOn: 1497979281815 },
+ { id: 5, frequency: 34, lastAccessedOn: 1488089211943 },
+ { id: 6, frequency: 14, lastAccessedOn: 1493517292488 },
+ { id: 7, frequency: 42, lastAccessedOn: 1486815299875 },
+ { id: 8, frequency: 33, lastAccessedOn: 1500762279114 },
+ { id: 10, frequency: 46, lastAccessedOn: 1483251641543 },
+];
+
+/**
+ * This const has a specific order which tests authenticity
+ * of `ProjectsService.getTopFrequentProjects` method so
+ * DO NOT change order of items in this const.
+ */
+export const sortedFrequents = [
+ { id: 10, frequency: 46, lastAccessedOn: 1483251641543 },
+ { id: 3, frequency: 44, lastAccessedOn: 1497675908472 },
+ { id: 7, frequency: 42, lastAccessedOn: 1486815299875 },
+ { id: 5, frequency: 34, lastAccessedOn: 1488089211943 },
+ { id: 8, frequency: 33, lastAccessedOn: 1500762279114 },
+ { id: 6, frequency: 14, lastAccessedOn: 1493517292488 },
+ { id: 2, frequency: 14, lastAccessedOn: 1488240890738 },
+ { id: 1, frequency: 12, lastAccessedOn: 1491400843391 },
+ { id: 4, frequency: 8, lastAccessedOn: 1497979281815 },
+];
diff --git a/spec/javascripts/projects_dropdown/service/projects_service_spec.js b/spec/javascripts/projects_dropdown/service/projects_service_spec.js
new file mode 100644
index 00000000000..d5dd8b3449a
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/service/projects_service_spec.js
@@ -0,0 +1,179 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+import bp from '~/breakpoints';
+import ProjectsService from '~/projects_dropdown/service/projects_service';
+import { FREQUENT_PROJECTS } from '~/projects_dropdown/constants';
+import { currentSession, unsortedFrequents, sortedFrequents } from '../mock_data';
+
+Vue.use(VueResource);
+
+FREQUENT_PROJECTS.MAX_COUNT = 3;
+
+describe('ProjectsService', () => {
+ let service;
+
+ beforeEach(() => {
+ gon.api_version = currentSession.apiVersion;
+ gon.current_user_id = 1;
+ service = new ProjectsService(currentSession.username);
+ });
+
+ describe('contructor', () => {
+ it('should initialize default properties of class', () => {
+ expect(service.isLocalStorageAvailable).toBeTruthy();
+ expect(service.currentUserName).toBe(currentSession.username);
+ expect(service.storageKey).toBe(currentSession.storageKey);
+ expect(service.projectsPath).toBeDefined();
+ });
+ });
+
+ describe('getSearchedProjects', () => {
+ it('should return promise from VueResource HTTP GET', () => {
+ spyOn(service.projectsPath, 'get').and.stub();
+
+ const searchQuery = 'lab';
+ const queryParams = {
+ simple: false,
+ per_page: 20,
+ membership: true,
+ order_by: 'last_activity_at',
+ search: searchQuery,
+ };
+
+ service.getSearchedProjects(searchQuery);
+ expect(service.projectsPath.get).toHaveBeenCalledWith(queryParams);
+ });
+ });
+
+ describe('logProjectAccess', () => {
+ let storage;
+
+ beforeEach(() => {
+ storage = {};
+
+ spyOn(window.localStorage, 'setItem').and.callFake((storageKey, value) => {
+ storage[storageKey] = value;
+ });
+
+ spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
+ if (storage[storageKey]) {
+ return storage[storageKey];
+ }
+
+ return null;
+ });
+ });
+
+ it('should create a project store if it does not exist and adds a project', () => {
+ service.logProjectAccess(currentSession.project);
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(1);
+ expect(projects[0].frequency).toBe(1);
+ expect(projects[0].lastAccessedOn).toBeDefined();
+ });
+
+ it('should prevent inserting same report multiple times into store', () => {
+ service.logProjectAccess(currentSession.project);
+ service.logProjectAccess(currentSession.project);
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(1);
+ });
+
+ it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
+ let projects;
+ spyOn(Math, 'abs').and.returnValue(3600001); // this will lead to `diff` > 1;
+ service.logProjectAccess(currentSession.project);
+
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].frequency).toBe(1);
+
+ service.logProjectAccess(currentSession.project);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].frequency).toBe(2);
+ expect(projects[0].lastAccessedOn).not.toBe(currentSession.project.lastAccessedOn);
+ });
+
+ it('should always update project metadata', () => {
+ let projects;
+ const oldProject = {
+ ...currentSession.project,
+ };
+
+ const newProject = {
+ ...currentSession.project,
+ name: 'New Name',
+ avatarUrl: 'new/avatar.png',
+ namespace: 'New / Namespace',
+ webUrl: 'http://localhost/new/web/url',
+ };
+
+ service.logProjectAccess(oldProject);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].name).toBe(oldProject.name);
+ expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl);
+ expect(projects[0].namespace).toBe(oldProject.namespace);
+ expect(projects[0].webUrl).toBe(oldProject.webUrl);
+
+ service.logProjectAccess(newProject);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].name).toBe(newProject.name);
+ expect(projects[0].avatarUrl).toBe(newProject.avatarUrl);
+ expect(projects[0].namespace).toBe(newProject.namespace);
+ expect(projects[0].webUrl).toBe(newProject.webUrl);
+ });
+
+ it('should not add more than 20 projects in store', () => {
+ for (let i = 1; i <= 5; i += 1) {
+ const project = Object.assign(currentSession.project, { id: i });
+ service.logProjectAccess(project);
+ }
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(3);
+ });
+ });
+
+ describe('getTopFrequentProjects', () => {
+ let storage = {};
+
+ beforeEach(() => {
+ storage[currentSession.storageKey] = JSON.stringify(unsortedFrequents);
+
+ spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
+ if (storage[storageKey]) {
+ return storage[storageKey];
+ }
+
+ return null;
+ });
+ });
+
+ it('should return top 5 frequently accessed projects for desktop screens', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('md');
+ const frequentProjects = service.getTopFrequentProjects();
+
+ expect(frequentProjects.length).toBe(5);
+ frequentProjects.forEach((project, index) => {
+ expect(project.id).toBe(sortedFrequents[index].id);
+ });
+ });
+
+ it('should return top 3 frequently accessed projects for mobile screens', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
+ const frequentProjects = service.getTopFrequentProjects();
+
+ expect(frequentProjects.length).toBe(3);
+ frequentProjects.forEach((project, index) => {
+ expect(project.id).toBe(sortedFrequents[index].id);
+ });
+ });
+
+ it('should return empty array if there are no projects available in store', () => {
+ storage = {};
+ expect(service.getTopFrequentProjects().length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/store/projects_store_spec.js b/spec/javascripts/projects_dropdown/store/projects_store_spec.js
new file mode 100644
index 00000000000..e57399d37cd
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/store/projects_store_spec.js
@@ -0,0 +1,41 @@
+import ProjectsStore from '~/projects_dropdown/store/projects_store';
+import { mockProject, mockRawProject } from '../mock_data';
+
+describe('ProjectsStore', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new ProjectsStore();
+ });
+
+ describe('setFrequentProjects', () => {
+ it('should set frequent projects list to state', () => {
+ store.setFrequentProjects([mockProject]);
+
+ expect(store.getFrequentProjects().length).toBe(1);
+ expect(store.getFrequentProjects()[0].id).toBe(mockProject.id);
+ });
+ });
+
+ describe('setSearchedProjects', () => {
+ it('should set searched projects list to state', () => {
+ store.setSearchedProjects([mockRawProject]);
+
+ const processedProjects = store.getSearchedProjects();
+ expect(processedProjects.length).toBe(1);
+ expect(processedProjects[0].id).toBe(mockRawProject.id);
+ expect(processedProjects[0].namespace).toBe(mockRawProject.name_with_namespace);
+ expect(processedProjects[0].webUrl).toBe(mockRawProject.web_url);
+ expect(processedProjects[0].avatarUrl).toBe(mockRawProject.avatar_url);
+ });
+ });
+
+ describe('clearSearchedProjects', () => {
+ it('should clear searched projects list from state', () => {
+ store.setSearchedProjects([mockRawProject]);
+ expect(store.getSearchedProjects().length).toBe(1);
+ store.clearSearchedProjects();
+ expect(store.getSearchedProjects().length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/javascripts/vue_shared/components/identicon_spec.js
index 4f194e5a64e..647680f00f7 100644
--- a/spec/javascripts/vue_shared/components/identicon_spec.js
+++ b/spec/javascripts/vue_shared/components/identicon_spec.js
@@ -1,25 +1,30 @@
import Vue from 'vue';
import identiconComponent from '~/vue_shared/components/identicon.vue';
-const createComponent = () => {
+const createComponent = (sizeClass) => {
const Component = Vue.extend(identiconComponent);
return new Component({
propsData: {
entityId: 1,
entityName: 'entity-name',
+ sizeClass,
},
}).$mount();
};
describe('IdenticonComponent', () => {
- let vm;
+ describe('computed', () => {
+ let vm;
- beforeEach(() => {
- vm = createComponent();
- });
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
- describe('computed', () => {
describe('identiconStyles', () => {
it('should return styles attribute value with `background-color` property', () => {
vm.entityId = 4;
@@ -48,9 +53,20 @@ describe('IdenticonComponent', () => {
describe('template', () => {
it('should render identicon', () => {
+ const vm = createComponent();
+
expect(vm.$el.nodeName).toBe('DIV');
expect(vm.$el.classList.contains('identicon')).toBeTruthy();
+ expect(vm.$el.classList.contains('s40')).toBeTruthy();
expect(vm.$el.getAttribute('style').indexOf('background-color') > -1).toBeTruthy();
+ vm.$destroy();
+ });
+
+ it('should render identicon with provided sizing class', () => {
+ const vm = createComponent('s32');
+
+ expect(vm.$el.classList.contains('s32')).toBeTruthy();
+ vm.$destroy();
});
});
});
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index 0d5fffa38ff..c56b08b18a2 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -214,7 +214,7 @@ end
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
#
-describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170608152748 do
+describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do
let(:migration) { described_class.new }
let(:project) { create(:project_empty_repo) }
let(:author) { create(:user) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 4cfb4b7d357..08959e7bc16 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -916,27 +916,37 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#find_branch' do
- it 'should return a Branch for master' do
- branch = repository.find_branch('master')
+ shared_examples 'finding a branch' do
+ it 'should return a Branch for master' do
+ branch = repository.find_branch('master')
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
- end
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
- it 'should handle non-existent branch' do
- branch = repository.find_branch('this-is-garbage')
+ it 'should handle non-existent branch' do
+ branch = repository.find_branch('this-is-garbage')
- expect(branch).to eq(nil)
+ expect(branch).to eq(nil)
+ end
end
- it 'should reload Rugged::Repository and return master' do
- expect(Rugged::Repository).to receive(:new).twice.and_call_original
+ context 'when Gitaly find_branch feature is enabled' do
+ it_behaves_like 'finding a branch'
+ end
- repository.find_branch('master')
- branch = repository.find_branch('master', force_reload: true)
+ context 'when Gitaly find_branch feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding a branch'
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
+ it 'should reload Rugged::Repository and return master' do
+ expect(Rugged::Repository).to receive(:new).twice.and_call_original
+
+ repository.find_branch('master')
+ branch = repository.find_branch('master', force_reload: true)
+
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
end
end
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index e521fcc6dc1..b07462e4978 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -2,45 +2,9 @@ require 'rails_helper'
describe Gitlab::Gpg::Commit do
describe '#signature' do
- let!(:project) { create :project, :repository, path: 'sample-project' }
- let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
-
- context 'unsigned commit' do
- it 'returns nil' do
- expect(described_class.new(project, commit_sha).signature).to be_nil
- end
- end
-
- context 'known and verified public key' do
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: create(:user, email: GpgHelpers::User1.emails.first)
- end
-
- before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
-
- it 'returns a valid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
- commit_sha: commit_sha,
- project: project,
- gpg_key: gpg_key,
- gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- gpg_key_user_name: GpgHelpers::User1.names.first,
- gpg_key_user_email: GpgHelpers::User1.emails.first,
- valid_signature: true
- )
- end
-
+ shared_examples 'returns the cached signature on second call' do
it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
+ gpg_commit = described_class.new(commit)
expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature
@@ -51,11 +15,140 @@ describe Gitlab::Gpg::Commit do
end
end
- context 'known but unverified public key' do
- let!(:gpg_key) { create :gpg_key, key: GpgHelpers::User1.public_key }
+ let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
- before do
- allow(Rugged::Commit).to receive(:extract_signature)
+ context 'unsigned commit' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
+ it 'returns nil' do
+ expect(described_class.new(commit).signature).to be_nil
+ end
+ end
+
+ context 'known key' do
+ context 'user matches the key uid' do
+ context 'user email matches the email committer' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
+
+ let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns a valid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'verified'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+
+ context 'user email does not match the committer email, but is the same user' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
+
+ let(:user) do
+ create(:user, email: GpgHelpers::User1.emails.first).tap do |user|
+ create :email, user: user, email: GpgHelpers::User2.emails.first
+ end
+ end
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'same_user_different_email'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+
+ context 'user email does not match the committer email' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
+
+ let(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'other_user'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+ end
+
+ context 'user does not match the key uid' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
+ let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
.and_return(
[
@@ -63,33 +156,27 @@ describe Gitlab::Gpg::Commit do
GpgHelpers::User1.signed_commit_base_data
]
)
- end
-
- it 'returns an invalid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
- commit_sha: commit_sha,
- project: project,
- gpg_key: gpg_key,
- gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- gpg_key_user_name: GpgHelpers::User1.names.first,
- gpg_key_user_email: GpgHelpers::User1.emails.first,
- valid_signature: false
- )
- end
-
- it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
-
- expect(gpg_commit).to receive(:using_keychain).and_call_original
- gpg_commit.signature
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'unverified_key'
+ )
+ end
- # consecutive call
- expect(gpg_commit).not_to receive(:using_keychain).and_call_original
- gpg_commit.signature
+ it_behaves_like 'returns the cached signature on second call'
end
end
- context 'unknown public key' do
+ context 'unknown key' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
before do
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
@@ -102,27 +189,18 @@ describe Gitlab::Gpg::Commit do
end
it 'returns an invalid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
+ expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha,
project: project,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: nil,
gpg_key_user_email: nil,
- valid_signature: false
+ verification_status: 'unknown_key'
)
end
- it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
-
- expect(gpg_commit).to receive(:using_keychain).and_call_original
- gpg_commit.signature
-
- # consecutive call
- expect(gpg_commit).not_to receive(:using_keychain).and_call_original
- gpg_commit.signature
- end
+ it_behaves_like 'returns the cached signature on second call'
end
end
end
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index 4de4419de27..b9fd4d02156 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -4,8 +4,29 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
describe '#run' do
let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:raw_commit) do
+ raw_commit = double(
+ :raw_commit,
+ signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ],
+ sha: commit_sha,
+ committer_email: GpgHelpers::User1.emails.first
+ )
+
+ allow(raw_commit).to receive :save!
+
+ raw_commit
+ end
+
+ let!(:commit) do
+ create :commit, git_commit: raw_commit, project: project
+ end
before do
+ allow_any_instance_of(Project).to receive(:commit).and_return(commit)
+
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
.and_return(
@@ -25,7 +46,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
end
it 'assigns the gpg key to the signature when the missing gpg key is added' do
@@ -39,7 +60,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -54,7 +75,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
end
@@ -68,7 +89,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
end
it 'updates the signature to being valid when the missing gpg key is added' do
@@ -82,7 +103,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -97,7 +118,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
)
end
end
@@ -115,7 +136,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
end
it 'updates the signature to being valid when the user updates the email address' do
@@ -123,7 +144,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
key: GpgHelpers::User1.public_key,
user: user
- expect(invalid_gpg_signature.reload.valid_signature).to be_falsey
+ expect(invalid_gpg_signature.reload.verification_status).to eq 'unverified_key'
# InvalidGpgSignatureUpdater is called by the after_update hook
user.update_attributes!(email: GpgHelpers::User1.emails.first)
@@ -133,7 +154,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -147,7 +168,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unverified_key'
)
# InvalidGpgSignatureUpdater is called by the after_update hook
@@ -158,7 +179,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unverified_key'
)
end
end
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 30ad033b204..11a2aea1915 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -42,6 +42,21 @@ describe Gitlab::Gpg do
described_class.user_infos_from_key('bogus')
).to eq []
end
+
+ it 'downcases the email' do
+ public_key = double(:key)
+ fingerprints = double(:fingerprints)
+ uid = double(:uid, name: 'Nannie Bernhard', email: 'NANNIE.BERNHARD@EXAMPLE.COM')
+ raw_key = double(:raw_key, uids: [uid])
+ allow(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(public_key).and_return(fingerprints)
+ allow(GPGME::Key).to receive(:find).with(:public, anything).and_return([raw_key])
+
+ user_infos = described_class.user_infos_from_key(public_key)
+ expect(user_infos).to eq([{
+ name: 'Nannie Bernhard',
+ email: 'nannie.bernhard@example.com'
+ }])
+ end
end
describe '.current_home_dir' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 8da02b0cf00..beed4e77e8b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -264,6 +264,7 @@ project:
- statistics
- container_repositories
- uploads
+- members_and_requesters
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 5b16fc5d084..d664d371028 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -11,8 +11,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
- allow(@project.repository).to receive(:fetch_ref).and_return(true)
- allow(@project.repository.raw).to receive(:rugged_branch_exists?).and_return(false)
+ allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true)
+ allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 065b0ec6658..8e3554375e8 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -117,6 +117,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['pipelines'].first['statuses'].count { |hash| hash['type'] == 'Ci::Build' }).to eq(1)
end
+ it 'has no when YML attributes but only the DB column' do
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')))
+ expect_any_instance_of(Ci::GitlabCiYamlProcessor).not_to receive(:build_attributes)
+
+ saved_project_json
+ end
+
it 'has pipeline commits' do
expect(saved_project_json['pipelines']).not_to be_empty
end
@@ -251,15 +258,11 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:label_priority, label: group_label, priority: 1)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone)
- commit_status = create(:commit_status, project: project)
- ci_pipeline = create(:ci_pipeline,
- project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch,
- statuses: [commit_status])
+ ci_build = create(:ci_build, project: project, when: nil)
+ ci_build.pipeline.update(project: project)
+ create(:commit_status, project: project, pipeline: ci_build.pipeline)
- create(:ci_build, pipeline: ci_pipeline, project: project)
create(:milestone, project: project)
create(:note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
@@ -267,7 +270,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:note_on_commit,
author: user,
project: project,
- commit_id: ci_pipeline.sha)
+ commit_id: ci_build.pipeline.sha)
create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 27f2ce60084..122b8ee0314 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -65,6 +65,7 @@ Note:
- change_position
- resolved_at
- resolved_by_id
+- resolved_by_push
- discussion_id
- original_discussion_id
LabelLink:
@@ -278,6 +279,7 @@ CommitStatus:
- auto_canceled_by_id
- retried
- protected
+- failure_reason
Ci::Variable:
- id
- project_id
@@ -397,6 +399,7 @@ Project:
- public_builds
- last_repository_check_failed
- last_repository_check_at
+- collapse_outdated_diff_comments
- container_registry_enabled
- only_allow_merge_if_pipeline_succeeds
- has_external_issue_tracker
@@ -405,6 +408,7 @@ Project:
- only_allow_merge_if_all_discussions_are_resolved
- auto_cancel_pending_pipelines
- printing_merge_request_link_enabled
+- resolve_outdated_diff_discussions
- build_allow_git_fetch
- last_repository_updated_at
- ci_config_path
diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb
new file mode 100644
index 00000000000..c262fdfcb61
--- /dev/null
+++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::IssuablesCountForState do
+ let(:finder) do
+ double(:finder, count_by_state: { opened: 2, closed: 1 })
+ end
+
+ let(:counter) { described_class.new(finder) }
+
+ describe '#for_state_or_opened' do
+ it 'returns the number of issuables for the given state' do
+ expect(counter.for_state_or_opened(:closed)).to eq(1)
+ end
+
+ it 'returns the number of open issuables when no state is given' do
+ expect(counter.for_state_or_opened).to eq(2)
+ end
+
+ it 'returns the number of open issuables when a nil value is given' do
+ expect(counter.for_state_or_opened(nil)).to eq(2)
+ end
+ end
+
+ describe '#[]' do
+ it 'returns the number of issuables for the given state' do
+ expect(counter[:closed]).to eq(1)
+ end
+
+ it 'casts valid states from Strings to Symbols' do
+ expect(counter['closed']).to eq(1)
+ end
+
+ it 'returns 0 when using an invalid state name as a String' do
+ expect(counter['kittens']).to be_zero
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 9d7b2136dab..48d56628ed5 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -52,4 +52,124 @@ describe Gitlab::SQL::Pattern do
end
end
end
+
+ describe '.select_fuzzy_words' do
+ subject(:select_fuzzy_words) { Issue.select_fuzzy_words(query) }
+
+ context 'with a word equal to 3 chars' do
+ let(:query) { 'foo' }
+
+ it 'returns array cotaining a word' do
+ expect(select_fuzzy_words).to match_array(['foo'])
+ end
+ end
+
+ context 'with a word shorter than 3 chars' do
+ let(:query) { 'fo' }
+
+ it 'returns empty array' do
+ expect(select_fuzzy_words).to match_array([])
+ end
+ end
+
+ context 'with two words both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns array containing two words' do
+ expect(select_fuzzy_words).to match_array(%w[foo baz])
+ end
+ end
+
+ context 'with two words divided by two spaces both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns array containing two words' do
+ expect(select_fuzzy_words).to match_array(%w[foo baz])
+ end
+ end
+
+ context 'with two words equal to 3 chars and shorter than 3 chars' do
+ let(:query) { 'foo ba' }
+
+ it 'returns array containing a word' do
+ expect(select_fuzzy_words).to match_array(['foo'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote' do
+ let(:query) { '"really bar"' }
+
+ it 'returns array containing a multi-word' do
+ expect(select_fuzzy_words).to match_array(['really bar'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz' }
+
+ it 'returns array containing a multi-word and tow words' do
+ expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote missing a spece before the first double quote' do
+ let(:query) { 'foo"really bar"' }
+
+ it 'returns array containing two words with double quote' do
+ expect(select_fuzzy_words).to match_array(['foo"really', 'bar"'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote missing a spece after the second double quote' do
+ let(:query) { '"really bar"baz' }
+
+ it 'returns array containing two words with double quote' do
+ expect(select_fuzzy_words).to match_array(['"really', 'bar"baz'])
+ end
+ end
+
+ context 'with two multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz "awesome feature"' }
+
+ it 'returns array containing two multi-words and tow words' do
+ expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz', 'awesome feature'])
+ end
+ end
+ end
+
+ describe '.to_fuzzy_arel' do
+ subject(:to_fuzzy_arel) { Issue.to_fuzzy_arel(:title, query) }
+
+ context 'with a word equal to 3 chars' do
+ let(:query) { 'foo' }
+
+ it 'returns a single ILIKE condition' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.*I?LIKE '\%foo\%'/)
+ end
+ end
+
+ context 'with a word shorter than 3 chars' do
+ let(:query) { 'fo' }
+
+ it 'returns nil' do
+ expect(to_fuzzy_arel).to be_nil
+ end
+ end
+
+ context 'with two words both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns a joining LIKE condition using a AND' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%'/)
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz' }
+
+ it 'returns a joining LIKE condition using a AND' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%' AND .*title.*I?LIKE '\%really bar\%'/)
+ end
+ end
+ end
end
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
new file mode 100644
index 00000000000..7125bfcab59
--- /dev/null
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
+ let(:username) { '_this_user_will_not_exist_unless_it_is_stubbed' }
+ let(:base_dir) { Dir.mktmpdir }
+ let(:home_dir) { File.join(base_dir, "/var/lib/#{username}") }
+ let(:ssh_dir) { File.join(home_dir, '.ssh') }
+ let(:forbidden_file) { 'id_rsa' }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:user).and_return(username)
+ end
+
+ after do
+ FileUtils.rm_rf(base_dir)
+ end
+
+ it 'only whitelists safe files' do
+ expect(described_class::WHITELIST).to contain_exactly('authorized_keys', 'authorized_keys2', 'known_hosts')
+ end
+
+ describe '#skip?' do
+ subject { described_class.new.skip? }
+
+ where(user_exists: [true, false], home_dir_exists: [true, false])
+
+ with_them do
+ let(:expected_result) { !user_exists || !home_dir_exists }
+
+ before do
+ stub_user if user_exists
+ stub_home_dir if home_dir_exists
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#check?' do
+ subject { described_class.new.check? }
+
+ before do
+ stub_user
+ end
+
+ it 'fails if a forbidden file exists' do
+ stub_ssh_file(forbidden_file)
+
+ is_expected.to be_falsy
+ end
+
+ it "succeeds if the SSH directory doesn't exist" do
+ FileUtils.rm_rf(ssh_dir)
+
+ is_expected.to be_truthy
+ end
+
+ it 'succeeds if all the whitelisted files exist' do
+ described_class::WHITELIST.each do |filename|
+ stub_ssh_file(filename)
+ end
+
+ is_expected.to be_truthy
+ end
+ end
+
+ def stub_user
+ allow(File).to receive(:expand_path).with("~#{username}").and_return(home_dir)
+ end
+
+ def stub_home_dir
+ FileUtils.mkdir_p(home_dir)
+ end
+
+ def stub_ssh_file(filename)
+ FileUtils.mkdir_p(ssh_dir)
+ FileUtils.touch(File.join(ssh_dir, filename))
+ end
+end
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 4de5da984ba..9da3648400e 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -35,6 +35,20 @@ describe SystemCheck::SimpleExecutor do
end
end
+ class DynamicSkipCheck < SystemCheck::BaseCheck
+ set_name 'dynamic skip check'
+ set_skip_reason 'this is a skip reason'
+
+ def skip?
+ self.skip_reason = 'this is a dynamic skip reason'
+ true
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
class MultiCheck < SystemCheck::BaseCheck
set_name 'multi check'
@@ -127,6 +141,10 @@ describe SystemCheck::SimpleExecutor do
expect(subject.checks.size).to eq(1)
end
+
+ it 'errors out when passing multiple items' do
+ expect { subject << [SimpleCheck, OtherCheck] }.to raise_error(ArgumentError)
+ end
end
subject { described_class.new('Test') }
@@ -205,10 +223,14 @@ describe SystemCheck::SimpleExecutor do
subject.run_check(SkipCheck)
end
- it 'displays #skip_reason' do
+ it 'displays .skip_reason' do
expect { subject.run_check(SkipCheck) }.to output(/this is a skip reason/).to_stdout
end
+ it 'displays #skip_reason' do
+ expect { subject.run_check(DynamicSkipCheck) }.to output(/this is a dynamic skip reason/).to_stdout
+ end
+
it 'does not execute #check when #skip? is true' do
expect_any_instance_of(SkipCheck).not_to receive(:check?)
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
new file mode 100644
index 00000000000..cfd4021fbac
--- /dev/null
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170825104051_migrate_issues_to_ghost_user.rb')
+
+describe MigrateIssuesToGhostUser, :migration do
+ describe '#up' do
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:users) { table(:users) }
+
+ before do
+ projects.create!(name: 'gitlab')
+ user = users.create(email: 'test@example.com')
+ issues.create(title: 'Issue 1', author_id: nil, project_id: 1)
+ issues.create(title: 'Issue 2', author_id: user.id, project_id: 1)
+ end
+
+ context 'when ghost user exists' 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 }
+ end
+
+ it 'migrates issues where author = nil to the ghost user' do
+ schema_migrate_up!
+
+ 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}
+ 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)
+ end
+
+ it 'migrates issues where author = nil to the ghost user' do
+ schema_migrate_up!
+
+ 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}
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 3fe3ec17d36..08d22f166e4 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1492,10 +1492,12 @@ describe Ci::Build do
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
- let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
+ let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
+
let(:user_trigger_variable) do
- { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
+ { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false }
end
+
let(:predefined_trigger_variable) do
{ key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
end
@@ -1504,8 +1506,26 @@ describe Ci::Build do
build.trigger_request = trigger_request
end
- it { is_expected.to include(user_trigger_variable) }
- it { is_expected.to include(predefined_trigger_variable) }
+ shared_examples 'returns variables for triggers' do
+ it { is_expected.to include(user_trigger_variable) }
+ it { is_expected.to include(predefined_trigger_variable) }
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+ end
+
+ it_behaves_like 'returns variables for triggers'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+ end
+
+ it_behaves_like 'returns variables for triggers'
+ end
end
context 'when pipeline has a variable' do
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
new file mode 100644
index 00000000000..7dcf3528f73
--- /dev/null
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Ci::TriggerRequest do
+ describe 'validation' do
+ it 'be invalid if saving a variable' do
+ trigger = build(:ci_trigger_request, variables: { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
+
+ expect(trigger).not_to be_valid
+ end
+
+ it 'be valid if not saving a variable' do
+ trigger = build(:ci_trigger_request)
+
+ expect(trigger).to be_valid
+ end
+ end
+end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index f7583645e69..858ec831200 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -443,4 +443,25 @@ describe CommitStatus do
end
end
end
+
+ describe 'set failure_reason when drop' do
+ let(:commit_status) { create(:commit_status, :created) }
+
+ subject do
+ commit_status.drop!(reason)
+ commit_status
+ end
+
+ context 'when failure_reason is nil' do
+ let(:reason) { }
+
+ it { is_expected.to be_unknown_failure }
+ end
+
+ context 'when failure_reason is script_failure' do
+ let(:reason) { :script_failure }
+
+ it { is_expected.to be_script_failure }
+ end
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index dfbe1a7c192..37f6fd3a25b 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -66,56 +66,76 @@ describe Issuable do
end
describe ".search" do
- let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
+ let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
- it 'returns notes with a matching title' do
+ it 'returns issues with a matching title' do
expect(issuable_class.search(searchable_issue.title))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching title' do
+ it 'returns issues with a partially matching title' do
expect(issuable_class.search('able')).to eq([searchable_issue])
end
- it 'returns notes with a matching title regardless of the casing' do
+ it 'returns issues with a matching title regardless of the casing' do
expect(issuable_class.search(searchable_issue.title.upcase))
.to eq([searchable_issue])
end
+
+ it 'returns issues with a fuzzy matching title' do
+ expect(issuable_class.search('searchable issue')).to eq([searchable_issue])
+ end
+
+ it 'returns all issues with a query shorter than 3 chars' do
+ expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ end
end
describe ".full_search" do
let!(:searchable_issue) do
- create(:issue, title: "Searchable issue", description: 'kittens')
+ create(:issue, title: "Searchable awesome issue", description: 'Many cute kittens')
end
- it 'returns notes with a matching title' do
+ it 'returns issues with a matching title' do
expect(issuable_class.full_search(searchable_issue.title))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching title' do
+ it 'returns issues with a partially matching title' do
expect(issuable_class.full_search('able')).to eq([searchable_issue])
end
- it 'returns notes with a matching title regardless of the casing' do
+ it 'returns issues with a matching title regardless of the casing' do
expect(issuable_class.full_search(searchable_issue.title.upcase))
.to eq([searchable_issue])
end
- it 'returns notes with a matching description' do
+ it 'returns issues with a fuzzy matching title' do
+ expect(issuable_class.full_search('searchable issue')).to eq([searchable_issue])
+ end
+
+ it 'returns issues with a matching description' do
expect(issuable_class.full_search(searchable_issue.description))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching description' do
+ it 'returns issues with a partially matching description' do
expect(issuable_class.full_search(searchable_issue.description))
.to eq([searchable_issue])
end
- it 'returns notes with a matching description regardless of the casing' do
+ it 'returns issues with a matching description regardless of the casing' do
expect(issuable_class.full_search(searchable_issue.description.upcase))
.to eq([searchable_issue])
end
+
+ it 'returns issues with a fuzzy matching description' do
+ expect(issuable_class.full_search('many kittens')).to eq([searchable_issue])
+ end
+
+ it 'returns all issues with a query shorter than 3 chars' do
+ expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ end
end
describe '.to_ability_name' do
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index d00faa4f8be..91591017587 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -189,8 +189,8 @@ describe Note, ResolvableNote do
allow(subject).to receive(:resolvable?).and_return(false)
end
- it "returns nil" do
- expect(subject.resolve!(current_user)).to be_nil
+ it "returns false" do
+ expect(subject.resolve!(current_user)).to be_falsey
end
it "doesn't set resolved_at" do
@@ -224,8 +224,8 @@ describe Note, ResolvableNote do
subject.resolve!(user)
end
- it "returns nil" do
- expect(subject.resolve!(current_user)).to be_nil
+ it "returns false" do
+ expect(subject.resolve!(current_user)).to be_falsey
end
it "doesn't change resolved_at" do
@@ -279,8 +279,8 @@ describe Note, ResolvableNote do
allow(subject).to receive(:resolvable?).and_return(false)
end
- it "returns nil" do
- expect(subject.unresolve!).to be_nil
+ it "returns false" do
+ expect(subject.unresolve!).to be_falsey
end
end
@@ -320,8 +320,8 @@ describe Note, ResolvableNote do
end
context "when not resolved" do
- it "returns nil" do
- expect(subject.unresolve!).to be_nil
+ it "returns false" do
+ expect(subject.unresolve!).to be_falsey
end
end
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index e48f20bf53b..9c99c3e5c08 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -99,14 +99,14 @@ describe GpgKey do
end
describe '#verified?' do
- it 'returns true one of the email addresses in the key belongs to the user' do
+ it 'returns true if one of the email addresses in the key belongs to the user' do
user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_truthy
end
- it 'returns false if one of the email addresses in the key does not belong to the user' do
+ it 'returns false if none of the email addresses in the key does not belong to the user' do
user = create :user, email: 'someone.else@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
@@ -114,6 +114,32 @@ describe GpgKey do
end
end
+ describe 'verified_and_belongs_to_email?' do
+ it 'returns false if none of the email addresses in the key does not belong to the user' do
+ user = create :user, email: 'someone.else@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_falsey
+ expect(gpg_key.verified_and_belongs_to_email?('someone.else@example.com')).to be_falsey
+ end
+
+ it 'returns false if one of the email addresses in the key belongs to the user and does not match the provided email' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_truthy
+ expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.net')).to be_falsey
+ end
+
+ it 'returns true if one of the email addresses in the key belongs to the user and matches the provided email' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_truthy
+ expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.com')).to be_truthy
+ end
+ end
+
describe 'notification', :mailer do
let(:user) { create(:user) }
@@ -129,15 +155,15 @@ describe GpgKey do
describe '#revoke' do
it 'invalidates all associated gpg signatures and destroys the key' do
gpg_key = create :gpg_key
- gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: gpg_key
+ gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: gpg_key
unrelated_gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key
- unrelated_gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: unrelated_gpg_key
+ unrelated_gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: unrelated_gpg_key
gpg_key.revoke
expect(gpg_signature.reload).to have_attributes(
- valid_signature: false,
+ verification_status: 'unknown_key',
gpg_key: nil
)
@@ -145,7 +171,7 @@ describe GpgKey do
# unrelated signature is left untouched
expect(unrelated_gpg_signature.reload).to have_attributes(
- valid_signature: true,
+ verification_status: 'verified',
gpg_key: unrelated_gpg_key
)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index f9cd12c0ff3..f36d6eeb327 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -9,6 +9,7 @@ describe Group do
it { is_expected.to have_many(:users).through(:group_members) }
it { is_expected.to have_many(:owners).through(:group_members) }
it { is_expected.to have_many(:requesters).dependent(:destroy) }
+ it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
@@ -25,22 +26,8 @@ describe Group do
group.add_developer(developer)
end
- describe '#members' do
- it 'includes members and exclude requesters' do
- member_user_ids = group.members.pluck(:user_id)
-
- expect(member_user_ids).to include(developer.id)
- expect(member_user_ids).not_to include(requester.id)
- end
- end
-
- describe '#requesters' do
- it 'does not include requesters' do
- requester_user_ids = group.requesters.pluck(:user_id)
-
- expect(requester_user_ids).to include(requester.id)
- expect(requester_user_ids).not_to include(developer.id)
- end
+ it_behaves_like 'members and requesters associations' do
+ let(:namespace) { group }
end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 87513e18b25..a07ce05a865 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -409,6 +409,15 @@ describe Member do
expect(members).to be_a Array
expect(members).to be_empty
end
+
+ it 'supports differents formats' do
+ list = ['joe@local.test', admin, user1.id, user2.id.to_s]
+
+ members = described_class.add_users(source, list, :master)
+
+ expect(members.size).to eq(4)
+ expect(members.first).to be_invite
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f5d079c27c4..d80d5657c42 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1262,7 +1262,6 @@ describe MergeRequest do
describe "#reload_diff" do
let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
-
let(:commit) { subject.project.commit(sample_commit.id) }
it "does not change existing merge request diff" do
@@ -1280,9 +1279,19 @@ describe MergeRequest do
subject.reload_diff
end
- it "updates diff discussion positions" do
- old_diff_refs = subject.diff_refs
+ it "calls update_diff_discussion_positions" do
+ expect(subject).to receive(:update_diff_discussion_positions)
+
+ subject.reload_diff
+ end
+ end
+ describe '#update_diff_discussion_positions' do
+ let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
+ let(:commit) { subject.project.commit(sample_commit.id) }
+ let(:old_diff_refs) { subject.diff_refs }
+
+ before do
# Update merge_request_diff so that #diff_refs will return commit.diff_refs
allow(subject).to receive(:create_merge_request_diff) do
subject.merge_request_diffs.create(
@@ -1293,7 +1302,9 @@ describe MergeRequest do
subject.merge_request_diff(true)
end
+ end
+ it "updates diff discussion positions" do
expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
subject.project,
subject.author,
@@ -1305,7 +1316,26 @@ describe MergeRequest do
expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
expect_any_instance_of(DiffNote).to receive(:save).once
- subject.reload_diff(subject.author)
+ subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
+ new_diff_refs: commit.diff_refs,
+ current_user: subject.author)
+ end
+
+ context 'when resolve_outdated_diff_discussions is set' do
+ before do
+ discussion
+
+ subject.project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'calls MergeRequests::ResolvedDiscussionNotificationService' do
+ expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService)
+ .to receive(:execute).with(subject)
+
+ subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
+ new_diff_refs: commit.diff_refs,
+ current_user: subject.author)
+ end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index be1ae295f75..1f7c6a82b91 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -74,6 +74,7 @@ describe Project do
it { is_expected.to have_many(:forks).through(:forked_project_links) }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_many(:pipeline_schedules) }
+ it { is_expected.to have_many(:members_and_requesters) }
context 'after initialized' do
it "has a project_feature" do
@@ -90,22 +91,8 @@ describe Project do
project.team << [developer, :developer]
end
- describe '#members' do
- it 'includes members and exclude requesters' do
- member_user_ids = project.members.pluck(:user_id)
-
- expect(member_user_ids).to include(developer.id)
- expect(member_user_ids).not_to include(requester.id)
- end
- end
-
- describe '#requesters' do
- it 'does not include requesters' do
- requester_user_ids = project.requesters.pluck(:user_id)
-
- expect(requester_user_ids).to include(requester.id)
- expect(requester_user_ids).not_to include(developer.id)
- end
+ it_behaves_like 'members and requesters associations' do
+ let(:namespace) { project }
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b70ab5581ac..fd83a58ed9f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2102,4 +2102,18 @@ describe User do
end
end
end
+
+ describe '#verified_email?' do
+ it 'returns true when the email is the primary email' do
+ user = build :user, email: 'email@example.com'
+
+ expect(user.verified_email?('email@example.com')).to be true
+ end
+
+ it 'returns false when the email is not the primary email' do
+ user = build :user, email: 'email@example.com'
+
+ expect(user.verified_email?('other_email@example.com')).to be false
+ end
+ end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index a7a34ecac72..1a8001be6ab 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -100,4 +100,38 @@ describe Ci::BuildPresenter do
end
end
end
+
+ describe '#trigger_variables' do
+ let(:build) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
+
+ context 'when variable is stored in ci_pipeline_variables' do
+ let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
+
+ context 'when pipeline is triggered by trigger API' do
+ it 'returns variables' do
+ expect(presenter.trigger_variables).to eq([pipeline_variable.to_runner_variable])
+ end
+ end
+
+ context 'when pipeline is not triggered by trigger API' do
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not return variables' do
+ expect(presenter.trigger_variables).to eq([])
+ end
+ end
+ end
+
+ context 'when variable is stored in ci_trigger_requests.variables' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+ end
+
+ it 'returns variables' do
+ expect(presenter.trigger_variables).to eq(trigger_request.user_variables)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index b1e011de604..cc794fad3a7 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -75,6 +75,22 @@ describe API::Branches do
let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}" }
shared_examples_for 'repository branch' do
+ context 'HEAD request' do
+ it 'returns 204 No Content' do
+ head api(route, user)
+
+ expect(response).to have_gitlab_http_status(204)
+ expect(response.body).to be_empty
+ end
+
+ it 'returns 404 Not Found' do
+ head api("/projects/#{project_id}/repository/branches/unknown", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_empty
+ end
+ end
+
it 'returns the repository branch' do
get api(route, current_user)
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index cc71865e1f3..e4c73583545 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -142,6 +142,9 @@ describe API::CommitStatuses do
expect(json_response['ref']).not_to be_empty
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
+ if status == 'failed'
+ expect(CommitStatus.find(json_response['id'])).to be_api_failure
+ end
end
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index a6c804fb2b3..1274e66bb4c 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,13 +5,26 @@ describe API::Internal do
let(:key) { create(:key, user: user) }
let(:project) { create(:project, :repository) }
let(:secret_token) { Gitlab::Shell.secret_token }
+ let(:gl_repository) { "project-#{project.id}" }
+ let(:reference_counter) { double('ReferenceCounter') }
describe "GET /internal/check" do
it do
+ expect_any_instance_of(Redis).to receive(:ping).and_return('PONG')
+
get api("/internal/check"), secret_token: secret_token
expect(response).to have_http_status(200)
expect(json_response['api_version']).to eq(API::API.version)
+ expect(json_response['redis']).to be(true)
+ end
+
+ it 'returns false for field `redis` when redis is unavailable' do
+ expect_any_instance_of(Redis).to receive(:ping).and_raise(Errno::ENOENT)
+
+ get api("/internal/check"), secret_token: secret_token
+
+ expect(json_response['redis']).to be(false)
end
end
@@ -661,9 +674,7 @@ describe API::Internal do
# end
describe 'POST /internal/post_receive' do
- let(:gl_repository) { "project-#{project.id}" }
let(:identifier) { 'key-123' }
- let(:reference_counter) { double('ReferenceCounter') }
let(:valid_params) do
{
@@ -749,6 +760,22 @@ describe API::Internal do
end
end
+ describe 'POST /internal/pre_receive' do
+ let(:valid_params) do
+ { gl_repository: gl_repository, secret_token: secret_token }
+ end
+
+ it 'decreases the reference counter and returns the result' do
+ expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository)
+ .and_return(reference_counter)
+ expect(reference_counter).to receive(:increase).and_return(true)
+
+ post api("/internal/pre_receive"), valid_params
+
+ expect(json_response['reference_counter_increased']).to be(true)
+ end
+ end
+
def project_with_repo_path(path)
double().tap do |fake_project|
allow(fake_project).to receive_message_chain('repository.path_to_repo' => path)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index dee75c96b86..1583d1c2435 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -138,6 +138,16 @@ describe API::Issues, :mailer do
expect(first_issue['id']).to eq(issue2.id)
end
+ it 'returns issues reacted by the authenticated user by the given emoji' do
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
+ award_emoji = create(:award_emoji, awardable: issue2, user: user2, name: 'star')
+
+ get api('/issues', user2), my_reaction_emoji: award_emoji.name, scope: 'all'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
it 'returns issues matching given search string for title' do
get api("/issues", user), search: issue.title
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 9027090aabd..21d2c9644fb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -117,6 +117,18 @@ describe API::MergeRequests do
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
end
+
+ it 'returns merge requests reacted by the authenticated user by the given emoji' do
+ merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star')
+
+ get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request3.id)
+ end
end
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index b6a5a7ffbb5..f650df57383 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::PipelineSchedules do
set(:developer) { create(:user) }
set(:user) { create(:user) }
- set(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository, public_builds: false) }
before do
project.add_developer(developer)
@@ -110,6 +110,18 @@ describe API::PipelineSchedules do
end
end
+ context 'authenticated user with insufficient permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
@@ -299,4 +311,150 @@ describe API::PipelineSchedules do
end
end
end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ let(:params) { attributes_for(:ci_pipeline_schedule_variable) }
+
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule_variable' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params
+ end.to change { pipeline_schedule.variables.count }.by(1)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['key']).to eq(params[:key])
+ expect(json_response['value']).to eq(params[:value])
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+
+ context 'when key has validation error' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params.merge('key' => '!?!?')
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('key')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
+ value: 'updated_value'
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['value']).to eq('updated_value')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ let(:master) { create(:user) }
+
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ project.add_master(master)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule_variable' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", master)
+ end.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+
+ expect(response).to have_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/____", master)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master) }
+
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4490e50702b..f771e4fa4ff 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -414,6 +414,7 @@ describe API::Projects do
jobs_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
+ resolve_outdated_diff_discussions: false,
only_allow_merge_if_pipeline_succeeds: false,
request_access_enabled: true,
only_allow_merge_if_all_discussions_are_resolved: false,
@@ -477,20 +478,40 @@ describe API::Projects do
expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
end
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: false)
+
+ post api('/projects', user), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
+ end
+
+ it 'sets a project as allowing outdated diff discussions to automatically resolve if resolve_outdated_diff_discussions' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: true)
+
+ post api('/projects', user), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
+ end
+
it 'sets a project as allowing merge even if build fails' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
+
post api('/projects', user), project
+
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
+
post api('/projects', user), project
+
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
post api('/projects', user), project
@@ -506,7 +527,7 @@ describe API::Projects do
end
it 'sets a project as allowing merge only if all discussions are resolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
post api('/projects', user), project
@@ -514,7 +535,7 @@ describe API::Projects do
end
it 'ignores import_url when it is nil' do
- project = attributes_for(:project, { import_url: nil })
+ project = attributes_for(:project, import_url: nil)
post api('/projects', user), project
@@ -642,20 +663,36 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: false)
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
+ end
+
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: true)
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
+ end
+
it 'sets a project as allowing merge even if build fails' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
- it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
+ it 'sets a project as allowing merge only if pipeline succeeds' do
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
post api("/projects/user/#{user.id}", admin), project
@@ -663,7 +700,7 @@ describe API::Projects do
end
it 'sets a project as allowing merge only if all discussions are resolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
post api("/projects/user/#{user.id}", admin), project
@@ -732,6 +769,7 @@ describe API::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 993164aa8fe..12720355a6d 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -557,17 +557,36 @@ describe API::Runner do
{ 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
end
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
+
before do
- trigger = create(:ci_trigger, project: project)
- create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [job], trigger: trigger)
project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
- it 'returns variables for triggers' do
- request_job
+ shared_examples 'expected variables behavior' do
+ it 'returns variables for triggers' do
+ request_job
- expect(response).to have_http_status(201)
- expect(json_response['variables']).to include(*expected_variables)
+ expect(response).to have_http_status(201)
+ expect(json_response['variables']).to include(*expected_variables)
+ end
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
+ end
+
+ it_behaves_like 'expected variables behavior'
end
end
@@ -626,13 +645,34 @@ describe API::Runner do
it 'mark job as succeeded' do
update_job(state: 'success')
- expect(job.reload.status).to eq 'success'
+ job.reload
+ expect(job).to be_success
end
it 'mark job as failed' do
update_job(state: 'failed')
- expect(job.reload.status).to eq 'failed'
+ job.reload
+ expect(job).to be_failed
+ expect(job).to be_unknown_failure
+ end
+
+ context 'when failure_reason is script_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'script_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_script_failure }
+ end
+
+ context 'when failure_reason is runner_system_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'runner_system_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_runner_system_failure }
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5fef4437997..37cb95a16e3 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -4,6 +4,7 @@ describe API::Users do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) }
+ let(:gpg_key) { create(:gpg_key, user: user) }
let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
@@ -753,6 +754,164 @@ describe API::Users do
end
end
+ describe 'POST /users/:id/keys' do
+ before do
+ admin
+ end
+
+ it 'does not create invalid GPG key' do
+ post api("/users/#{user.id}/gpg_keys", admin)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('key is missing')
+ end
+
+ it 'creates GPG key' do
+ key_attrs = attributes_for :gpg_key
+ expect do
+ post api("/users/#{user.id}/gpg_keys", admin), key_attrs
+
+ expect(response).to have_http_status(201)
+ end.to change { user.gpg_keys.count }.by(1)
+ end
+
+ it 'returns 400 for invalid ID' do
+ post api('/users/999999/gpg_keys', admin)
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ describe 'GET /user/:id/gpg_keys' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api("/users/#{user.id}/gpg_keys")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get api('/users/999999/gpg_keys', admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ delete api("/users/#{user.id}/gpg_keys/42", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns array of GPG keys' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/users/#{user.id}/gpg_keys", admin)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['key']).to eq(gpg_key.key)
+ end
+ end
+ end
+
+ describe 'DELETE /user/:id/gpg_keys/:key_id' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete api("/users/#{user.id}/keys/42")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'deletes existing key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(204)
+ end.to change { user.gpg_keys.count }.by(-1)
+ end
+
+ it 'returns 404 error if user not found' do
+ user.keys << key
+ user.save
+
+ delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ delete api("/users/#{user.id}/gpg_keys/42", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+ end
+ end
+
+ describe 'POST /user/:id/gpg_keys/:key_id/revoke' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ post api("/users/#{user.id}/gpg_keys/42/revoke")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'revokes existing key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin)
+
+ expect(response).to have_http_status(:accepted)
+ end.to change { user.gpg_keys.count }.by(-1)
+ end
+
+ it 'returns 404 error if user not found' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ post api("/users/#{user.id}/gpg_keys/42/revoke", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+ end
+ end
+
describe "POST /users/:id/emails" do
before do
admin
@@ -1153,6 +1312,173 @@ describe API::Users do
end
end
+ describe 'GET /user/gpg_keys' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/user/gpg_keys')
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns array of GPG keys' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api('/user/gpg_keys', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['key']).to eq(gpg_key.key)
+ end
+
+ context 'scopes' do
+ let(:path) { '/user/gpg_keys' }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
+ end
+ end
+
+ describe 'GET /user/gpg_keys/:key_id' do
+ it 'returns a single key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/user/gpg_keys/#{gpg_key.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['key']).to eq(gpg_key.key)
+ end
+
+ it 'returns 404 Not Found within invalid ID' do
+ get api('/user/gpg_keys/42', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it "returns 404 error if admin accesses user's GPG key" do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/user/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 404 for invalid ID' do
+ get api('/users/gpg_keys/ASDF', admin)
+
+ expect(response).to have_http_status(404)
+ end
+
+ context 'scopes' do
+ let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
+ end
+
+ describe 'POST /user/gpg_keys' do
+ it 'creates a GPG key' do
+ key_attrs = attributes_for :gpg_key
+ expect do
+ post api('/user/gpg_keys', user), key_attrs
+
+ expect(response).to have_http_status(201)
+ end.to change { user.gpg_keys.count }.by(1)
+ end
+
+ it 'returns a 401 error if unauthorized' do
+ post api('/user/gpg_keys'), key: 'some key'
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'does not create GPG key without key' do
+ post api('/user/gpg_keys', user)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('key is missing')
+ end
+ end
+
+ describe 'POST /user/gpg_keys/:key_id/revoke' do
+ it 'revokes existing GPG key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ post api("/user/gpg_keys/#{gpg_key.id}/revoke", user)
+
+ expect(response).to have_http_status(:accepted)
+ end.to change { user.gpg_keys.count}.by(-1)
+ end
+
+ it 'returns 404 if key ID not found' do
+ post api('/user/gpg_keys/42/revoke', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 401 error if unauthorized' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ post api("/user/gpg_keys/#{gpg_key.id}/revoke")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 404 for invalid ID' do
+ post api('/users/gpg_keys/ASDF/revoke', admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'DELETE /user/gpg_keys/:key_id' do
+ it 'deletes existing GPG key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ delete api("/user/gpg_keys/#{gpg_key.id}", user)
+
+ expect(response).to have_http_status(204)
+ end.to change { user.gpg_keys.count}.by(-1)
+ end
+
+ it 'returns 404 if key ID not found' do
+ delete api('/user/gpg_keys/42', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 401 error if unauthorized' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ delete api("/user/gpg_keys/#{gpg_key.id}")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 404 for invalid ID' do
+ delete api('/users/gpg_keys/ASDF', admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
describe "GET /user/emails" do
context "when unauthenticated" do
it "returns authentication error" do
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index a514166274a..cae2c3118da 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -687,6 +687,7 @@ describe API::V3::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['builds_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index d4648136841..7ccf387f2dc 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -37,7 +37,7 @@ describe API::V3::Triggers do
it 'returns unauthorized if token is for different project' do
post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(404)
end
end
@@ -80,7 +80,8 @@ describe API::V3::Triggers do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
- expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
+ expect(pipeline.variables.map { |v| { v.key => v.value } }.first).to eq(variables)
+ expect(json_response['variables']).to eq(variables)
end
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
deleted file mode 100644
index 8295813a1ca..00000000000
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateTriggerRequestService do
- let(:service) { described_class }
- let(:project) { create(:project, :repository) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
- let(:owner) { create(:user) }
-
- before do
- stub_ci_pipeline_to_return_yaml_file
-
- project.add_developer(owner)
- end
-
- describe '#execute' do
- context 'valid params' do
- subject { service.execute(project, trigger, 'master') }
-
- context 'without owner' do
- it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(subject.pipeline).to be_trigger }
- end
-
- context 'with owner' do
- it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.trigger_request.builds.first.user).to eq(owner) }
- it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(subject.pipeline).to be_trigger }
- it { expect(subject.pipeline.user).to eq(owner) }
- end
- end
-
- context 'no commit for ref' do
- subject { service.execute(project, trigger, 'other-branch') }
-
- it { expect(subject.pipeline).not_to be_persisted }
- end
-
- context 'no builds created' do
- subject { service.execute(project, trigger, 'master') }
-
- before do
- stub_ci_pipeline_yaml_file('script: { only: [develop], script: hello World }')
- end
-
- it { expect(subject.pipeline).not_to be_persisted }
- end
- end
-end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 7c9c117bf71..f5ed9ff608f 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -22,7 +22,7 @@ describe Ci::RetryBuildService do
%i[type lock_version target_url base_tags
commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id
- user_id auto_canceled_by_id retried].freeze
+ user_id auto_canceled_by_id retried failure_reason].freeze
shared_examples 'build duplication' do
let(:stage) do
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index c239494298b..82b156f5ebe 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -150,21 +150,7 @@ describe Discussions::UpdateDiffPositionService do
)
end
- context "when the diff line is the same" do
- let(:line) { 16 }
-
- it "updates the position" do
- subject.execute(discussion)
-
- expect(discussion.original_position).to eq(old_position)
- expect(discussion.position).not_to eq(old_position)
- expect(discussion.position.new_line).to eq(22)
- end
- end
-
- context "when the diff line has changed" do
- let(:line) { 9 }
-
+ shared_examples 'outdated diff note' do
it "doesn't update the position" do
subject.execute(discussion)
@@ -189,5 +175,51 @@ describe Discussions::UpdateDiffPositionService do
subject.execute(discussion)
end
end
+
+ context "when the diff line is the same" do
+ let(:line) { 16 }
+
+ it "updates the position" do
+ subject.execute(discussion)
+
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).not_to eq(old_position)
+ expect(discussion.position.new_line).to eq(22)
+ end
+
+ context 'when the resolve_outdated_diff_discussions setting is set' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'does not resolve the discussion' do
+ subject.execute(discussion)
+
+ expect(discussion).not_to be_resolved
+ expect(discussion).not_to be_resolved_by_push
+ end
+ end
+ end
+
+ context "when the diff line has changed" do
+ let(:line) { 9 }
+
+ include_examples 'outdated diff note'
+
+ context 'when the resolve_outdated_diff_discussions setting is set' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'sets resolves the discussion and sets resolved_by_push' do
+ subject.execute(discussion)
+
+ expect(discussion).to be_resolved
+ expect(discussion).to be_resolved_by_push
+ end
+
+ include_examples 'outdated diff note'
+ end
+ end
end
end
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index aa6ad6340f5..031366d1825 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -116,6 +116,7 @@ describe Projects::UpdatePagesService do
expect(deploy_status.description)
.to match(/artifacts for pages are too large/)
+ expect(deploy_status).to be_script_failure
end
end
diff --git a/spec/support/group_members_shared_example.rb b/spec/support/group_members_shared_example.rb
new file mode 100644
index 00000000000..547c83c7955
--- /dev/null
+++ b/spec/support/group_members_shared_example.rb
@@ -0,0 +1,27 @@
+RSpec.shared_examples 'members and requesters associations' do
+ describe '#members_and_requesters' do
+ it 'includes members and requesters' do
+ member_and_requester_user_ids = namespace.members_and_requesters.pluck(:user_id)
+
+ expect(member_and_requester_user_ids).to include(requester.id, developer.id)
+ end
+ end
+
+ describe '#members' do
+ it 'includes members and exclude requesters' do
+ member_user_ids = namespace.members.pluck(:user_id)
+
+ expect(member_user_ids).to include(developer.id)
+ expect(member_user_ids).not_to include(requester.id)
+ end
+ end
+
+ describe '#requesters' do
+ it 'does not include requesters' do
+ requester_user_ids = namespace.requesters.pluck(:user_id)
+
+ expect(requester_user_ids).to include(requester.id)
+ expect(requester_user_ids).not_to include(developer.id)
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 1e39f80699c..71b9deeabc3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,7 +5,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'signed-commits' => '5d4a1cb',
+ 'signed-commits' => '2d1096e',
'not-merged-branch' => 'b83d6e3',
'branch-merged' => '498214d',
'empty-branch' => '7efb185',
@@ -176,6 +176,24 @@ module TestEnv
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
@gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i }
+ wait_gitaly
+ end
+
+ def wait_gitaly
+ sleep_time = 10
+ sleep_interval = 0.1
+ socket = Gitlab::GitalyClient.address('default').sub('unix:', '')
+
+ Integer(sleep_time / sleep_interval).times do
+ begin
+ Socket.unix(socket)
+ return
+ rescue
+ sleep sleep_interval
+ end
+ end
+
+ raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds"
end
def stop_gitaly
diff --git a/spec/views/layouts/nav/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index faea2505e40..b17bc6692f3 100644
--- a/spec/views/layouts/nav/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -1,11 +1,13 @@
require 'spec_helper'
-describe 'layouts/nav/_project' do
+describe 'layouts/nav/sidebar/_project' do
describe 'container registry tab' do
before do
+ project = create(:project, :repository)
stub_container_registry_config(enabled: true)
- assign(:project, create(:project, :repository))
+ assign(:project, project)
+ assign(:repository, project.repository)
allow(view).to receive(:current_ref).and_return('master')
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 117f48450e2..d4279626e75 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -195,20 +195,4 @@ describe 'projects/jobs/show' do
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
end
end
-
- describe 'shows trigger variables in sidebar' do
- let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
-
- before do
- build.trigger_request = trigger_request
- render
- end
-
- it 'shows trigger variables in separate lines' do
- expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_1')
- expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_2')
- expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_1')
- expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
- end
- end
end
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
index 54978baca88..aa6c347d738 100644
--- a/spec/workers/create_gpg_signature_worker_spec.rb
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -7,9 +7,14 @@ describe CreateGpgSignatureWorker do
let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
it 'calls Gitlab::Gpg::Commit#signature' do
- expect(Gitlab::Gpg::Commit).to receive(:new).with(project, commit_sha).and_call_original
+ commit = instance_double(Commit)
+ gpg_commit = instance_double(Gitlab::Gpg::Commit)
- expect_any_instance_of(Gitlab::Gpg::Commit).to receive(:signature)
+ allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
+ allow(project).to receive(:commit).with(commit_sha).and_return(commit)
+
+ expect(Gitlab::Gpg::Commit).to receive(:new).with(commit).and_return(gpg_commit)
+ expect(gpg_commit).to receive(:signature)
described_class.new.perform(commit_sha, project.id)
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 549635f7f33..ac6f4fefb4e 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -6,27 +6,31 @@ describe StuckCiJobsWorker do
let(:worker) { described_class.new }
let(:exclusive_lease_uuid) { SecureRandom.uuid }
- subject do
- job.reload
- job.status
- end
-
before do
job.update!(status: status, updated_at: updated_at)
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid)
end
shared_examples 'job is dropped' do
- it 'changes status' do
+ before do
worker.perform
- is_expected.to eq('failed')
+ job.reload
+ end
+
+ it "changes status" do
+ expect(job).to be_failed
+ expect(job).to be_stuck_or_timeout_failure
end
end
shared_examples 'job is unchanged' do
- it "doesn't change status" do
+ before do
worker.perform
- is_expected.to eq(status)
+ job.reload
+ end
+
+ it "doesn't change status" do
+ expect(job.status).to eq(status)
end
end
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index ff23445e2b0..345e61ae3f2 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -31,7 +31,7 @@ cmake-build-debug/
## Plugin-specific files:
# IntelliJ
-/out/
+out/
# mpeltonen/sbt-idea plugin
.idea_modules/
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index 450f32ec40c..eee88b2f0f7 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -18,3 +18,4 @@ cabal.sandbox.config
.stack-work/
cabal.project.local
.HTF/
+.ghc.environment.*
diff --git a/vendor/gitignore/Prestashop.gitignore b/vendor/gitignore/Prestashop.gitignore
index 7c6ae1e31cc..81f45e19eba 100644
--- a/vendor/gitignore/Prestashop.gitignore
+++ b/vendor/gitignore/Prestashop.gitignore
@@ -7,8 +7,10 @@ config/settings.*.php
# The following files are generated by PrestaShop.
admin-dev/autoupgrade/
-/cache/
+/cache/*
!/cache/index.php
+!/cache/*/
+/cache/*/*
!/cache/cachefs/index.php
!/cache/purifier/index.php
!/cache/push/index.php
diff --git a/vendor/gitignore/Smalltalk.gitignore b/vendor/gitignore/Smalltalk.gitignore
index 75272b23472..943995e1172 100644
--- a/vendor/gitignore/Smalltalk.gitignore
+++ b/vendor/gitignore/Smalltalk.gitignore
@@ -13,6 +13,10 @@ SqueakDebug.log
# Monticello package cache
/package-cache
+# playground cache
+/play-cache
+/play-stash
+
# Metacello-github cache
/github-cache
github-*.zip
diff --git a/vendor/gitignore/Symfony.gitignore b/vendor/gitignore/Symfony.gitignore
index 6c224e024e9..85fd714a965 100644
--- a/vendor/gitignore/Symfony.gitignore
+++ b/vendor/gitignore/Symfony.gitignore
@@ -39,3 +39,6 @@
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
+
+# Embedded web-server pid file
+/.web-server-pid
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 22fd88a55a3..89c66054885 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -151,7 +151,7 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
+# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
diff --git a/vendor/gitlab-ci-yml/Go.gitlab-ci.yml b/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
index e23b6e212f0..8a214352d2a 100644
--- a/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
@@ -1,14 +1,19 @@
image: golang:latest
+variables:
+ # Please edit to your GitLab project
+ REPO_NAME: gitlab.com/namespace/project
+
# The problem is that to be able to use go get, one needs to put
# the repository in the $GOPATH. So for example if your gitlab domain
-# is mydomainperso.com, and that your repository is repos/projectname, and
+# is gitlab.com, and that your repository is namespace/project, and
# the default GOPATH being /go, then you'd need to have your
-# repository in /go/src/mydomainperso.com/repos/projectname
+# repository in /go/src/gitlab.com/namespace/project
# Thus, making a symbolic link corrects this.
before_script:
- - ln -s /builds /go/src/mydomainperso.com
- - cd /go/src/mydomainperso.com/repos/projectname
+ - mkdir -p $GOPATH/src/$REPO_NAME
+ - ln -svf $CI_PROJECT_DIR/* $GOPATH/src/$REPO_NAME
+ - cd $GOPATH/src/$REPO_NAME
stages:
- test
@@ -17,21 +22,14 @@ stages:
format:
stage: test
script:
- # Add here all the dependencies, or use glide/govendor to get
- # them automatically.
- # - curl https://glide.sh/get | sh
- - go get github.com/alecthomas/kingpin
- - go tool vet -composites=false -shadow=true *.go
- - go test -race $(go list ./... | grep -v /vendor/)
+ - go fmt $(go list ./... | grep -v /vendor/)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -race $(go list ./... | grep -v /vendor/)
compile:
stage: build
script:
- # Add here all the dependencies, or use glide/govendor/...
- # to get them automatically.
- - go get github.com/alecthomas/kingpin
- # Better put this in a Makefile
- - go build -race -ldflags "-extldflags '-static'" -o mybinary
+ - go build -race -ldflags "-extldflags '-static'" -o mybinary
artifacts:
- paths:
- - mybinary
+ paths:
+ - mybinary
diff --git a/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml b/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml
index a65e48a3389..48d98dddfad 100644
--- a/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml
@@ -1,41 +1,36 @@
-# This template uses the java:8 docker image because there isn't any
-# official Gradle image at this moment
-#
# This is the Gradle build system for JVM applications
# https://gradle.org/
# https://github.com/gradle/gradle
-image: java:8
+image: gradle:alpine
# Disable the Gradle daemon for Continuous Integration servers as correctness
# is usually a priority over speed in CI environments. Using a fresh
# runtime for each build is more reliable since the runtime is completely
# isolated from any previous builds.
variables:
- GRADLE_OPTS: "-Dorg.gradle.daemon=false"
+ GRADLE_OPTS: "-Dorg.gradle.daemon=false"
-# Make the gradle wrapper executable. This essentially downloads a copy of
-# Gradle to build the project with.
-# https://docs.gradle.org/current/userguide/gradle_wrapper.html
-# It is expected that any modern gradle project has a wrapper
before_script:
- - chmod +x gradlew
+ - export GRADLE_USER_HOME=`pwd`/.gradle
-# We redirect the gradle user home using -g so that it caches the
-# wrapper and dependencies.
-# https://docs.gradle.org/current/userguide/gradle_command_line.html
-#
-# Unfortunately it also caches the build output so
-# cleaning removes reminants of any cached builds.
-# The assemble task actually builds the project.
-# If it fails here, the tests can't run.
build:
stage: build
- script:
- - ./gradlew -g /cache/.gradle clean assemble
- allow_failure: false
+ script: gradle --build-cache assemble
+ cache:
+ key: "$CI_COMMIT_REF_NAME"
+ policy: push
+ paths:
+ - build
+ - .gradle
+
-# Use the generated build output to run the tests.
test:
stage: test
- script:
- - ./gradlew -g /cache/.gradle check
+ script: gradle check
+ cache:
+ key: "$CI_COMMIT_REF_NAME"
+ policy: pull
+ paths:
+ - build
+ - .gradle
+
diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
index 434de4f055a..0ad662cf704 100644
--- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
@@ -34,6 +34,10 @@ before_script:
# Install php extensions
- docker-php-ext-install mbstring mcrypt pdo_mysql curl json intl gd xml zip bz2 opcache
+ # Install & enable Xdebug for code coverage reports
+ - pecl install xdebug
+ - docker-php-ext-enable xdebug
+
# Install Composer and project dependencies.
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
diff --git a/vendor/gitlab-ci-yml/PHP.gitlab-ci.yml b/vendor/gitlab-ci-yml/PHP.gitlab-ci.yml
index bb8caa49d6b..33f44ee9222 100644
--- a/vendor/gitlab-ci-yml/PHP.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/PHP.gitlab-ci.yml
@@ -11,6 +11,9 @@ before_script:
- apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
# Install PHP extensions
- docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache
+# Install & enable Xdebug for code coverage reports
+- pecl install xdebug
+- docker-php-ext-enable xdebug
# Install and run Composer
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 4e181e85451..ff7bdd32239 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -1,6 +1,6 @@
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/ruby/tags/
-image: "ruby:2.3"
+image: "ruby:2.4"
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
@@ -40,9 +40,9 @@ rails:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - bundle exec rake db:migrate
- - bundle exec rake db:seed
- - bundle exec rake test
+ - rails db:migrate
+ - rails db:seed
+ - rails test
# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk
# are supported too: https://github.com/travis-ci/dpl