summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Lopez <james@jameslopez.es>2018-04-09 08:52:19 +0200
committerJames Lopez <james@jameslopez.es>2018-04-09 08:52:19 +0200
commit88b903c569c8c47433d291c215c43396c297ace8 (patch)
tree2395bc1f468be1d35f09661a87ab84b5fe0d4703
parent99ac00b1fdb29e7fec890567ba9bae3dbd8e9061 (diff)
parent86ca1a77c58b508fc1b037c5fffbfb22fd992b42 (diff)
downloadgitlab-ce-10-7-stable-prepare-rc3.tar.gz
Merge remote-tracking branch 'origin/master' into 10-7-stable-prepare-rc310-7-stable-prepare-rc3
-rw-r--r--.scss-lint.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--Gemfile.rails5.lock6
-rw-r--r--app/assets/javascripts/awards_handler.js15
-rw-r--r--app/assets/javascripts/badges/components/badge.vue121
-rw-r--r--app/assets/javascripts/badges/components/badge_form.vue219
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue57
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue89
-rw-r--r--app/assets/javascripts/badges/components/badge_settings.vue70
-rw-r--r--app/assets/javascripts/badges/constants.js2
-rw-r--r--app/assets/javascripts/badges/empty_badge.js7
-rw-r--r--app/assets/javascripts/badges/store/actions.js167
-rw-r--r--app/assets/javascripts/badges/store/index.js13
-rw-r--r--app/assets/javascripts/badges/store/mutation_types.js21
-rw-r--r--app/assets/javascripts/badges/store/mutations.js158
-rw-r--r--app/assets/javascripts/badges/store/state.js13
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js6
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js13
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js6
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js1
-rw-r--r--app/assets/javascripts/boards/models/issue.js6
-rw-r--r--app/assets/javascripts/ide/components/ide_file_buttons.vue1
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue40
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue13
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js1
-rw-r--r--app/assets/javascripts/ide/stores/utils.js1
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js7
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js20
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue24
-rw-r--r--app/assets/javascripts/monitoring/components/graph/axis.vue142
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue32
-rw-r--r--app/assets/javascripts/monitoring/components/graph/legend.vue228
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_info.vue29
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_line.vue36
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js2
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js84
-rw-r--r--app/assets/javascripts/mr_notes/index.js5
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue11
-rw-r--r--app/assets/javascripts/notes/constants.js6
-rw-r--r--app/assets/javascripts/notes/mixins/noteable.js11
-rw-r--r--app/assets/javascripts/pages/groups/settings/badges/index.js10
-rw-r--r--app/assets/javascripts/pages/projects/settings/badges/index/index.js10
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/form.js19
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js18
-rw-r--r--app/assets/javascripts/pages/shared/mount_badge_settings.js24
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue96
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue84
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue180
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue78
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js23
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediator.js7
-rw-r--r--app/assets/javascripts/profile/account/components/update_username.vue121
-rw-r--r--app/assets/javascripts/profile/account/index.js15
-rw-r--r--app/assets/javascripts/search_autocomplete.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue52
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue68
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue77
-rw-r--r--app/assets/stylesheets/framework/images.scss2
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/pages/commits.scss65
-rw-r--r--app/assets/stylesheets/pages/environments.scss46
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss14
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/assets/stylesheets/pages/repo.scss39
-rw-r--r--app/assets/stylesheets/pages/settings.scss20
-rw-r--r--app/controllers/boards/issues_controller.rb3
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb2
-rw-r--r--app/controllers/dashboard_controller.rb19
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/groups/settings/badges_controller.rb13
-rw-r--r--app/controllers/jwt_controller.rb3
-rw-r--r--app/controllers/profiles_controller.rb18
-rw-r--r--app/controllers/projects/deploy_tokens_controller.rb10
-rw-r--r--app/controllers/projects/repositories_controller.rb17
-rw-r--r--app/controllers/projects/settings/badges_controller.rb13
-rw-r--r--app/controllers/projects/settings/repository_controller.rb30
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/commits_helper.rb27
-rw-r--r--app/helpers/deploy_tokens_helper.rb12
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb24
-rw-r--r--app/helpers/workhorse_helper.rb4
-rw-r--r--app/models/commit.rb5
-rw-r--r--app/models/deploy_token.rb61
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/issue.rb12
-rw-r--r--app/models/merge_request_diff_commit.rb2
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/project_deploy_token.rb8
-rw-r--r--app/models/user.rb16
-rw-r--r--app/policies/deploy_token_policy.rb11
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb21
-rw-r--r--app/services/boards/issues/list_service.rb5
-rw-r--r--app/services/deploy_tokens/create_service.rb7
-rw-r--r--app/services/issuable/destroy_service.rb1
-rw-r--r--app/services/issuable_base_service.rb3
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/projects/base_move_relations_service.rb22
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb28
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb23
-rw-r--r--app/services/projects/move_access_service.rb25
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb31
-rw-r--r--app/services/projects/move_forks_service.rb42
-rw-r--r--app/services/projects/move_lfs_objects_projects_service.rb29
-rw-r--r--app/services/projects/move_notification_settings_service.rb38
-rw-r--r--app/services/projects/move_project_authorizations_service.rb40
-rw-r--r--app/services/projects/move_project_group_links_service.rb40
-rw-r--r--app/services/projects/move_project_members_service.rb40
-rw-r--r--app/services/projects/move_users_star_projects_service.rb20
-rw-r--r--app/services/projects/overwrite_project_service.rb69
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/projects/update_pages_service.rb14
-rw-r--r--app/views/dashboard/issues.html.haml12
-rw-r--r--app/views/dashboard/merge_requests.html.haml12
-rw-r--r--app/views/groups/settings/badges/index.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml8
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml9
-rw-r--r--app/views/profiles/accounts/show.html.haml16
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml1
-rw-r--r--app/views/projects/_home_panel.html.haml11
-rw-r--r--app/views/projects/buttons/_download.html.haml9
-rw-r--r--app/views/projects/commits/_commit.html.haml15
-rw-r--r--app/views/projects/deploy_tokens/_form.html.haml29
-rw-r--r--app/views/projects/deploy_tokens/_index.html.haml18
-rw-r--r--app/views/projects/deploy_tokens/_new_deploy_token.html.haml14
-rw-r--r--app/views/projects/deploy_tokens/_revoke_modal.html.haml17
-rw-r--r--app/views/projects/deploy_tokens/_table.html.haml31
-rw-r--r--app/views/projects/issues/_discussion.html.haml1
-rw-r--r--app/views/projects/jobs/_empty_state.html.haml5
-rw-r--r--app/views/projects/jobs/_empty_states.html.haml9
-rw-r--r--app/views/projects/jobs/show.html.haml23
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--app/views/projects/registry/repositories/index.html.haml4
-rw-r--r--app/views/projects/settings/badges/index.html.haml4
-rw-r--r--app/views/projects/settings/repository/show.html.haml1
-rw-r--r--app/views/shared/_recaptcha_form.html.haml2
-rw-r--r--app/views/shared/badges/_badge_settings.html.haml4
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_assignee.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml3
-rw-r--r--app/views/shared/dashboard/_no_filter_selected.html.haml8
-rw-r--r--app/views/shared/issuable/_filter.html.haml9
-rw-r--r--app/views/shared/issuable/_nav.html.haml11
-rw-r--r--changelogs/unreleased/17939-osw-patch-support-gfm.yml5
-rw-r--r--changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml5
-rw-r--r--changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml5
-rw-r--r--changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml6
-rw-r--r--changelogs/unreleased/42568-pipeline-empty-state.yml5
-rw-r--r--changelogs/unreleased/43246-checkfilter.yml6
-rw-r--r--changelogs/unreleased/ab-37462-cache-personal-projects-count.yml5
-rw-r--r--changelogs/unreleased/add-cpu-mem-totals.yml5
-rw-r--r--changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml5
-rw-r--r--changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml6
-rw-r--r--changelogs/unreleased/fix-dashboard-sorting.yml5
-rw-r--r--changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml5
-rw-r--r--changelogs/unreleased/fl-fix-annoying-actions.yml5
-rw-r--r--changelogs/unreleased/issue_42443.yml5
-rw-r--r--changelogs/unreleased/issue_44270.yml5
-rw-r--r--changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml5
-rw-r--r--changelogs/unreleased/jramsay-38830-tarball.yml5
-rw-r--r--changelogs/unreleased/remove-pages-tar-support.yml5
-rw-r--r--changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml5
-rw-r--r--changelogs/unreleased/ui-mr-counter-cache.yml5
-rw-r--r--changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml5
-rw-r--r--config/initializers/active_record_array_type_casting.rb31
-rw-r--r--config/karma.config.js2
-rw-r--r--config/prometheus/additional_metrics.yml34
-rw-r--r--config/routes/group.rb1
-rw-r--r--config/routes/project.rb13
-rw-r--r--config/routes/repository.rb7
-rw-r--r--db/migrate/20180319190020_create_deploy_tokens.rb19
-rw-r--r--db/migrate/20180405142733_create_project_deploy_tokens.rb16
-rw-r--r--db/schema.rb25
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/monitoring/prometheus/index.md9
-rw-r--r--doc/administration/plugins.md70
-rw-r--r--doc/api/group_badges.md5
-rw-r--r--doc/api/project_badges.md5
-rw-r--r--doc/api/project_import_export.md1
-rw-r--r--doc/ci/environments.md15
-rw-r--r--doc/ci/variables/README.md59
-rw-r--r--doc/ci/yaml/README.md3
-rw-r--r--doc/development/changelog.md1
-rw-r--r--doc/user/gitlab_com/index.md106
-rw-r--r--doc/user/project/badges.md73
-rw-r--r--doc/user/project/container_registry.md11
-rw-r--r--doc/user/project/deploy_tokens/img/deploy_tokens.pngbin0 -> 75650 bytes
-rw-r--r--doc/user/project/deploy_tokens/index.md76
-rw-r--r--doc/user/project/img/project_overview_badges.pngbin0 -> 40188 bytes
-rw-r--r--doc/user/project/index.md2
-rw-r--r--doc/user/project/issue_board.md3
-rw-r--r--doc/user/project/pipelines/settings.md2
-rw-r--r--features/steps/shared/builds.rb2
-rw-r--r--lib/api/badges.rb1
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/project_import.rb4
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/v3/repositories.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb4
-rw-r--r--lib/forever.rb13
-rw-r--r--lib/gitlab/auth.rb22
-rw-r--r--lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb2
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb4
-rw-r--r--lib/gitlab/ci/status/build/canceled.rb21
-rw-r--r--lib/gitlab/ci/status/build/created.rb22
-rw-r--r--lib/gitlab/ci/status/build/erased.rb21
-rw-r--r--lib/gitlab/ci/status/build/factory.rb8
-rw-r--r--lib/gitlab/ci/status/build/manual.rb22
-rw-r--r--lib/gitlab/ci/status/build/pending.rb22
-rw-r--r--lib/gitlab/ci/status/build/play.rb4
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb4
-rw-r--r--lib/gitlab/ci/status/build/skipped.rb21
-rw-r--r--lib/gitlab/ci/status/build/stop.rb4
-rw-r--r--lib/gitlab/ci/status/core.rb8
-rw-r--r--lib/gitlab/database/sha_attribute.rb51
-rw-r--r--lib/gitlab/git/checksum.rb82
-rw-r--r--lib/gitlab/git/conflict/resolver.rb2
-rw-r--r--lib/gitlab/git/repository.rb121
-rw-r--r--lib/gitlab/git_access.rb16
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb11
-rw-r--r--lib/gitlab/import_export/importer.rb42
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb2
-rw-r--r--lib/gitlab/import_export/statistics_restorer.rb17
-rw-r--r--lib/gitlab/prometheus/queries/query_additional_metrics.rb2
-rw-r--r--lib/gitlab/workhorse.rb4
-rw-r--r--rubocop/cop/rspec/factories_in_migration_specs.rb40
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--rubocop/spec_helpers.rb13
-rw-r--r--spec/controllers/dashboard_controller_spec.rb2
-rw-r--r--spec/controllers/profiles_controller_spec.rb22
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb13
-rw-r--r--spec/factories/deploy_tokens.rb14
-rw-r--r--spec/factories/project_deploy_tokens.rb6
-rw-r--r--spec/factories/users_star_projects.rb6
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb17
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb30
-rw-r--r--spec/features/dashboard/issues_spec.rb9
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb8
-rw-r--r--spec/features/groups/settings/group_badges_spec.rb124
-rw-r--r--spec/features/issues/spam_issues_spec.rb3
-rw-r--r--spec/features/labels_hierarchy_spec.rb10
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb9
-rw-r--r--spec/features/profile_spec.rb8
-rw-r--r--spec/features/profiles/account_spec.rb12
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb17
-rw-r--r--spec/features/projects/jobs_spec.rb57
-rw-r--r--spec/features/projects/settings/project_badges_spec.rb125
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb27
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb127
-rw-r--r--spec/fixtures/api/schemas/issue.json2
-rw-r--r--spec/helpers/issuables_helper_spec.rb29
-rw-r--r--spec/javascripts/badges/components/badge_form_spec.js171
-rw-r--r--spec/javascripts/badges/components/badge_list_row_spec.js97
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js88
-rw-r--r--spec/javascripts/badges/components/badge_settings_spec.js109
-rw-r--r--spec/javascripts/badges/components/badge_spec.js147
-rw-r--r--spec/javascripts/badges/dummy_badge.js23
-rw-r--r--spec/javascripts/badges/store/actions_spec.js607
-rw-r--r--spec/javascripts/badges/store/mutations_spec.js418
-rw-r--r--spec/javascripts/boards/issue_card_spec.js2
-rw-r--r--spec/javascripts/fixtures/one_white_pixel.pngbin0 -> 68 bytes
-rw-r--r--spec/javascripts/helpers/vue_mount_component_helper.js6
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js14
-rw-r--r--spec/javascripts/matchers.js35
-rw-r--r--spec/javascripts/monitoring/graph/axis_spec.js65
-rw-r--r--spec/javascripts/monitoring/graph/legend_spec.js108
-rw-r--r--spec/javascripts/monitoring/graph/track_info_spec.js44
-rw-r--r--spec/javascripts/monitoring/graph/track_line_spec.js52
-rw-r--r--spec/javascripts/monitoring/graph_spec.js25
-rw-r--r--spec/javascripts/monitoring/mock_data.js14732
-rw-r--r--spec/javascripts/notes/mock_data.js1
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js25
-rw-r--r--spec/javascripts/profile/account/components/update_username_spec.js172
-rw-r--r--spec/javascripts/search_autocomplete_spec.js214
-rw-r--r--spec/javascripts/test_bundle.js11
-rw-r--r--spec/javascripts/test_constants.js4
-rw-r--r--spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js29
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb31
-rw-r--r--spec/lib/forever_spec.rb21
-rw-r--r--spec/lib/gitlab/auth_spec.rb122
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/build/erased_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb4
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb8
-rw-r--r--spec/lib/gitlab/git/checksum_spec.rb38
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb100
-rw-r--r--spec/lib/gitlab/git_access_spec.rb76
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb21
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb42
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb4
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb6
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb18
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb2
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb10
-rw-r--r--spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb8
-rw-r--r--spec/migrations/issues_moved_to_id_foreign_key_spec.rb6
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb24
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb4
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb4
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb2
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb16
-rw-r--r--spec/migrations/remove_dot_git_from_usernames_spec.rb4
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb18
-rw-r--r--spec/migrations/remove_project_labels_group_id_spec.rb6
-rw-r--r--spec/migrations/remove_soft_removed_objects_spec.rb8
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb2
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb2
-rw-r--r--spec/migrations/rename_users_with_renamed_namespace_spec.rb8
-rw-r--r--spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb4
-rw-r--r--spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb2
-rw-r--r--spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb8
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb6
-rw-r--r--spec/models/deploy_token_spec.rb134
-rw-r--r--spec/models/environment_spec.rb26
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb6
-rw-r--r--spec/models/project_deploy_token_spec.rb14
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/user_spec.rb20
-rw-r--r--spec/policies/deploy_token_policy_spec.rb45
-rw-r--r--spec/requests/api/issues_spec.rb24
-rw-r--r--spec/requests/api/project_import_spec.rb23
-rw-r--r--spec/routing/project_routing_spec.rb30
-rw-r--r--spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb48
-rw-r--r--spec/services/boards/issues/list_service_spec.rb25
-rw-r--r--spec/services/deploy_tokens/create_service_spec.rb45
-rw-r--r--spec/services/issuable/destroy_service_spec.rb14
-rw-r--r--spec/services/issues/update_service_spec.rb6
-rw-r--r--spec/services/merge_requests/conflicts/list_service_spec.rb16
-rw-r--r--spec/services/projects/create_service_spec.rb8
-rw-r--r--spec/services/projects/destroy_service_spec.rb28
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb26
-rw-r--r--spec/services/projects/move_access_service_spec.rb114
-rw-r--r--spec/services/projects/move_deploy_keys_projects_service_spec.rb58
-rw-r--r--spec/services/projects/move_forks_service_spec.rb96
-rw-r--r--spec/services/projects/move_lfs_objects_projects_service_spec.rb55
-rw-r--r--spec/services/projects/move_notification_settings_service_spec.rb56
-rw-r--r--spec/services/projects/move_project_authorizations_service_spec.rb56
-rw-r--r--spec/services/projects/move_project_group_links_service_spec.rb65
-rw-r--r--spec/services/projects/move_project_members_service_spec.rb65
-rw-r--r--spec/services/projects/move_users_star_projects_service_spec.rb42
-rw-r--r--spec/services/projects/overwrite_project_service_spec.rb198
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/services/projects/update_pages_service_spec.rb104
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/helpers/expect_offense.rb20
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb8
-rw-r--r--spec/support/issuables_requiring_filter_shared_examples.rb15
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb17
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb2
372 files changed, 15665 insertions, 9913 deletions
diff --git a/.scss-lint.yml b/.scss-lint.yml
index dcd4cac780a..180d377d6f8 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -59,6 +59,8 @@ linters:
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: true
+ ignore_consecutive:
+ - cursor
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 9188543ea64..c5c735103b2 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.93.0
+0.94.0
diff --git a/Gemfile b/Gemfile
index 5eac6d73269..d85ee988644 100644
--- a/Gemfile
+++ b/Gemfile
@@ -421,7 +421,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.91.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.94.0', require: 'gitaly'
gem 'grpc', '~> 1.10.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
diff --git a/Gemfile.lock b/Gemfile.lock
index 55e7bd9492a..a1150dfccdd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -290,7 +290,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.91.0)
+ gitaly-proto (0.94.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
@@ -587,7 +587,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
- parser (2.5.0.3)
+ parser (2.5.0.5)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
@@ -1061,7 +1061,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.91.0)
+ gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 08ae3fb514c..18bbad88ec3 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -291,7 +291,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.91.0)
+ gitaly-proto (0.94.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
@@ -587,7 +587,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
- parser (2.5.0.4)
+ parser (2.5.0.5)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
@@ -1062,7 +1062,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.91.0)
+ gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 0e1ca7fe883..976d32abe9b 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -4,7 +4,8 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import { __ } from './locale';
-import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
+import { updateTooltipTitle } from './lib/utils/common_utils';
+import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
@@ -243,7 +244,7 @@ class AwardsHandler {
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
- if (this.isInVueNoteablePage() && !isMainAwardsBlock) {
+ if (isInVueNoteablePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', '');
this.hideMenuElement($('.emoji-menu'));
@@ -295,16 +296,8 @@ class AwardsHandler {
}
}
- isVueMRDiscussions() {
- return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
- }
-
- isInVueNoteablePage() {
- return isInIssuePage() || isInEpicPage() || this.isVueMRDiscussions();
- }
-
getVotesBlock() {
- if (this.isInVueNoteablePage()) {
+ if (isInVueNoteablePage()) {
const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
if ($el.length) {
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
new file mode 100644
index 00000000000..6e6cb31e3ac
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -0,0 +1,121 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import Tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ name: 'Badge',
+ components: {
+ Icon,
+ LoadingIcon,
+ Tooltip,
+ },
+ directives: {
+ Tooltip,
+ },
+ props: {
+ imageUrl: {
+ type: String,
+ required: true,
+ },
+ linkUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ hasError: false,
+ isLoading: true,
+ numRetries: 0,
+ };
+ },
+ computed: {
+ imageUrlWithRetries() {
+ if (this.numRetries === 0) {
+ return this.imageUrl;
+ }
+
+ return `${this.imageUrl}#retries=${this.numRetries}`;
+ },
+ },
+ watch: {
+ imageUrl() {
+ this.hasError = false;
+ this.isLoading = true;
+ this.numRetries = 0;
+ },
+ },
+ methods: {
+ onError() {
+ this.isLoading = false;
+ this.hasError = true;
+ },
+ onLoad() {
+ this.isLoading = false;
+ },
+ reloadImage() {
+ this.hasError = false;
+ this.isLoading = true;
+ this.numRetries += 1;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <a
+ v-show="!isLoading && !hasError"
+ :href="linkUrl"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <img
+ class="project-badge"
+ :src="imageUrlWithRetries"
+ @load="onLoad"
+ @error="onError"
+ aria-hidden="true"
+ />
+ </a>
+
+ <loading-icon
+ v-show="isLoading"
+ :inline="true"
+ />
+
+ <div
+ v-show="hasError"
+ class="btn-group"
+ >
+ <div class="btn btn-default btn-xs disabled">
+ <icon
+ class="prepend-left-8 append-right-8"
+ name="doc_image"
+ :size="16"
+ aria-hidden="true"
+ />
+ </div>
+ <div
+ class="btn btn-default btn-xs disabled"
+ >
+ <span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span>
+ </div>
+ </div>
+
+ <button
+ v-show="hasError"
+ class="btn btn-transparent btn-xs text-primary"
+ type="button"
+ v-tooltip
+ :title="s__('Badges|Reload badge image')"
+ @click="reloadImage"
+ >
+ <icon
+ name="retry"
+ :size="16"
+ />
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
new file mode 100644
index 00000000000..ae942b2c1a7
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -0,0 +1,219 @@
+<script>
+import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
+import createFlash from '~/flash';
+import { s__, sprintf } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import createEmptyBadge from '../empty_badge';
+import Badge from './badge.vue';
+
+const badgePreviewDelayInMilliseconds = 1500;
+
+export default {
+ name: 'BadgeForm',
+ components: {
+ Badge,
+ LoadingButton,
+ LoadingIcon,
+ },
+ props: {
+ isEditing: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'badgeInAddForm',
+ 'badgeInEditForm',
+ 'docsUrl',
+ 'isRendering',
+ 'isSaving',
+ 'renderedBadge',
+ ]),
+ badge() {
+ if (this.isEditing) {
+ return this.badgeInEditForm;
+ }
+
+ return this.badgeInAddForm;
+ },
+ canSubmit() {
+ return (
+ this.badge !== null &&
+ this.badge.imageUrl &&
+ this.badge.imageUrl.trim() !== '' &&
+ this.badge.linkUrl &&
+ this.badge.linkUrl.trim() !== '' &&
+ !this.isSaving
+ );
+ },
+ helpText() {
+ const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
+ .map(placeholder => `<code>%{${placeholder}}</code>`)
+ .join(', ');
+ return sprintf(
+ s__('Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}'),
+ {
+ docsLinkEnd: '</a>',
+ docsLinkStart: `<a href="${_.escape(this.docsUrl)}">`,
+ placeholders,
+ },
+ false,
+ );
+ },
+ renderedImageUrl() {
+ return this.renderedBadge ? this.renderedBadge.renderedImageUrl : '';
+ },
+ renderedLinkUrl() {
+ return this.renderedBadge ? this.renderedBadge.renderedLinkUrl : '';
+ },
+ imageUrl: {
+ get() {
+ return this.badge ? this.badge.imageUrl : '';
+ },
+ set(imageUrl) {
+ const badge = this.badge || createEmptyBadge();
+ this.updateBadgeInForm({
+ ...badge,
+ imageUrl,
+ });
+ },
+ },
+ linkUrl: {
+ get() {
+ return this.badge ? this.badge.linkUrl : '';
+ },
+ set(linkUrl) {
+ const badge = this.badge || createEmptyBadge();
+ this.updateBadgeInForm({
+ ...badge,
+ linkUrl,
+ });
+ },
+ },
+ submitButtonLabel() {
+ if (this.isEditing) {
+ return s__('Badges|Save changes');
+ }
+ return s__('Badges|Add badge');
+ },
+ },
+ methods: {
+ ...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']),
+ debouncedPreview: _.debounce(function preview() {
+ this.renderBadge();
+ }, badgePreviewDelayInMilliseconds),
+ onCancel() {
+ this.stopEditing();
+ },
+ onSubmit() {
+ if (!this.canSubmit) {
+ return Promise.resolve();
+ }
+
+ if (this.isEditing) {
+ return this.saveBadge()
+ .then(() => {
+ createFlash(s__('Badges|The badge was saved.'), 'notice');
+ })
+ .catch(error => {
+ createFlash(
+ s__('Badges|Saving the badge failed, please check the entered URLs and try again.'),
+ );
+ throw error;
+ });
+ }
+
+ return this.addBadge()
+ .then(() => {
+ createFlash(s__('Badges|A new badge was added.'), 'notice');
+ })
+ .catch(error => {
+ createFlash(
+ s__('Badges|Adding the badge failed, please check the entered URLs and try again.'),
+ );
+ throw error;
+ });
+ },
+ },
+ badgeImageUrlPlaceholder:
+ 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
+ badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
+};
+</script>
+
+<template>
+ <form
+ class="prepend-top-default append-bottom-default"
+ @submit.prevent.stop="onSubmit"
+ >
+ <div class="form-group">
+ <label for="badge-link-url">{{ s__('Badges|Link') }}</label>
+ <input
+ id="badge-link-url"
+ type="text"
+ class="form-control"
+ v-model="linkUrl"
+ :placeholder="$options.badgeLinkUrlPlaceholder"
+ @input="debouncedPreview"
+ />
+ <span
+ class="help-block"
+ v-html="helpText"
+ ></span>
+ </div>
+
+ <div class="form-group">
+ <label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
+ <input
+ id="badge-image-url"
+ type="text"
+ class="form-control"
+ v-model="imageUrl"
+ :placeholder="$options.badgeImageUrlPlaceholder"
+ @input="debouncedPreview"
+ />
+ <span
+ class="help-block"
+ v-html="helpText"
+ ></span>
+ </div>
+
+ <div class="form-group">
+ <label for="badge-preview">{{ s__('Badges|Badge image preview') }}</label>
+ <badge
+ id="badge-preview"
+ v-show="renderedBadge && !isRendering"
+ :image-url="renderedImageUrl"
+ :link-url="renderedLinkUrl"
+ />
+ <p v-show="isRendering">
+ <loading-icon
+ :inline="true"
+ />
+ </p>
+ <p
+ v-show="!renderedBadge && !isRendering"
+ class="disabled-content"
+ >{{ s__('Badges|No image to preview') }}</p>
+ </div>
+
+ <div class="row-content-block">
+ <loading-button
+ type="submit"
+ container-class="btn btn-success"
+ :disabled="!canSubmit"
+ :loading="isSaving"
+ :label="submitButtonLabel"
+ />
+ <button
+ class="btn btn-cancel"
+ type="button"
+ v-if="isEditing"
+ @click="onCancel"
+ >{{ __('Cancel') }}</button>
+ </div>
+ </form>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
new file mode 100644
index 00000000000..ca7197e1e0f
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -0,0 +1,57 @@
+<script>
+import { mapState } from 'vuex';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import BadgeListRow from './badge_list_row.vue';
+import { GROUP_BADGE } from '../constants';
+
+export default {
+ name: 'BadgeList',
+ components: {
+ BadgeListRow,
+ LoadingIcon,
+ },
+ computed: {
+ ...mapState(['badges', 'isLoading', 'kind']),
+ hasNoBadges() {
+ return !this.isLoading && (!this.badges || !this.badges.length);
+ },
+ isGroupBadge() {
+ return this.kind === GROUP_BADGE;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{ s__('Badges|Your badges') }}
+ <span
+ v-show="!isLoading"
+ class="badge"
+ >{{ badges.length }}</span>
+ </div>
+ <loading-icon
+ v-show="isLoading"
+ class="panel-body"
+ size="2"
+ />
+ <div
+ v-if="hasNoBadges"
+ class="panel-body"
+ >
+ <span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
+ <span v-else>{{ s__('Badges|This project has no badges') }}</span>
+ </div>
+ <div
+ v-else
+ class="panel-body"
+ >
+ <badge-list-row
+ v-for="badge in badges"
+ :key="badge.id"
+ :badge="badge"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
new file mode 100644
index 00000000000..af062bdf8c6
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -0,0 +1,89 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import { PROJECT_BADGE } from '../constants';
+import Badge from './badge.vue';
+
+export default {
+ name: 'BadgeListRow',
+ components: {
+ Badge,
+ Icon,
+ LoadingIcon,
+ },
+ props: {
+ badge: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['kind']),
+ badgeKindText() {
+ if (this.badge.kind === PROJECT_BADGE) {
+ return s__('Badges|Project Badge');
+ }
+
+ return s__('Badges|Group Badge');
+ },
+ canEditBadge() {
+ return this.badge.kind === this.kind;
+ },
+ },
+ methods: {
+ ...mapActions(['editBadge', 'updateBadgeInModal']),
+ },
+};
+</script>
+
+<template>
+ <div class="gl-responsive-table-row-layout gl-responsive-table-row">
+ <badge
+ class="table-section section-30"
+ :image-url="badge.renderedImageUrl"
+ :link-url="badge.renderedLinkUrl"
+ />
+ <span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
+ <div class="table-section section-10">
+ <span class="badge">{{ badgeKindText }}</span>
+ </div>
+ <div class="table-section section-10 table-button-footer">
+ <div
+ v-if="canEditBadge"
+ class="table-action-buttons">
+ <button
+ class="btn btn-default append-right-8"
+ type="button"
+ :disabled="badge.isDeleting"
+ @click="editBadge(badge)"
+ >
+ <icon
+ name="pencil"
+ :size="16"
+ :aria-label="__('Edit')"
+ />
+ </button>
+ <button
+ class="btn btn-danger"
+ type="button"
+ data-toggle="modal"
+ data-target="#delete-badge-modal"
+ :disabled="badge.isDeleting"
+ @click="updateBadgeInModal(badge)"
+ >
+ <icon
+ name="remove"
+ :size="16"
+ :aria-label="__('Delete')"
+ />
+ </button>
+ <loading-icon
+ v-show="badge.isDeleting"
+ :inline="true"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue
new file mode 100644
index 00000000000..83f78394238
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_settings.vue
@@ -0,0 +1,70 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import Badge from './badge.vue';
+import BadgeForm from './badge_form.vue';
+import BadgeList from './badge_list.vue';
+
+export default {
+ name: 'BadgeSettings',
+ components: {
+ Badge,
+ BadgeForm,
+ BadgeList,
+ GlModal,
+ },
+ computed: {
+ ...mapState(['badgeInModal', 'isEditing']),
+ deleteModalText() {
+ return s__(
+ 'Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.',
+ );
+ },
+ },
+ methods: {
+ ...mapActions(['deleteBadge']),
+ onSubmitModal() {
+ this.deleteBadge(this.badgeInModal)
+ .then(() => {
+ createFlash(s__('Badges|The badge was deleted.'), 'notice');
+ })
+ .catch(error => {
+ createFlash(s__('Badges|Deleting the badge failed, please try again.'));
+ throw error;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="badge-settings">
+ <gl-modal
+ id="delete-badge-modal"
+ :header-title-text="s__('Badges|Delete badge?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('Badges|Delete badge')"
+ @submit="onSubmitModal">
+ <div class="well">
+ <badge
+ :image-url="badgeInModal ? badgeInModal.renderedImageUrl : ''"
+ :link-url="badgeInModal ? badgeInModal.renderedLinkUrl : ''"
+ />
+ </div>
+ <p v-html="deleteModalText"></p>
+ </gl-modal>
+
+ <badge-form
+ v-show="isEditing"
+ :is-editing="true"
+ />
+
+ <badge-form
+ v-show="!isEditing"
+ :is-editing="false"
+ />
+ <badge-list v-show="!isEditing" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/badges/constants.js b/app/assets/javascripts/badges/constants.js
new file mode 100644
index 00000000000..8fbe3db5ef1
--- /dev/null
+++ b/app/assets/javascripts/badges/constants.js
@@ -0,0 +1,2 @@
+export const GROUP_BADGE = 'group';
+export const PROJECT_BADGE = 'project';
diff --git a/app/assets/javascripts/badges/empty_badge.js b/app/assets/javascripts/badges/empty_badge.js
new file mode 100644
index 00000000000..49a9b5e1be8
--- /dev/null
+++ b/app/assets/javascripts/badges/empty_badge.js
@@ -0,0 +1,7 @@
+export default () => ({
+ imageUrl: '',
+ isDeleting: false,
+ linkUrl: '',
+ renderedImageUrl: '',
+ renderedLinkUrl: '',
+});
diff --git a/app/assets/javascripts/badges/store/actions.js b/app/assets/javascripts/badges/store/actions.js
new file mode 100644
index 00000000000..5542278b3e0
--- /dev/null
+++ b/app/assets/javascripts/badges/store/actions.js
@@ -0,0 +1,167 @@
+import axios from '~/lib/utils/axios_utils';
+import types from './mutation_types';
+
+export const transformBackendBadge = badge => ({
+ id: badge.id,
+ imageUrl: badge.image_url,
+ kind: badge.kind,
+ linkUrl: badge.link_url,
+ renderedImageUrl: badge.rendered_image_url,
+ renderedLinkUrl: badge.rendered_link_url,
+ isDeleting: false,
+});
+
+export default {
+ requestNewBadge({ commit }) {
+ commit(types.REQUEST_NEW_BADGE);
+ },
+ receiveNewBadge({ commit }, newBadge) {
+ commit(types.RECEIVE_NEW_BADGE, newBadge);
+ },
+ receiveNewBadgeError({ commit }) {
+ commit(types.RECEIVE_NEW_BADGE_ERROR);
+ },
+ addBadge({ dispatch, state }) {
+ const newBadge = state.badgeInAddForm;
+ const endpoint = state.apiEndpointUrl;
+ dispatch('requestNewBadge');
+ return axios
+ .post(endpoint, {
+ image_url: newBadge.imageUrl,
+ link_url: newBadge.linkUrl,
+ })
+ .catch(error => {
+ dispatch('receiveNewBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveNewBadge', transformBackendBadge(res.data));
+ });
+ },
+ requestDeleteBadge({ commit }, badgeId) {
+ commit(types.REQUEST_DELETE_BADGE, badgeId);
+ },
+ receiveDeleteBadge({ commit }, badgeId) {
+ commit(types.RECEIVE_DELETE_BADGE, badgeId);
+ },
+ receiveDeleteBadgeError({ commit }, badgeId) {
+ commit(types.RECEIVE_DELETE_BADGE_ERROR, badgeId);
+ },
+ deleteBadge({ dispatch, state }, badge) {
+ const badgeId = badge.id;
+ dispatch('requestDeleteBadge', badgeId);
+ const endpoint = `${state.apiEndpointUrl}/${badgeId}`;
+ return axios
+ .delete(endpoint)
+ .catch(error => {
+ dispatch('receiveDeleteBadgeError', badgeId);
+ throw error;
+ })
+ .then(() => {
+ dispatch('receiveDeleteBadge', badgeId);
+ });
+ },
+
+ editBadge({ commit }, badge) {
+ commit(types.START_EDITING, badge);
+ },
+
+ requestLoadBadges({ commit }, data) {
+ commit(types.REQUEST_LOAD_BADGES, data);
+ },
+ receiveLoadBadges({ commit }, badges) {
+ commit(types.RECEIVE_LOAD_BADGES, badges);
+ },
+ receiveLoadBadgesError({ commit }) {
+ commit(types.RECEIVE_LOAD_BADGES_ERROR);
+ },
+
+ loadBadges({ dispatch, state }, data) {
+ dispatch('requestLoadBadges', data);
+ const endpoint = state.apiEndpointUrl;
+ return axios
+ .get(endpoint)
+ .catch(error => {
+ dispatch('receiveLoadBadgesError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveLoadBadges', res.data.map(transformBackendBadge));
+ });
+ },
+
+ requestRenderedBadge({ commit }) {
+ commit(types.REQUEST_RENDERED_BADGE);
+ },
+ receiveRenderedBadge({ commit }, renderedBadge) {
+ commit(types.RECEIVE_RENDERED_BADGE, renderedBadge);
+ },
+ receiveRenderedBadgeError({ commit }) {
+ commit(types.RECEIVE_RENDERED_BADGE_ERROR);
+ },
+
+ renderBadge({ dispatch, state }) {
+ const badge = state.isEditing ? state.badgeInEditForm : state.badgeInAddForm;
+ const { linkUrl, imageUrl } = badge;
+ if (!linkUrl || linkUrl.trim() === '' || !imageUrl || imageUrl.trim() === '') {
+ return Promise.resolve(badge);
+ }
+
+ dispatch('requestRenderedBadge');
+
+ const parameters = [
+ `link_url=${encodeURIComponent(linkUrl)}`,
+ `image_url=${encodeURIComponent(imageUrl)}`,
+ ].join('&');
+ const renderEndpoint = `${state.apiEndpointUrl}/render?${parameters}`;
+ return axios
+ .get(renderEndpoint)
+ .catch(error => {
+ dispatch('receiveRenderedBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveRenderedBadge', transformBackendBadge(res.data));
+ });
+ },
+
+ requestUpdatedBadge({ commit }) {
+ commit(types.REQUEST_UPDATED_BADGE);
+ },
+ receiveUpdatedBadge({ commit }, updatedBadge) {
+ commit(types.RECEIVE_UPDATED_BADGE, updatedBadge);
+ },
+ receiveUpdatedBadgeError({ commit }) {
+ commit(types.RECEIVE_UPDATED_BADGE_ERROR);
+ },
+
+ saveBadge({ dispatch, state }) {
+ const badge = state.badgeInEditForm;
+ const endpoint = `${state.apiEndpointUrl}/${badge.id}`;
+ dispatch('requestUpdatedBadge');
+ return axios
+ .put(endpoint, {
+ image_url: badge.imageUrl,
+ link_url: badge.linkUrl,
+ })
+ .catch(error => {
+ dispatch('receiveUpdatedBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveUpdatedBadge', transformBackendBadge(res.data));
+ });
+ },
+
+ stopEditing({ commit }) {
+ commit(types.STOP_EDITING);
+ },
+
+ updateBadgeInForm({ commit }, badge) {
+ commit(types.UPDATE_BADGE_IN_FORM, badge);
+ },
+
+ updateBadgeInModal({ commit }, badge) {
+ commit(types.UPDATE_BADGE_IN_MODAL, badge);
+ },
+};
diff --git a/app/assets/javascripts/badges/store/index.js b/app/assets/javascripts/badges/store/index.js
new file mode 100644
index 00000000000..7a5df403a0e
--- /dev/null
+++ b/app/assets/javascripts/badges/store/index.js
@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ state: createState(),
+ actions,
+ mutations,
+});
diff --git a/app/assets/javascripts/badges/store/mutation_types.js b/app/assets/javascripts/badges/store/mutation_types.js
new file mode 100644
index 00000000000..d73f91b6005
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutation_types.js
@@ -0,0 +1,21 @@
+export default {
+ RECEIVE_DELETE_BADGE: 'RECEIVE_DELETE_BADGE',
+ RECEIVE_DELETE_BADGE_ERROR: 'RECEIVE_DELETE_BADGE_ERROR',
+ RECEIVE_LOAD_BADGES: 'RECEIVE_LOAD_BADGES',
+ RECEIVE_LOAD_BADGES_ERROR: 'RECEIVE_LOAD_BADGES_ERROR',
+ RECEIVE_NEW_BADGE: 'RECEIVE_NEW_BADGE',
+ RECEIVE_NEW_BADGE_ERROR: 'RECEIVE_NEW_BADGE_ERROR',
+ RECEIVE_RENDERED_BADGE: 'RECEIVE_RENDERED_BADGE',
+ RECEIVE_RENDERED_BADGE_ERROR: 'RECEIVE_RENDERED_BADGE_ERROR',
+ RECEIVE_UPDATED_BADGE: 'RECEIVE_UPDATED_BADGE',
+ RECEIVE_UPDATED_BADGE_ERROR: 'RECEIVE_UPDATED_BADGE_ERROR',
+ REQUEST_DELETE_BADGE: 'REQUEST_DELETE_BADGE',
+ REQUEST_LOAD_BADGES: 'REQUEST_LOAD_BADGES',
+ REQUEST_NEW_BADGE: 'REQUEST_NEW_BADGE',
+ REQUEST_RENDERED_BADGE: 'REQUEST_RENDERED_BADGE',
+ REQUEST_UPDATED_BADGE: 'REQUEST_UPDATED_BADGE',
+ START_EDITING: 'START_EDITING',
+ STOP_EDITING: 'STOP_EDITING',
+ UPDATE_BADGE_IN_FORM: 'UPDATE_BADGE_IN_FORM',
+ UPDATE_BADGE_IN_MODAL: 'UPDATE_BADGE_IN_MODAL',
+};
diff --git a/app/assets/javascripts/badges/store/mutations.js b/app/assets/javascripts/badges/store/mutations.js
new file mode 100644
index 00000000000..bd84e68c00f
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutations.js
@@ -0,0 +1,158 @@
+import types from './mutation_types';
+import { PROJECT_BADGE } from '../constants';
+
+const reorderBadges = badges =>
+ badges.sort((a, b) => {
+ if (a.kind !== b.kind) {
+ return a.kind === PROJECT_BADGE ? 1 : -1;
+ }
+
+ return a.id - b.id;
+ });
+
+export default {
+ [types.RECEIVE_NEW_BADGE](state, newBadge) {
+ Object.assign(state, {
+ badgeInAddForm: null,
+ badges: reorderBadges(state.badges.concat(newBadge)),
+ isSaving: false,
+ renderedBadge: null,
+ });
+ },
+ [types.RECEIVE_NEW_BADGE_ERROR](state) {
+ Object.assign(state, {
+ isSaving: false,
+ });
+ },
+ [types.REQUEST_NEW_BADGE](state) {
+ Object.assign(state, {
+ isSaving: true,
+ });
+ },
+
+ [types.RECEIVE_UPDATED_BADGE](state, updatedBadge) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === updatedBadge.id) {
+ return updatedBadge;
+ }
+ return badge;
+ });
+ Object.assign(state, {
+ badgeInEditForm: null,
+ badges,
+ isEditing: false,
+ isSaving: false,
+ renderedBadge: null,
+ });
+ },
+ [types.RECEIVE_UPDATED_BADGE_ERROR](state) {
+ Object.assign(state, {
+ isSaving: false,
+ });
+ },
+ [types.REQUEST_UPDATED_BADGE](state) {
+ Object.assign(state, {
+ isSaving: true,
+ });
+ },
+
+ [types.RECEIVE_LOAD_BADGES](state, badges) {
+ Object.assign(state, {
+ badges: reorderBadges(badges),
+ isLoading: false,
+ });
+ },
+ [types.RECEIVE_LOAD_BADGES_ERROR](state) {
+ Object.assign(state, {
+ isLoading: false,
+ });
+ },
+ [types.REQUEST_LOAD_BADGES](state, data) {
+ Object.assign(state, {
+ kind: data.kind, // project or group
+ apiEndpointUrl: data.apiEndpointUrl,
+ docsUrl: data.docsUrl,
+ isLoading: true,
+ });
+ },
+
+ [types.RECEIVE_DELETE_BADGE](state, badgeId) {
+ const badges = state.badges.filter(badge => badge.id !== badgeId);
+ Object.assign(state, {
+ badges,
+ });
+ },
+ [types.RECEIVE_DELETE_BADGE_ERROR](state, badgeId) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === badgeId) {
+ return {
+ ...badge,
+ isDeleting: false,
+ };
+ }
+
+ return badge;
+ });
+ Object.assign(state, {
+ badges,
+ });
+ },
+ [types.REQUEST_DELETE_BADGE](state, badgeId) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === badgeId) {
+ return {
+ ...badge,
+ isDeleting: true,
+ };
+ }
+
+ return badge;
+ });
+ Object.assign(state, {
+ badges,
+ });
+ },
+
+ [types.RECEIVE_RENDERED_BADGE](state, renderedBadge) {
+ Object.assign(state, { isRendering: false, renderedBadge });
+ },
+ [types.RECEIVE_RENDERED_BADGE_ERROR](state) {
+ Object.assign(state, { isRendering: false });
+ },
+ [types.REQUEST_RENDERED_BADGE](state) {
+ Object.assign(state, { isRendering: true });
+ },
+
+ [types.START_EDITING](state, badge) {
+ Object.assign(state, {
+ badgeInEditForm: { ...badge },
+ isEditing: true,
+ renderedBadge: { ...badge },
+ });
+ },
+ [types.STOP_EDITING](state) {
+ Object.assign(state, {
+ badgeInEditForm: null,
+ isEditing: false,
+ renderedBadge: null,
+ });
+ },
+
+ [types.UPDATE_BADGE_IN_FORM](state, badge) {
+ if (state.isEditing) {
+ Object.assign(state, {
+ badgeInEditForm: badge,
+ });
+ } else {
+ Object.assign(state, {
+ badgeInAddForm: badge,
+ });
+ }
+ },
+
+ [types.UPDATE_BADGE_IN_MODAL](state, badge) {
+ Object.assign(state, {
+ badgeInModal: badge,
+ });
+ },
+};
diff --git a/app/assets/javascripts/badges/store/state.js b/app/assets/javascripts/badges/store/state.js
new file mode 100644
index 00000000000..43413aeb5bb
--- /dev/null
+++ b/app/assets/javascripts/badges/store/state.js
@@ -0,0 +1,13 @@
+export default () => ({
+ apiEndpointUrl: null,
+ badgeInAddForm: null,
+ badgeInEditForm: null,
+ badgeInModal: null,
+ badges: [],
+ docsUrl: null,
+ renderedBadge: null,
+ isEditing: false,
+ isLoading: false,
+ isRendering: false,
+ isSaving: false,
+});
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a44969272a1..c4ee4f6c855 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -60,10 +60,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
this.issue = this.detail.issue;
this.list = this.detail.list;
-
- this.$nextTick(() => {
- this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate;
- });
},
deep: true
},
@@ -91,7 +87,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
saveAssignees () {
this.loadingAssignees = true;
- gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint)
+ gl.issueBoards.BoardsStore.detail.issue.update()
.then(() => {
this.loadingAssignees = false;
})
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 8aee5b23c76..84fe9b1288a 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -68,15 +68,6 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return this.issue.assignees.length > this.numberOverLimit;
},
- cardUrl() {
- let baseUrl = this.issueLinkBase;
-
- if (this.groupId && this.issue.project) {
- baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path);
- }
-
- return `${baseUrl}/${this.issue.iid}`;
- },
issueId() {
if (this.issue.iid) {
return `#${this.issue.iid}`;
@@ -153,13 +144,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
/>
<a
class="js-no-trigger"
- :href="cardUrl"
+ :href="issue.path"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issueId"
>
- <template v-if="groupId && issue.project">{{issue.project.path}}</template>{{ issueId }}
+ {{ issue.referencePath }}
</span>
</h4>
<div class="card-assignee">
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 09c683ff621..0a0820ec5fd 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -17,14 +17,10 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
type: Object,
required: true,
},
- issueUpdate: {
- type: String,
- required: true,
- },
},
computed: {
updateUrl() {
- return this.issueUpdate.replace(':project_path', this.issue.project.path);
+ return this.issue.path;
},
},
methods: {
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index fb40b9f5565..70367c4f711 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super({
page: 'boards',
+ isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
});
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 4c5079efc8b..b381d48d625 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -23,6 +23,8 @@ class ListIssue {
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
+ this.referencePath = obj.reference_path;
+ this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
@@ -98,7 +100,7 @@ class ListIssue {
this.isLoading[key] = value;
}
- update (url) {
+ update () {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
@@ -113,7 +115,7 @@ class ListIssue {
}
const projectPath = this.project ? this.project.path : '';
- return Vue.http.patch(url.replace(':project_path', projectPath), data);
+ return Vue.http.patch(`${this.path}.json`, data);
}
}
diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue
index 6d07329df71..a6c6f46a144 100644
--- a/app/assets/javascripts/ide/components/ide_file_buttons.vue
+++ b/app/assets/javascripts/ide/components/ide_file_buttons.vue
@@ -36,6 +36,7 @@ export default {
>
<a
v-tooltip
+ v-if="!file.binary"
:href="file.blamePath"
:title="__('Blame')"
class="btn btn-xs btn-transparent blame"
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 9c386896448..152a5f632ad 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -1,25 +1,23 @@
<script>
- import icon from '~/vue_shared/components/icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
- import timeAgoMixin from '~/vue_shared/mixins/timeago';
+import icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import timeAgoMixin from '~/vue_shared/mixins/timeago';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [timeAgoMixin],
+ props: {
+ file: {
+ type: Object,
+ required: true,
},
- directives: {
- tooltip,
- },
- mixins: [
- timeAgoMixin,
- ],
- props: {
- file: {
- type: Object,
- required: true,
- },
- },
- };
+ },
+};
</script>
<template>
@@ -50,7 +48,9 @@
<div class="text-right">
{{ file.eol }}
</div>
- <div class="text-right">
+ <div
+ class="text-right"
+ v-if="!file.binary">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div class="text-right">
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 8a709b31ea0..6aa44ca2c11 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -171,10 +171,10 @@ export default {
id="ide"
class="blob-viewer-container blob-editor-container"
>
- <div
- class="ide-mode-tabs clearfix"
- v-if="!shouldHideEditor">
- <ul class="nav-links pull-left">
+ <div class="ide-mode-tabs clearfix">
+ <ul
+ class="nav-links pull-left"
+ v-if="!shouldHideEditor">
<li :class="editTabCSS">
<a
href="javascript:void(0);"
@@ -210,9 +210,10 @@ export default {
>
</div>
<content-viewer
- v-if="!shouldHideEditor && file.viewMode === 'preview'"
+ v-if="shouldHideEditor || file.viewMode === 'preview'"
:content="file.content || file.raw"
- :path="file.path"
+ :path="file.rawPath"
+ :file-size="file.size"
:project-path="file.projectId"/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 6a143e518f9..eeb14b5490c 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -43,6 +43,7 @@ export default {
raw: null,
baseRaw: null,
html: data.html,
+ size: data.size,
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 4befcc501ef..05a019de54f 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -40,6 +40,7 @@ export const dataStructure = () => ({
eol: '',
viewMode: 'edit',
previewMode: null,
+ size: 0,
});
export const decorateData = entity => {
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index de65ea15a60..914de9de940 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -1,7 +1,12 @@
-/* eslint-disable import/prefer-default-export */
+import $ from 'jquery';
+import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie } from './common_utils';
+
+const isVueMRDiscussions = () => isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
export const addClassIfElementExists = (element, className) => {
if (element) {
element.classList.add(className);
}
};
+
+export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isVueMRDiscussions();
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 94d03621bff..b54ecd2d543 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -7,7 +7,8 @@
* @param {String} text
* @returns {String}
*/
-export const addDelimiter = text => (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
+export const addDelimiter = text =>
+ (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
/**
* Returns '99+' for numbers bigger than 99.
@@ -22,7 +23,8 @@ export const highCountTrim = count => (count > 99 ? '99+' : count);
* @param {String} string
* @requires {String}
*/
-export const humanize = string => string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+export const humanize = string =>
+ string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
/**
* Adds an 's' to the end of the string when count is bigger than 0
@@ -53,7 +55,7 @@ export const slugify = str => str.trim().toLowerCase();
* @param {Number} maxLength
* @returns {String}
*/
-export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
+export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3)}...`;
/**
* Capitalizes first character
@@ -80,3 +82,15 @@ export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, re
* @param {*} string
*/
export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
+
+/**
+ * Converts a sentence to lower case from the second word onwards
+ * e.g. Hello World => Hello world
+ *
+ * @param {*} string
+ */
+export const convertToSentenceCase = string => {
+ const splitWord = string.split(' ').map((word, index) => (index > 0 ? word.toLowerCase() : word));
+
+ return splitWord.join(' ');
+};
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 04d546fafa0..f93b1da4f58 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -1,8 +1,10 @@
<script>
import { scaleLinear, scaleTime } from 'd3-scale';
import { axisLeft, axisBottom } from 'd3-axis';
+import _ from 'underscore';
import { max, extent } from 'd3-array';
import { select } from 'd3-selection';
+import GraphAxis from './graph/axis.vue';
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
@@ -18,10 +20,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }
export default {
components: {
- GraphLegend,
+ GraphAxis,
GraphFlag,
GraphDeployment,
GraphPath,
+ GraphLegend,
},
mixins: [MonitoringMixin],
props: {
@@ -138,7 +141,7 @@ export default {
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;
- this.baseGraphHeight = this.graphHeight;
+ this.baseGraphHeight = this.graphHeight - 50;
this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1
@@ -177,10 +180,8 @@ export default {
this.graphHeightOffset,
);
- if (!this.showLegend) {
- this.baseGraphHeight -= 50;
- } else if (this.timeSeries.length > 3) {
- this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
+ if (_.findWhere(this.timeSeries, { renderCanary: true })) {
+ this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true }));
}
const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
@@ -251,17 +252,13 @@ export default {
class="y-axis"
transform="translate(70, 20)"
/>
- <graph-legend
+ <graph-axis
:graph-width="graphWidth"
:graph-height="graphHeight"
:margin="margin"
:measurements="measurements"
- :legend-title="legendTitle"
:y-axis-label="yAxisLabel"
- :time-series="timeSeries"
:unit-of-display="unitOfDisplay"
- :current-data-index="currentDataIndex"
- :show-legend-group="showLegend"
/>
<svg
class="graph-data"
@@ -306,5 +303,10 @@ export default {
:deployment-flag-data="deploymentFlagData"
/>
</div>
+ <graph-legend
+ v-if="showLegend"
+ :legend-title="legendTitle"
+ :time-series="timeSeries"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue
new file mode 100644
index 00000000000..fc4b3689dfd
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/axis.vue
@@ -0,0 +1,142 @@
+<script>
+import { convertToSentenceCase } from '~/lib/utils/text_utility';
+import { s__ } from '~/locale';
+
+export default {
+ props: {
+ graphWidth: {
+ type: Number,
+ required: true,
+ },
+ graphHeight: {
+ type: Number,
+ required: true,
+ },
+ margin: {
+ type: Object,
+ required: true,
+ },
+ measurements: {
+ type: Object,
+ required: true,
+ },
+ yAxisLabel: {
+ type: String,
+ required: true,
+ },
+ unitOfDisplay: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ yLabelWidth: 0,
+ yLabelHeight: 0,
+ };
+ },
+ computed: {
+ textTransform() {
+ const yCoordinate =
+ (this.graphHeight -
+ this.margin.top +
+ this.measurements.axisLabelLineOffset) /
+ 2 || 0;
+
+ return `translate(15, ${yCoordinate}) rotate(-90)`;
+ },
+
+ rectTransform() {
+ const yCoordinate =
+ (this.graphHeight -
+ this.margin.top +
+ this.measurements.axisLabelLineOffset) /
+ 2 +
+ this.yLabelWidth / 2 || 0;
+
+ return `translate(0, ${yCoordinate}) rotate(-90)`;
+ },
+
+ xPosition() {
+ return (
+ (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 -
+ this.margin.right || 0
+ );
+ },
+
+ yPosition() {
+ return (
+ this.graphHeight -
+ this.margin.top +
+ this.measurements.axisLabelLineOffset || 0
+ );
+ },
+
+ yAxisLabelSentenceCase() {
+ return `${convertToSentenceCase(this.yAxisLabel)} (${this.unitOfDisplay})`;
+ },
+
+ timeString() {
+ return s__('PrometheusDashboard|Time');
+ },
+ },
+ mounted() {
+ this.$nextTick(() => {
+ const bbox = this.$refs.ylabel.getBBox();
+ this.yLabelWidth = bbox.width + 10; // Added some padding
+ this.yLabelHeight = bbox.height + 5;
+ });
+ },
+};
+</script>
+<template>
+ <g class="axis-label-container">
+ <line
+ class="label-x-axis-line"
+ stroke="#000000"
+ stroke-width="1"
+ x1="10"
+ :y1="yPosition"
+ :x2="graphWidth + 20"
+ :y2="yPosition"
+ />
+ <line
+ class="label-y-axis-line"
+ stroke="#000000"
+ stroke-width="1"
+ x1="10"
+ y1="0"
+ :x2="10"
+ :y2="yPosition"
+ />
+ <rect
+ class="rect-axis-text"
+ :transform="rectTransform"
+ :width="yLabelWidth"
+ :height="yLabelHeight"
+ />
+ <text
+ class="label-axis-text y-label-text"
+ text-anchor="middle"
+ :transform="textTransform"
+ ref="ylabel"
+ >
+ {{ yAxisLabelSentenceCase }}
+ </text>
+ <rect
+ class="rect-axis-text"
+ :x="xPosition + 60"
+ :y="graphHeight - 80"
+ width="35"
+ height="50"
+ />
+ <text
+ class="label-axis-text x-label-text"
+ :x="xPosition + 60"
+ :y="yPosition"
+ dy=".35em"
+ >
+ {{ timeString }}
+ </text>
+ </g>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 906c7c51f52..b8202e25685 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -1,11 +1,13 @@
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
-import icon from '../../../vue_shared/components/icon.vue';
+import Icon from '../../../vue_shared/components/icon.vue';
+import TrackLine from './track_line.vue';
export default {
components: {
- icon,
+ Icon,
+ TrackLine,
},
props: {
currentXCoordinate: {
@@ -107,11 +109,6 @@ export default {
}
return `series ${index + 1}`;
},
- strokeDashArray(type) {
- if (type === 'dashed') return '6, 3';
- if (type === 'dotted') return '3, 3';
- return null;
- },
},
};
</script>
@@ -160,28 +157,13 @@ export default {
</div>
</div>
<div class="popover-content">
- <table>
+ <table class="prometheus-table">
<tr
v-for="(series, index) in timeSeries"
:key="index"
>
- <td>
- <svg
- width="15"
- height="6"
- >
- <line
- :stroke="series.lineColor"
- :stroke-dasharray="strokeDashArray(series.lineStyle)"
- stroke-width="4"
- x1="0"
- x2="15"
- y1="2"
- y2="2"
- />
- </svg>
- </td>
- <td>{{ seriesMetricLabel(index, series) }}</td>
+ <track-line :track="series"/>
+ <td>{{ series.track }} {{ seriesMetricLabel(index, series) }}</td>
<td>
<strong>{{ seriesMetricValue(series) }}</strong>
</td>
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index a7a058a9203..da9280cf1f1 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -1,204 +1,72 @@
<script>
-import { formatRelevantDigits } from '../../../lib/utils/number_utils';
+import TrackLine from './track_line.vue';
+import TrackInfo from './track_info.vue';
export default {
+ components: {
+ TrackLine,
+ TrackInfo,
+ },
props: {
- graphWidth: {
- type: Number,
- required: true,
- },
- graphHeight: {
- type: Number,
- required: true,
- },
- margin: {
- type: Object,
- required: true,
- },
- measurements: {
- type: Object,
- required: true,
- },
legendTitle: {
type: String,
required: true,
},
- yAxisLabel: {
- type: String,
- required: true,
- },
timeSeries: {
type: Array,
required: true,
},
- unitOfDisplay: {
- type: String,
- required: true,
- },
- currentDataIndex: {
- type: Number,
- required: true,
- },
- showLegendGroup: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- data() {
- return {
- yLabelWidth: 0,
- yLabelHeight: 0,
- seriesXPosition: 0,
- metricUsageXPosition: 0,
- };
- },
- computed: {
- textTransform() {
- const yCoordinate =
- (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
-
- return `translate(15, ${yCoordinate}) rotate(-90)`;
- },
- rectTransform() {
- const yCoordinate =
- (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
- this.yLabelWidth / 2 || 0;
-
- return `translate(0, ${yCoordinate}) rotate(-90)`;
- },
- xPosition() {
- return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
- },
- yPosition() {
- return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
- },
- },
- 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;
- });
},
methods: {
- translateLegendGroup(index) {
- return `translate(0, ${12 * index})`;
- },
- formatMetricUsage(series) {
- const value =
- series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value;
- if (isNaN(value)) {
- return '-';
- }
- return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
- },
- createSeriesString(index, series) {
- if (series.metricTag) {
- return `${series.metricTag} ${this.formatMetricUsage(series)}`;
- }
- return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
- },
- strokeDashArray(type) {
- if (type === 'dashed') return '6, 3';
- if (type === 'dotted') return '3, 3';
- return null;
+ isStable(track) {
+ return {
+ 'prometheus-table-row-highlight': track.trackName !== 'Canary' && track.renderCanary,
+ };
},
},
};
</script>
<template>
- <g class="axis-label-container">
- <line
- class="label-x-axis-line"
- stroke="#000000"
- stroke-width="1"
- x1="10"
- :y1="yPosition"
- :x2="graphWidth + 20"
- :y2="yPosition"
- />
- <line
- class="label-y-axis-line"
- stroke="#000000"
- stroke-width="1"
- x1="10"
- y1="0"
- :x2="10"
- :y2="yPosition"
- />
- <rect
- class="rect-axis-text"
- :transform="rectTransform"
- :width="yLabelWidth"
- :height="yLabelHeight"
- />
- <text
- class="label-axis-text y-label-text"
- text-anchor="middle"
- :transform="textTransform"
- ref="ylabel"
- >
- {{ yAxisLabel }}
- </text>
- <rect
- class="rect-axis-text"
- :x="xPosition + 60"
- :y="graphHeight - 80"
- width="35"
- height="50"
- />
- <text
- class="label-axis-text x-label-text"
- :x="xPosition + 60"
- :y="yPosition"
- dy=".35em"
- >
- Time
- </text>
- <template v-if="showLegendGroup">
- <g
- class="legend-group"
+ <div class="prometheus-graph-legends prepend-left-10">
+ <table class="prometheus-table">
+ <tr
v-for="(series, index) in timeSeries"
:key="index"
- :transform="translateLegendGroup(index)"
+ v-if="series.shouldRenderLegend"
+ :class="isStable(series)"
>
- <line
- :stroke="series.lineColor"
- :stroke-width="measurements.legends.height"
- :stroke-dasharray="strokeDashArray(series.lineStyle)"
- :x1="measurements.legends.offsetX"
- :x2="measurements.legends.offsetX + measurements.legends.width"
- :y1="graphHeight - measurements.legends.offsetY"
- :y2="graphHeight - measurements.legends.offsetY"
- />
- <text
- v-if="timeSeries.length > 1"
- class="legend-metric-title"
- ref="legendTitleSvg"
- x="38"
- :y="graphHeight - 30"
- >
- {{ createSeriesString(index, series) }}
- </text>
- <text
- v-else
+ <td>
+ <strong v-if="series.renderCanary">{{ series.trackName }}</strong>
+ </td>
+ <track-line :track="series" />
+ <td
class="legend-metric-title"
- ref="legendTitleSvg"
- x="38"
- :y="graphHeight - 30"
- >
- {{ legendTitle }} {{ formatMetricUsage(series) }}
- </text>
- </g>
- </template>
- </g>
+ v-if="timeSeries.length > 1">
+ <track-info
+ :track="series"
+ v-if="series.metricTag" />
+ <track-info
+ v-else
+ :track="series">
+ <strong>{{ legendTitle }}</strong> series {{ index + 1 }}
+ </track-info>
+ </td>
+ <td v-else>
+ <track-info :track="series">
+ <strong>{{ legendTitle }}</strong>
+ </track-info>
+ </td>
+ <template v-for="(track, trackIndex) in series.tracksLegend">
+ <track-line
+ :track="track"
+ :key="`track-line-${trackIndex}`"/>
+ <td :key="`track-info-${trackIndex}`">
+ <track-info
+ class="legend-metric-title"
+ :track="track" />
+ </td>
+ </template>
+ </tr>
+ </table>
+ </div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph/track_info.vue b/app/assets/javascripts/monitoring/components/graph/track_info.vue
new file mode 100644
index 00000000000..ec1c2222af9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_info.vue
@@ -0,0 +1,29 @@
+<script>
+import { formatRelevantDigits } from '~/lib/utils/number_utils';
+
+export default {
+ name: 'TrackInfo',
+ props: {
+ track: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ summaryMetrics() {
+ return `Avg: ${formatRelevantDigits(this.track.average)} · Max: ${formatRelevantDigits(
+ this.track.max,
+ )}`;
+ },
+ },
+};
+</script>
+<template>
+ <span>
+ <slot>
+ <strong> {{ track.metricTag }} </strong>
+ </slot>
+ {{ summaryMetrics }}
+ </span>
+</template>
+
diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue
new file mode 100644
index 00000000000..79b322e2e42
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue
@@ -0,0 +1,36 @@
+<script>
+export default {
+ name: 'TrackLine',
+ props: {
+ track: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ stylizedLine() {
+ if (this.track.lineStyle === 'dashed') return '6, 3';
+ if (this.track.lineStyle === 'dotted') return '3, 3';
+ return null;
+ },
+ },
+};
+</script>
+<template>
+ <td>
+ <svg
+ width="15"
+ height="6">
+ <line
+ :stroke-dasharray="stylizedLine"
+ :stroke="track.lineColor"
+ stroke-width="4"
+ :x1="0"
+ :x2="15"
+ :y1="2"
+ :y2="2"
+ />
+ </svg>
+ </td>
+</template>
+
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 854636e9a89..535c415cd6d 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
function sortMetrics(metrics) {
- return _.chain(metrics).sortBy('weight').sortBy('title').value();
+ return _.chain(metrics).sortBy('title').sortBy('weight').value();
}
function normalizeMetrics(metrics) {
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index b5b8e3c255d..8a93c7e6bae 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -1,10 +1,21 @@
import _ from 'underscore';
import { scaleLinear, scaleTime } from 'd3-scale';
import { line, area, curveLinear } from 'd3-shape';
-import { extent, max } from 'd3-array';
+import { extent, max, sum } from 'd3-array';
import { timeMinute } from 'd3-time';
-
-const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute };
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+const d3 = {
+ scaleLinear,
+ scaleTime,
+ line,
+ area,
+ curveLinear,
+ extent,
+ max,
+ timeMinute,
+ sum,
+};
const defaultColorPalette = {
blue: ['#1f78d1', '#8fbce8'],
@@ -20,6 +31,8 @@ const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) {
let usedColors = [];
+ let renderCanary = false;
+ const timeSeriesParsed = [];
function pickColor(name) {
let pick;
@@ -38,16 +51,23 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
return defaultColorPalette[pick];
}
- return query.result.map((timeSeries, timeSeriesNumber) => {
+ query.result.forEach((timeSeries, timeSeriesNumber) => {
let metricTag = '';
let lineColor = '';
let areaColor = '';
+ let shouldRenderLegend = true;
+ const timeSeriesValues = timeSeries.values.map(d => d.value);
+ const maximumValue = d3.max(timeSeriesValues);
+ const accum = d3.sum(timeSeriesValues);
+ const trackName = capitalizeFirstCharacter(query.track ? query.track : 'Stable');
+
+ if (trackName === 'Canary') {
+ renderCanary = true;
+ }
- const timeSeriesScaleX = d3.scaleTime()
- .range([0, graphWidth - 70]);
+ const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
- const timeSeriesScaleY = d3.scaleLinear()
- .range([graphHeight - graphHeightOffset, 0]);
+ const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(xDom);
timeSeriesScaleX.ticks(d3.timeMinute, 60);
@@ -55,13 +75,15 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
const defined = d => !isNaN(d.value) && d.value != null;
- const lineFunction = d3.line()
+ const lineFunction = d3
+ .line()
.defined(defined)
.curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
- const areaFunction = d3.area()
+ const areaFunction = d3
+ .area()
.defined(defined)
.curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time))
@@ -69,38 +91,62 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
.y1(d => timeSeriesScaleY(d.value));
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
- const seriesCustomizationData = query.series != null &&
- _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
+ const seriesCustomizationData =
+ query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
if (seriesCustomizationData) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
+ shouldRenderLegend = false;
} else {
metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor();
+ if (timeSeriesParsed.length > 1) {
+ shouldRenderLegend = false;
+ }
}
- if (query.track) {
- metricTag += ` - ${query.track}`;
+ if (!shouldRenderLegend) {
+ if (!timeSeriesParsed[0].tracksLegend) {
+ timeSeriesParsed[0].tracksLegend = [];
+ }
+ timeSeriesParsed[0].tracksLegend.push({
+ max: maximumValue,
+ average: accum / timeSeries.values.length,
+ lineStyle,
+ lineColor,
+ metricTag,
+ });
}
- return {
+ timeSeriesParsed.push({
linePath: lineFunction(timeSeries.values),
areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX,
values: timeSeries.values,
+ max: maximumValue,
+ average: accum / timeSeries.values.length,
lineStyle,
lineColor,
areaColor,
metricTag,
- };
+ trackName,
+ shouldRenderLegend,
+ renderCanary,
+ });
});
+
+ return timeSeriesParsed;
}
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
- const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat(
- query.result.reduce((allResults, result) => allResults.concat(result.values), []),
- ), []);
+ const allValues = queries.reduce(
+ (allQueryResults, query) =>
+ allQueryResults.concat(
+ query.result.reduce((allResults, result) => allResults.concat(result.values), []),
+ ),
+ [],
+ );
const xDom = d3.extent(allValues, d => d.time);
const yDom = [0, d3.max(allValues.map(d => d.value))];
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 096c4ef5f31..e3c5bf06b3d 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -13,8 +13,11 @@ export default function initMrNotes() {
data() {
const notesDataset = document.getElementById('js-vue-mr-discussions')
.dataset;
+ const noteableData = JSON.parse(notesDataset.noteableData);
+ noteableData.noteableType = notesDataset.noteableType;
+
return {
- noteableData: JSON.parse(notesDataset.noteableData),
+ noteableData,
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData),
};
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 5bd81c7cad6..ebfc827ac57 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -49,16 +49,7 @@ export default {
computed: {
...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
noteableType() {
- // FIXME -- @fatihacet Get this from JSON data.
- const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
-
- if (this.noteableData.noteableType === EPIC_NOTEABLE_TYPE) {
- return EPIC_NOTEABLE_TYPE;
- }
-
- return this.noteableData.merge_params
- ? MERGE_REQUEST_NOTEABLE_TYPE
- : ISSUE_NOTEABLE_TYPE;
+ return this.noteableData.noteableType;
},
allNotes() {
if (this.isLoading) {
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 68f8cb1cf1e..c4de4826eda 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -14,3 +14,9 @@ export const EPIC_NOTEABLE_TYPE = 'epic';
export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post';
+
+export const NOTEABLE_TYPE_MAPPING = {
+ Issue: ISSUE_NOTEABLE_TYPE,
+ MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE,
+ Epic: EPIC_NOTEABLE_TYPE,
+};
diff --git a/app/assets/javascripts/notes/mixins/noteable.js b/app/assets/javascripts/notes/mixins/noteable.js
index 5bf8216a1f3..b68543d71c8 100644
--- a/app/assets/javascripts/notes/mixins/noteable.js
+++ b/app/assets/javascripts/notes/mixins/noteable.js
@@ -9,16 +9,7 @@ export default {
},
computed: {
noteableType() {
- switch (this.note.noteable_type) {
- case 'MergeRequest':
- return constants.MERGE_REQUEST_NOTEABLE_TYPE;
- case 'Issue':
- return constants.ISSUE_NOTEABLE_TYPE;
- case 'Epic':
- return constants.EPIC_NOTEABLE_TYPE;
- default:
- return '';
- }
+ return constants.NOTEABLE_TYPE_MAPPING[this.note.noteable_type];
},
},
};
diff --git a/app/assets/javascripts/pages/groups/settings/badges/index.js b/app/assets/javascripts/pages/groups/settings/badges/index.js
new file mode 100644
index 00000000000..74e96ee4a8f
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/badges/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { GROUP_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountBadgeSettings(GROUP_BADGE);
+});
diff --git a/app/assets/javascripts/pages/projects/settings/badges/index/index.js b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
new file mode 100644
index 00000000000..30469550866
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { PROJECT_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountBadgeSettings(PROJECT_BADGE);
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
new file mode 100644
index 00000000000..ffc84dc106b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
@@ -0,0 +1,3 @@
+import initForm from '../form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/settings/repository/form.js b/app/assets/javascripts/pages/projects/settings/repository/form.js
new file mode 100644
index 00000000000..a5c17ab322c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/form.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-new */
+
+import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
+import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
+import initSettingsPanels from '~/settings_panels';
+import initDeployKeys from '~/deploy_keys';
+import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
+import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
+import DueDateSelectors from '~/due_date_select';
+
+export default () => {
+ new ProtectedTagCreate();
+ new ProtectedTagEditList();
+ initDeployKeys();
+ initSettingsPanels();
+ new ProtectedBranchCreate(); // eslint-disable-line no-new
+ new ProtectedBranchEditList(); // eslint-disable-line no-new
+ new DueDateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index 788d86d1192..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,17 +1,3 @@
-/* eslint-disable no-new */
+import initForm from '../form';
-import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
-import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
-import initSettingsPanels from '~/settings_panels';
-import initDeployKeys from '~/deploy_keys';
-import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
-import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
-
-document.addEventListener('DOMContentLoaded', () => {
- new ProtectedTagCreate();
- new ProtectedTagEditList();
- initDeployKeys();
- initSettingsPanels();
- new ProtectedBranchCreate(); // eslint-disable-line no-new
- new ProtectedBranchEditList(); // eslint-disable-line no-new
-});
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/shared/mount_badge_settings.js b/app/assets/javascripts/pages/shared/mount_badge_settings.js
new file mode 100644
index 00000000000..1397c0834ff
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/mount_badge_settings.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import store from '~/badges/store';
+
+export default kind => {
+ const badgeSettingsElement = document.getElementById('badge-settings');
+
+ store.dispatch('loadBadges', {
+ kind,
+ apiEndpointUrl: badgeSettingsElement.dataset.apiEndpointUrl,
+ docsUrl: badgeSettingsElement.dataset.docsUrl,
+ });
+
+ return new Vue({
+ el: badgeSettingsElement,
+ store,
+ components: {
+ BadgeSettings,
+ },
+ render(createElement) {
+ return createElement(BadgeSettings);
+ },
+ });
+};
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index d7effb27bff..e99d949801f 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,60 +1,72 @@
<script>
- import tooltip from '../../../vue_shared/directives/tooltip';
- import icon from '../../../vue_shared/components/icon.vue';
- import { dasherize } from '../../../lib/utils/text_utility';
- /**
- * Renders either a cancel, retry or play icon pointing to the given path.
- * TODO: Remove UJS from here and use an async request instead.
- */
- export default {
- components: {
- icon,
- },
+import $ from 'jquery';
+import tooltip from '../../../vue_shared/directives/tooltip';
+import Icon from '../../../vue_shared/components/icon.vue';
+import { dasherize } from '../../../lib/utils/text_utility';
+import eventHub from '../../event_hub';
+/**
+ * Renders either a cancel, retry or play icon pointing to the given path.
+ */
+export default {
+ components: {
+ Icon,
+ },
- directives: {
- tooltip,
- },
+ directives: {
+ tooltip,
+ },
- props: {
- tooltipText: {
- type: String,
- required: true,
- },
+ props: {
+ tooltipText: {
+ type: String,
+ required: true,
+ },
- link: {
- type: String,
- required: true,
- },
+ link: {
+ type: String,
+ required: true,
+ },
- actionMethod: {
- type: String,
- required: true,
- },
+ actionIcon: {
+ type: String,
+ required: true,
+ },
- actionIcon: {
- type: String,
- required: true,
- },
+ buttonDisabled: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ cssClass() {
+ const actionIconDash = dasherize(this.actionIcon);
+ return `${actionIconDash} js-icon-${actionIconDash}`;
+ },
+ isDisabled() {
+ return this.buttonDisabled === this.link;
},
+ },
- computed: {
- cssClass() {
- const actionIconDash = dasherize(this.actionIcon);
- return `${actionIconDash} js-icon-${actionIconDash}`;
- },
+ methods: {
+ onClickAction() {
+ $(this.$el).tooltip('hide');
+ eventHub.$emit('graphAction', this.link);
},
- };
+ },
+};
</script>
<template>
- <a
+ <button
+ type="button"
+ @click="onClickAction"
v-tooltip
- :data-method="actionMethod"
:title="tooltipText"
- :href="link"
- class="ci-action-icon-container ci-action-icon-wrapper"
+ class="btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
data-container="body"
+ :disabled="isDisabled"
>
<icon :name="actionIcon" />
- </a>
+ </button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ab84711d4a2..ac9ce7e47d6 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,54 +1,59 @@
<script>
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
- import stageColumnComponent from './stage_column_component.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import StageColumnComponent from './stage_column_component.vue';
- export default {
- components: {
- stageColumnComponent,
- loadingIcon,
- },
+export default {
+ components: {
+ StageColumnComponent,
+ LoadingIcon,
+ },
- props: {
- isLoading: {
- type: Boolean,
- required: true,
- },
- pipeline: {
- type: Object,
- required: true,
- },
+ props: {
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ actionDisabled: {
+ type: String,
+ required: false,
+ default: null,
},
+ },
- computed: {
- graph() {
- return this.pipeline.details && this.pipeline.details.stages;
- },
+ computed: {
+ graph() {
+ return this.pipeline.details && this.pipeline.details.stages;
},
+ },
- methods: {
- capitalizeStageName(name) {
- return name.charAt(0).toUpperCase() + name.slice(1);
- },
+ methods: {
+ capitalizeStageName(name) {
+ return name.charAt(0).toUpperCase() + name.slice(1);
+ },
- isFirstColumn(index) {
- return index === 0;
- },
+ isFirstColumn(index) {
+ return index === 0;
+ },
- stageConnectorClass(index, stage) {
- let className;
+ stageConnectorClass(index, stage) {
+ let className;
- // If it's the first stage column and only has one job
- if (index === 0 && stage.groups.length === 1) {
- className = 'no-margin';
- } else if (index > 0) {
- // If it is not the first column
- className = 'left-margin';
- }
+ // If it's the first stage column and only has one job
+ if (index === 0 && stage.groups.length === 1) {
+ className = 'no-margin';
+ } else if (index > 0) {
+ // If it is not the first column
+ className = 'left-margin';
+ }
- return className;
- },
+ return className;
},
- };
+ },
+};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
@@ -70,6 +75,7 @@
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
+ :action-disabled="actionDisabled"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index d501c465a96..c6e5ae6df41 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -1,96 +1,102 @@
<script>
- import actionComponent from './action_component.vue';
- import dropdownActionComponent from './dropdown_action_component.vue';
- import jobNameComponent from './job_name_component.vue';
- import tooltip from '../../../vue_shared/directives/tooltip';
-
- /**
- * Renders the badge for the pipeline graph and the job's dropdown.
- *
- * The following object should be provided as `job`:
- *
- * {
- * "id": 4256,
- * "name": "test",
- * "status": {
- * "icon": "icon_status_success",
- * "text": "passed",
- * "label": "passed",
- * "group": "success",
- * "tooltip": "passed",
- * "details_path": "/root/ci-mock/builds/4256",
- * "action": {
- * "icon": "retry",
- * "title": "Retry",
- * "path": "/root/ci-mock/builds/4256/retry",
- * "method": "post"
- * }
- * }
- * }
- */
-
- export default {
- components: {
- actionComponent,
- dropdownActionComponent,
- jobNameComponent,
+import ActionComponent from './action_component.vue';
+import DropdownActionComponent from './dropdown_action_component.vue';
+import JobNameComponent from './job_name_component.vue';
+import tooltip from '../../../vue_shared/directives/tooltip';
+
+/**
+ * Renders the badge for the pipeline graph and the job's dropdown.
+ *
+ * The following object should be provided as `job`:
+ *
+ * {
+ * "id": 4256,
+ * "name": "test",
+ * "status": {
+ * "icon": "icon_status_success",
+ * "text": "passed",
+ * "label": "passed",
+ * "group": "success",
+ * "tooltip": "passed",
+ * "details_path": "/root/ci-mock/builds/4256",
+ * "action": {
+ * "icon": "retry",
+ * "title": "Retry",
+ * "path": "/root/ci-mock/builds/4256/retry",
+ * "method": "post"
+ * }
+ * }
+ * }
+ */
+
+export default {
+ components: {
+ ActionComponent,
+ DropdownActionComponent,
+ JobNameComponent,
+ },
+
+ directives: {
+ tooltip,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
},
- directives: {
- tooltip,
+ cssClassJobName: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- job: {
- type: Object,
- required: true,
- },
-
- cssClassJobName: {
- type: String,
- required: false,
- default: '',
- },
-
- isDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
+
+ isDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ actionDisabled: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+
+ computed: {
+ status() {
+ return this.job && this.job.status ? this.job.status : {};
+ },
+
+ tooltipText() {
+ const textBuilder = [];
+
+ if (this.job.name) {
+ textBuilder.push(this.job.name);
+ }
+
+ if (this.job.name && this.status.tooltip) {
+ textBuilder.push('-');
+ }
+
+ if (this.status.tooltip) {
+ textBuilder.push(`${this.job.status.tooltip}`);
+ }
+
+ return textBuilder.join(' ');
},
- computed: {
- status() {
- return this.job && this.job.status ? this.job.status : {};
- },
-
- tooltipText() {
- const textBuilder = [];
-
- if (this.job.name) {
- textBuilder.push(this.job.name);
- }
-
- if (this.job.name && this.status.tooltip) {
- textBuilder.push('-');
- }
-
- if (this.status.tooltip) {
- textBuilder.push(`${this.job.status.tooltip}`);
- }
-
- return textBuilder.join(' ');
- },
-
- /**
- * Verifies if the provided job has an action path
- *
- * @return {Boolean}
- */
- hasAction() {
- return this.job.status && this.job.status.action && this.job.status.action.path;
- },
+ /**
+ * Verifies if the provided job has an action path
+ *
+ * @return {Boolean}
+ */
+ hasAction() {
+ return this.job.status && this.job.status.action && this.job.status.action.path;
},
- };
+ },
+};
</script>
<template>
<div class="ci-job-component">
@@ -132,7 +138,7 @@
:tooltip-text="status.action.title"
:link="status.action.path"
:action-icon="status.action.icon"
- :action-method="status.action.method"
+ :button-disabled="actionDisabled"
/>
<dropdown-action-component
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 7adcf4017b8..f6e6569e15b 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,50 +1,55 @@
<script>
- import jobComponent from './job_component.vue';
- import dropdownJobComponent from './dropdown_job_component.vue';
+import JobComponent from './job_component.vue';
+import DropdownJobComponent from './dropdown_job_component.vue';
- export default {
- components: {
- jobComponent,
- dropdownJobComponent,
+export default {
+ components: {
+ JobComponent,
+ DropdownJobComponent,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
},
- props: {
- title: {
- type: String,
- required: true,
- },
- jobs: {
- type: Array,
- required: true,
- },
+ jobs: {
+ type: Array,
+ required: true,
+ },
- isFirstColumn: {
- type: Boolean,
- required: false,
- default: false,
- },
+ isFirstColumn: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
- stageConnectorClass: {
- type: String,
- required: false,
- default: '',
- },
+ stageConnectorClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actionDisabled: {
+ type: String,
+ required: false,
+ default: null,
},
+ },
- methods: {
- firstJob(list) {
- return list[0];
- },
+ methods: {
+ firstJob(list) {
+ return list[0];
+ },
- jobId(job) {
- return `ci-badge-${job.name}`;
- },
+ jobId(job) {
+ return `ci-badge-${job.name}`;
+ },
- buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
- },
+ buildConnnectorClass(index) {
+ return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
- };
+ },
+};
</script>
<template>
<li
@@ -69,6 +74,7 @@
v-if="job.size === 1"
:job="job"
css-class-job-name="build-content"
+ :action-disabled="actionDisabled"
/>
<dropdown-job-component
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 6b26708148c..900eb7855f4 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -25,13 +25,36 @@ export default () => {
data() {
return {
mediator,
+ actionDisabled: null,
};
},
+ created() {
+ eventHub.$on('graphAction', this.postAction);
+ },
+ beforeDestroy() {
+ eventHub.$off('graphAction', this.postAction);
+ },
+ methods: {
+ postAction(action) {
+ this.actionDisabled = action;
+
+ this.mediator.service.postAction(action)
+ .then(() => {
+ this.mediator.refreshPipeline();
+ this.actionDisabled = null;
+ })
+ .catch(() => {
+ this.actionDisabled = null;
+ Flash(__('An error occurred while making the request.'));
+ });
+ },
+ },
render(createElement) {
return createElement('pipeline-graph', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
+ actionDisabled: this.actionDisabled,
},
});
},
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index 10f238fe73b..621969cd622 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -52,8 +52,11 @@ export default class pipelinesMediator {
}
refreshPipeline() {
- this.service.getPipeline()
+ this.poll.stop();
+
+ return this.service.getPipeline()
.then(response => this.successCallback(response))
- .catch(() => this.errorCallback());
+ .catch(() => this.errorCallback())
+ .finally(() => this.poll.restart());
}
}
diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue
new file mode 100644
index 00000000000..e5de3f69b01
--- /dev/null
+++ b/app/assets/javascripts/profile/account/components/update_username.vue
@@ -0,0 +1,121 @@
+<script>
+import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { s__, sprintf } from '~/locale';
+import Flash from '~/flash';
+
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ actionUrl: {
+ type: String,
+ required: true,
+ },
+ rootUrl: {
+ type: String,
+ required: true,
+ },
+ initialUsername: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isRequestPending: false,
+ username: this.initialUsername,
+ newUsername: this.initialUsername,
+ };
+ },
+ computed: {
+ path() {
+ return sprintf(s__('Profiles|Current path: %{path}'), {
+ path: `${this.rootUrl}${this.username}`,
+ });
+ },
+ modalText() {
+ return sprintf(
+ s__(`Profiles|
+You are going to change the username %{currentUsernameBold} to %{newUsernameBold}.
+Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group.
+Please update your Git repository remotes as soon as possible.`),
+ {
+ currentUsernameBold: `<strong>${_.escape(this.username)}</strong>`,
+ newUsernameBold: `<strong>${_.escape(this.newUsername)}</strong>`,
+ currentUsername: _.escape(this.username),
+ newUsername: _.escape(this.newUsername),
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ onConfirm() {
+ this.isRequestPending = true;
+ const username = this.newUsername;
+ const putData = {
+ user: {
+ username,
+ },
+ };
+
+ return axios
+ .put(this.actionUrl, putData)
+ .then(result => {
+ Flash(result.data.message, 'notice');
+ this.username = username;
+ this.isRequestPending = false;
+ })
+ .catch(error => {
+ Flash(error.response.data.message);
+ this.isRequestPending = false;
+ throw error;
+ });
+ },
+ },
+ modalId: 'username-change-confirmation-modal',
+ inputId: 'username-change-input',
+ buttonText: s__('Profiles|Update username'),
+};
+</script>
+<template>
+ <div>
+ <div class="form-group">
+ <label :for="$options.inputId">{{ s__('Profiles|Path') }}</label>
+ <div class="input-group">
+ <div class="input-group-addon">{{ rootUrl }}</div>
+ <input
+ :id="$options.inputId"
+ class="form-control"
+ required="required"
+ v-model="newUsername"
+ :disabled="isRequestPending"
+ />
+ </div>
+ <p class="help-block">
+ {{ path }}
+ </p>
+ </div>
+ <button
+ :data-target="`#${$options.modalId}`"
+ class="btn btn-warning"
+ type="button"
+ data-toggle="modal"
+ :disabled="isRequestPending || newUsername === username"
+ >
+ {{ $options.buttonText }}
+ </button>
+ <gl-modal
+ :id="$options.modalId"
+ :header-title-text="s__('Profiles|Change username') + '?'"
+ footer-primary-button-variant="warning"
+ :footer-primary-button-text="$options.buttonText"
+ @submit="onConfirm"
+ >
+ <span v-html="modalText"></span>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index 84049a1f0b7..59c13e1a042 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -1,10 +1,25 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
+import UpdateUsername from './components/update_username.vue';
import deleteAccountModal from './components/delete_account_modal.vue';
export default () => {
Vue.use(Translate);
+ const updateUsernameElement = document.getElementById('update-username');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: updateUsernameElement,
+ components: {
+ UpdateUsername,
+ },
+ render(createElement) {
+ return createElement('update-username', {
+ props: { ...updateUsernameElement.dataset },
+ });
+ },
+ });
+
const deleteAccountButton = document.getElementById('delete-account-button');
const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 7dd3e9858c6..2da022fde63 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -233,21 +233,21 @@ export default class SearchAutocomplete {
const issueItems = [
{
text: 'Issues assigned to me',
- url: `${issuesPath}/?assignee_username=${userName}`,
+ url: `${issuesPath}/?assignee_id=${userId}`,
},
{
text: "Issues I've created",
- url: `${issuesPath}/?author_username=${userName}`,
+ url: `${issuesPath}/?author_id=${userId}`,
},
];
const mergeRequestItems = [
{
text: 'Merge requests assigned to me',
- url: `${mrPath}/?assignee_username=${userName}`,
+ url: `${mrPath}/?assignee_id=${userId}`,
},
{
text: "Merge requests I've created",
- url: `${mrPath}/?author_username=${userName}`,
+ url: `${mrPath}/?author_id=${userId}`,
},
];
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
index fb8ccea91c7..4155e1bab9c 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -1,17 +1,24 @@
<script>
import { viewerInformationForPath } from './lib/viewer_utils';
import MarkdownViewer from './viewers/markdown_viewer.vue';
+import ImageViewer from './viewers/image_viewer.vue';
+import DownloadViewer from './viewers/download_viewer.vue';
export default {
props: {
content: {
type: String,
- required: true,
+ default: '',
},
path: {
type: String,
required: true,
},
+ fileSize: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
projectPath: {
type: String,
required: false,
@@ -20,12 +27,18 @@ export default {
},
computed: {
viewer() {
+ if (!this.path) return null;
+
const previewInfo = viewerInformationForPath(this.path);
+ if (!previewInfo) return DownloadViewer;
+
switch (previewInfo.id) {
case 'markdown':
return MarkdownViewer;
+ case 'image':
+ return ImageViewer;
default:
- return null;
+ return DownloadViewer;
}
},
},
@@ -36,6 +49,8 @@ export default {
<div class="preview-container">
<component
:is="viewer"
+ :path="path"
+ :file-size="fileSize"
:project-path="projectPath"
:content="content"
/>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
index 4f2e1e47dd1..f01a51da0b3 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -1,4 +1,7 @@
const viewers = {
+ image: {
+ id: 'image',
+ },
markdown: {
id: 'markdown',
previewTitle: 'Preview Markdown',
@@ -7,6 +10,12 @@ const viewers = {
const fileNameViewers = {};
const fileExtensionViewers = {
+ jpg: 'image',
+ jpeg: 'image',
+ gif: 'image',
+ png: 'image',
+ bmp: 'image',
+ ico: 'image',
md: 'markdown',
markdown: 'markdown',
};
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
new file mode 100644
index 00000000000..395a71acccf
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
@@ -0,0 +1,52 @@
+<script>
+import Icon from '../../icon.vue';
+import { numberToHumanSize } from '../../../../lib/utils/number_utils';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ fileSize: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ },
+ computed: {
+ fileSizeReadable() {
+ return numberToHumanSize(this.fileSize);
+ },
+ fileName() {
+ return this.path.split('/').pop();
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="file-container">
+ <div class="file-content">
+ <p class="prepend-top-10 file-info">
+ {{ fileName }} ({{ fileSizeReadable }})
+ </p>
+ <a
+ :href="path"
+ class="btn btn-default"
+ rel="nofollow"
+ download
+ target="_blank">
+ <icon
+ name="download"
+ css-classes="pull-left append-right-8"
+ :size="16"
+ />
+ {{ __('Download') }}
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
new file mode 100644
index 00000000000..a5999f909ca
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
@@ -0,0 +1,68 @@
+<script>
+import { numberToHumanSize } from '../../../../lib/utils/number_utils';
+
+export default {
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ fileSize: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ width: 0,
+ height: 0,
+ isZoomable: false,
+ isZoomed: false,
+ };
+ },
+ computed: {
+ fileSizeReadable() {
+ return numberToHumanSize(this.fileSize);
+ },
+ },
+ methods: {
+ onImgLoad() {
+ const contentImg = this.$refs.contentImg;
+ this.isZoomable =
+ contentImg.naturalWidth > contentImg.width || contentImg.naturalHeight > contentImg.height;
+
+ this.width = contentImg.naturalWidth;
+ this.height = contentImg.naturalHeight;
+ },
+ onImgClick() {
+ if (this.isZoomable) this.isZoomed = !this.isZoomed;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="file-container">
+ <div class="file-content image_file">
+ <img
+ ref="contentImg"
+ :class="{ 'isZoomable': isZoomable, 'isZoomed': isZoomed }"
+ :src="path"
+ :alt="path"
+ @load="onImgLoad"
+ @click="onImgClick"/>
+ <p class="file-info prepend-top-10">
+ <template v-if="fileSize>0">
+ {{ fileSizeReadable }}
+ </template>
+ <template v-if="fileSize>0 && width && height">
+ -
+ </template>
+ <template v-if="width && height">
+ {{ width }} x {{ height }}
+ </template>
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index 67c9181c7b1..f28e5e2715d 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -1,47 +1,42 @@
<script>
- const buttonVariants = [
- 'danger',
- 'primary',
- 'success',
- 'warning',
- ];
+const buttonVariants = ['danger', 'primary', 'success', 'warning'];
- export default {
- name: 'GlModal',
+export default {
+ name: 'GlModal',
- props: {
- id: {
- type: String,
- required: false,
- default: null,
- },
- headerTitleText: {
- type: String,
- required: false,
- default: '',
- },
- footerPrimaryButtonVariant: {
- type: String,
- required: false,
- default: 'primary',
- validator: value => buttonVariants.indexOf(value) !== -1,
- },
- footerPrimaryButtonText: {
- type: String,
- required: false,
- default: '',
- },
+ props: {
+ id: {
+ type: String,
+ required: false,
+ default: null,
},
+ headerTitleText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ footerPrimaryButtonVariant: {
+ type: String,
+ required: false,
+ default: 'primary',
+ validator: value => buttonVariants.includes(value),
+ },
+ footerPrimaryButtonText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
- methods: {
- emitCancel(event) {
- this.$emit('cancel', event);
- },
- emitSubmit(event) {
- this.$emit('submit', event);
- },
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
},
- };
+ },
+};
</script>
<template>
@@ -60,7 +55,7 @@
<slot name="header">
<button
type="button"
- class="close"
+ class="close js-modal-close-action"
data-dismiss="modal"
:aria-label="s__('Modal|Close')"
@click="emitCancel($event)"
@@ -83,7 +78,7 @@
<slot name="footer">
<button
type="button"
- class="btn"
+ class="btn js-modal-cancel-action"
data-dismiss="modal"
@click="emitCancel($event)"
>
@@ -91,7 +86,7 @@
</button>
<button
type="button"
- class="btn"
+ class="btn js-modal-primary-action"
:class="`btn-${footerPrimaryButtonVariant}`"
data-dismiss="modal"
@click="emitSubmit($event)"
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index df1cafc9f8e..62a0fba3da3 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -20,7 +20,7 @@
width: 100%;
}
- $image-widths: 80 250 306 394 430;
+ $image-widths: 80 130 250 306 394 430;
@each $width in $image-widths {
&.svg-#{$width} {
img,
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 7829d722560..34fccf6f0a4 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -39,7 +39,7 @@
.table-section {
white-space: nowrap;
- $section-widths: 10 15 20 25 30 40 100;
+ $section-widths: 10 15 20 25 30 40 50 100;
@each $width in $section-widths {
&.section-#{$width} {
flex: 0 0 #{$width + '%'};
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index a81904d5338..8ee1bb03d55 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -767,3 +767,8 @@ $border-color-settings: #e1e1e1;
Modals
*/
$modal-body-height: 134px;
+
+/*
+Prometheus
+*/
+$prometheus-table-row-highlight-color: $theme-gray-100;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b487f6278c2..86cdda0359e 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -107,7 +107,6 @@
}
}
-
.commits-compare-switch {
float: left;
margin-right: 9px;
@@ -179,7 +178,7 @@
.commit-detail {
display: flex;
justify-content: space-between;
- align-items: flex-start;
+ align-items: center;
flex-grow: 1;
.merge-request-branches & {
@@ -200,37 +199,63 @@
}
.ci-status-link {
- display: inline-block;
- position: relative;
- top: 2px;
+ display: inline-flex;
}
- .btn-clipboard,
- .btn-transparent {
- padding-left: 0;
- padding-right: 0;
+ > .ci-status-link,
+ > .btn,
+ > .commit-sha-group {
+ margin-left: $gl-padding-8;
}
+}
+.commit-sha-group {
+ display: inline-flex;
+
+ .label,
.btn {
- &:not(:first-child) {
- margin-left: $gl-padding;
- }
+ padding: $gl-vert-padding $gl-btn-padding;
+ border: 1px $border-color solid;
+ font-size: $gl-font-size;
+ line-height: $line-height-base;
+ border-radius: 0;
+ display: flex;
+ align-items: center;
+ }
+
+ .label-monospace {
+ @extend .monospace;
+ user-select: text;
+ color: $gl-text-color;
+ background-color: $gray-light;
}
- .commit-sha {
- font-size: 14px;
- font-weight: $gl-font-weight-bold;
+ .btn svg {
+ top: auto;
+ fill: $gl-text-color-secondary;
}
- .ci-status-icon {
- position: relative;
- top: 2px;
+ .fa-clipboard {
+ color: $gl-text-color-secondary;
+ }
+
+ :first-child {
+ border-bottom-left-radius: $border-radius-default;
+ border-top-left-radius: $border-radius-default;
+ }
+
+ :not(:first-child) {
+ border-left: 0;
+ }
+
+ :last-child {
+ border-bottom-right-radius: $border-radius-default;
+ border-top-right-radius: $border-radius-default;
}
}
.commit,
.generic_commit_status {
-
a,
button {
color: $gl-text-color;
@@ -303,10 +328,8 @@
}
}
-
.gpg-status-box {
padding: 2px 10px;
- margin-right: $gl-padding;
&:empty {
display: none;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 58700661142..3a300086fa3 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -273,21 +273,6 @@
line-height: 1.2;
}
- table {
- border-collapse: collapse;
- padding: 0;
- margin: 0;
- }
-
- td {
- vertical-align: middle;
-
- + td {
- padding-left: 5px;
- vertical-align: top;
- }
- }
-
.deploy-meta-content {
border-bottom: 1px solid $white-dark;
@@ -323,6 +308,26 @@
}
}
+.prometheus-table {
+ border-collapse: collapse;
+ padding: 0;
+ margin: 0;
+
+ td {
+ vertical-align: middle;
+
+ + td {
+ padding-left: 5px;
+ vertical-align: top;
+ }
+ }
+
+ .legend-metric-title {
+ font-size: 12px;
+ vertical-align: middle;
+ }
+}
+
.prometheus-svg-container {
position: relative;
height: 0;
@@ -330,8 +335,7 @@
padding: 0;
padding-bottom: 100%;
- .text-metric-usage,
- .legend-metric-title {
+ .text-metric-usage {
fill: $black;
font-weight: $gl-font-weight-normal;
font-size: 12px;
@@ -374,10 +378,6 @@
}
}
- .text-metric-title {
- font-size: 12px;
- }
-
.y-label-text,
.x-label-text {
fill: $gray-darkest;
@@ -414,3 +414,7 @@
}
}
}
+
+.prometheus-table-row-highlight {
+ background-color: $prometheus-table-row-highlight-color;
+}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index ce2f1482456..8d5eb2e8c5a 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -495,17 +495,17 @@
svg {
fill: $gl-text-color-secondary;
position: relative;
- left: 5px;
- top: 2px;
- width: 18px;
- height: 18px;
+ left: 1px;
+ top: -1px;
+ width: 16px;
+ height: 16px;
}
&.play {
svg {
- width: #{$ci-action-icon-size - 8};
- height: #{$ci-action-icon-size - 8};
- left: 8px;
+ width: 16px;
+ height: 16px;
+ left: 3px;
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 9a770d77685..790e91e4431 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1143,3 +1143,11 @@ pre.light-well {
white-space: pre-wrap;
}
}
+
+.project-badge {
+ opacity: 0.9;
+
+ &:hover {
+ opacity: 1;
+ }
+}
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 8cc5c8fc877..a414deb8921 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -312,6 +312,45 @@
height: 100%;
overflow: auto;
+ .file-container {
+ background-color: $gray-darker;
+ display: flex;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+
+ text-align: center;
+
+ .file-content {
+ padding: $gl-padding;
+ max-width: 100%;
+ max-height: 100%;
+
+ img {
+ max-width: 90%;
+ max-height: 90%;
+ }
+
+ .isZoomable {
+ cursor: pointer;
+ cursor: zoom-in;
+
+ &.isZoomed {
+ cursor: pointer;
+ cursor: zoom-out;
+ max-width: none;
+ max-height: none;
+ margin-right: $gl-padding;
+ }
+ }
+ }
+
+ .file-info {
+ font-size: $label-font-size;
+ color: $diff-image-info-color;
+ }
+ }
+
.md-previewer {
padding: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index a6ca8ed5016..c410049bc0b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -284,3 +284,23 @@
.deprecated-service {
cursor: default;
}
+
+.personal-access-tokens-never-expires-label {
+ color: $note-disabled-comment-color;
+}
+
+.created-deploy-token-container {
+ .deploy-token-field {
+ width: 90%;
+ display: inline;
+ }
+
+ .btn-clipboard {
+ margin-left: 5px;
+ }
+
+ .deploy-token-help-block {
+ display: block;
+ margin-bottom: 0;
+ }
+}
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 19dbee84c11..7d7ff217e5d 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -96,7 +96,8 @@ module Boards
resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true,
- sidebar_endpoints: true,
+ issue_endpoints: true,
+ include_full_project_path: board.group_board?,
include: {
project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 2753f83c3cf..2fdf346ef44 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -10,7 +10,7 @@ module AuthenticatesWithTwoFactor
# This action comes from DeviseController, but because we call `sign_in`
# manually, not skipping this action would cause a "You are already signed
# in." error message to be shown upon successful login.
- skip_before_action :require_no_authentication, only: [:create]
+ skip_before_action :require_no_authentication, only: [:create], raise: false
end
# Store the user's ID in the session for later retrieval and render the
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 280ed93faf8..68d328fa797 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -2,9 +2,17 @@ class DashboardController < Dashboard::ApplicationController
include IssuesAction
include MergeRequestsAction
+ FILTER_PARAMS = [
+ :author_id,
+ :assignee_id,
+ :milestone_title,
+ :label_name
+ ].freeze
+
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests]
+ before_action :check_filters_presence!, only: [:issues, :merge_requests]
respond_to :html
@@ -39,4 +47,15 @@ class DashboardController < Dashboard::ApplicationController
def set_show_full_reference
@show_full_reference = true
end
+
+ def check_filters_presence!
+ @no_filters_set = FILTER_PARAMS.none? { |k| params.key?(k) }
+
+ return unless @no_filters_set
+
+ respond_to do |format|
+ format.html
+ format.atom { head :bad_request }
+ end
+ end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index acf6aaf57f4..5903689dc62 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
- render json: milestones.map { |m| m.for_display.slice(:title, :name) }
+ render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) }
end
end
end
diff --git a/app/controllers/groups/settings/badges_controller.rb b/app/controllers/groups/settings/badges_controller.rb
new file mode 100644
index 00000000000..edb334a3d88
--- /dev/null
+++ b/app/controllers/groups/settings/badges_controller.rb
@@ -0,0 +1,13 @@
+module Groups
+ module Settings
+ class BadgesController < Groups::ApplicationController
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ before_action :authorize_admin_group!
+
+ def index
+ @badge_api_endpoint = api_v4_groups_badges_path(id: @group.id)
+ end
+ end
+ end
+end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 7d6fe6a0232..67057b5b126 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -25,8 +25,7 @@ class JwtController < ApplicationController
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
- if @authentication_result.failed? ||
- (@authentication_result.actor.present? && !@authentication_result.actor.is_a?(User))
+ if @authentication_result.failed?
render_unauthorized
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 3d27ae18b17..ac71f72e624 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -53,13 +53,19 @@ class ProfilesController < Profiles::ApplicationController
def update_username
result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute
- options = if result[:status] == :success
- { notice: "Username successfully changed" }
- else
- { alert: "Username change failed - #{result[:message]}" }
- end
+ respond_to do |format|
+ if result[:status] == :success
+ message = s_("Profiles|Username successfully changed")
- redirect_back_or_default(default: { action: 'show' }, options: options)
+ format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
+ format.json { render json: { message: message }, status: :ok }
+ else
+ message = s_("Profiles|Username change failed - %{message}") % { message: result[:message] }
+
+ format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: message }) }
+ format.json { render json: { message: message }, status: :unprocessable_entity }
+ end
+ end
end
private
diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb
new file mode 100644
index 00000000000..2f91b8f36de
--- /dev/null
+++ b/app/controllers/projects/deploy_tokens_controller.rb
@@ -0,0 +1,10 @@
+class Projects::DeployTokensController < Projects::ApplicationController
+ before_action :authorize_admin_project!
+
+ def revoke
+ @token = @project.deploy_tokens.find(params[:id])
+ @token.revoke!
+
+ redirect_to project_settings_repository_path(project)
+ end
+end
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index d5af0341d18..a6167e9dc6c 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,6 +1,9 @@
class Projects::RepositoriesController < Projects::ApplicationController
+ include ExtractsPath
+
# Authorize
before_action :require_non_empty_project, except: :create
+ before_action :assign_archive_vars, only: :archive
before_action :authorize_download_code!
before_action :authorize_admin_project!, only: :create
@@ -11,9 +14,21 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- send_git_archive @repository, ref: params[:ref], format: params[:format]
+ append_sha = params[:append_sha]
+
+ shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
+ append_sha = false if @filename == shortname
+
+ send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
end
+
+ def assign_archive_vars
+ @id = params[:id]
+ @ref, @filename = extract_ref(@id)
+ rescue InvalidPathError
+ render_404
+ end
end
diff --git a/app/controllers/projects/settings/badges_controller.rb b/app/controllers/projects/settings/badges_controller.rb
new file mode 100644
index 00000000000..f7b70dd4b7b
--- /dev/null
+++ b/app/controllers/projects/settings/badges_controller.rb
@@ -0,0 +1,13 @@
+module Projects
+ module Settings
+ class BadgesController < Projects::ApplicationController
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ before_action :authorize_admin_project!
+
+ def index
+ @badge_api_endpoint = api_v4_projects_badges_path(id: @project.id)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index dd9e4a2af3e..f17056f13e0 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -4,13 +4,31 @@ module Projects
before_action :authorize_admin_project!
def show
- @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
+ render_show
+ end
- define_protected_refs
+ def create_deploy_token
+ @new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
+
+ if @new_deploy_token.persisted?
+ flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
+ end
+
+ render_show
end
private
+ def render_show
+ @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
+ @deploy_tokens = @project.deploy_tokens.active
+
+ define_deploy_token
+ define_protected_refs
+
+ render 'show'
+ end
+
def define_protected_refs
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_tags = @project.protected_tags.order(:name).page(params[:page])
@@ -51,6 +69,14 @@ module Projects
gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options)
end
+
+ def define_deploy_token
+ @new_deploy_token ||= DeployToken.new
+ end
+
+ def deploy_token_params
+ params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry)
+ end
end
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 86ec500ceb3..228c8d2e8f9 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -228,9 +228,7 @@ module ApplicationHelper
scope: params[:scope],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
- assignee_username: params[:assignee_username],
author_id: params[:author_id],
- author_username: params[:author_username],
search: params[:search],
label_name: params[:label_name]
}
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 0333c29e2fd..7cc56de24e4 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -93,25 +93,18 @@ module CommitsHelper
return unless current_controller?(:commits)
if @path.blank?
- return link_to(
- _("Browse Files"),
- project_tree_path(project, commit),
- class: "btn btn-default"
- )
+ url = project_tree_path(project, commit)
+ tooltip = _("Browse Files")
elsif @repo.blob_at(commit.id, @path)
- return link_to(
- _("Browse File"),
- project_blob_path(project,
- tree_join(commit.id, @path)),
- class: "btn btn-default"
- )
+ url = project_blob_path(project, tree_join(commit.id, @path))
+ tooltip = _("Browse File")
elsif @path.present?
- return link_to(
- _("Browse Directory"),
- project_tree_path(project,
- tree_join(commit.id, @path)),
- class: "btn btn-default"
- )
+ url = project_tree_path(project, tree_join(commit.id, @path))
+ tooltip = _("Browse Directory")
+ end
+
+ link_to url, class: "btn btn-default has-tooltip", title: tooltip, data: { container: "body" } do
+ sprite_icon('folder-open')
end
end
diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb
new file mode 100644
index 00000000000..bd921322476
--- /dev/null
+++ b/app/helpers/deploy_tokens_helper.rb
@@ -0,0 +1,12 @@
+module DeployTokensHelper
+ def expand_deploy_tokens_section?(deploy_token)
+ deploy_token.persisted? ||
+ deploy_token.errors.present? ||
+ Rails.env.test?
+ end
+
+ def container_registry_enabled?(project)
+ Gitlab.config.registry.enabled &&
+ can?(current_user, :read_container_image, project)
+ end
+end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 16eceb3f48f..95fea2f18d1 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,6 +1,6 @@
module GroupsHelper
def group_nav_link_paths
- %w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
+ %w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
def group_sidebar_links
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 6d6b840f485..06c3e569c84 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -159,16 +159,18 @@ module IssuablesHelper
label_names.join(', ')
end
- def issuables_state_counter_text(issuable_type, state)
+ def issuables_state_counter_text(issuable_type, state, display_count)
titles = {
opened: "Open"
}
state_title = titles[state] || state.to_s.humanize
- count = issuables_count_for_state(issuable_type, state)
-
html = content_tag(:span, state_title)
- html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
+
+ if display_count
+ count = issuables_count_for_state(issuable_type, state)
+ html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
+ end
html.html_safe
end
@@ -191,24 +193,10 @@ module IssuablesHelper
end
end
- def issuable_filter_params
- [
- :search,
- :author_id,
- :assignee_id,
- :milestone_title,
- :label_name
- ]
- end
-
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
- def issuable_filter_present?
- issuable_filter_params.any? { |k| params.key?(k) }
- end
-
def issuable_initial_data(issuable)
data = {
endpoint: issuable_path(issuable),
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 88f374be1e5..9f78b80c71d 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -24,8 +24,8 @@ module WorkhorseHelper
end
# Archive a Git repository and send it through Workhorse
- def send_git_archive(repository, ref:, format:)
- headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+ def send_git_archive(repository, **kwargs)
+ headers.store(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
head :ok
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 3f7f36e83c0..de860df4b9c 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -30,6 +30,8 @@ class Commit
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
+ # Used by GFM to match and present link extensions on node texts and hrefs.
+ LINK_EXTENSION_PATTERN = /(patch)/.freeze
def banzai_render_context(field)
pipeline = field == :description ? :commit_description : :single_line
@@ -143,7 +145,8 @@ class Commit
end
def self.link_reference_pattern
- @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
+ @link_reference_pattern ||=
+ super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/)
end
def to_reference(from = nil, full: false)
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
new file mode 100644
index 00000000000..fe726b156d4
--- /dev/null
+++ b/app/models/deploy_token.rb
@@ -0,0 +1,61 @@
+class DeployToken < ActiveRecord::Base
+ include Expirable
+ include TokenAuthenticatable
+ add_authentication_token_field :token
+
+ AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
+
+ default_value_for(:expires_at) { Forever.date }
+
+ has_many :project_deploy_tokens, inverse_of: :deploy_token
+ has_many :projects, through: :project_deploy_tokens
+
+ validate :ensure_at_least_one_scope
+ before_save :ensure_token
+
+ accepts_nested_attributes_for :project_deploy_tokens
+
+ scope :active, -> { where("revoked = false AND expires_at >= NOW()") }
+
+ def revoke!
+ update!(revoked: true)
+ end
+
+ def active?
+ !revoked
+ end
+
+ def scopes
+ AVAILABLE_SCOPES.select { |token_scope| read_attribute(token_scope) }
+ end
+
+ def username
+ "gitlab+deploy-token-#{id}"
+ end
+
+ def has_access_to?(requested_project)
+ project == requested_project
+ end
+
+ # This is temporal. Currently we limit DeployToken
+ # to a single project, later we're going to extend
+ # that to be for multiple projects and namespaces.
+ def project
+ projects.first
+ end
+
+ def expires_at
+ expires_at = read_attribute(:expires_at)
+ expires_at != Forever.date ? expires_at : nil
+ end
+
+ def expires_at=(value)
+ write_attribute(:expires_at, value.presence || Forever.date)
+ end
+
+ private
+
+ def ensure_at_least_one_scope
+ errors.add(:base, "Scopes can't be blank") unless read_repository || read_registry
+ end
+end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 9517723d9d9..fddb269af4b 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -224,7 +224,7 @@ class Environment < ActiveRecord::Base
end
def deployment_platform
- project.deployment_platform(environment: self)
+ project.deployment_platform(environment: self.name)
end
private
diff --git a/app/models/group.rb b/app/models/group.rb
index 3cfe21ac93b..8ff781059cc 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -286,6 +286,10 @@ class Group < Namespace
false
end
+ def refresh_project_authorizations
+ refresh_members_authorized_projects(blocking: false)
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 13abc6c1a0d..13d9e42bcc8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -272,11 +272,17 @@ class Issue < ActiveRecord::Base
def as_json(options = {})
super(options).tap do |json|
- if options.key?(:sidebar_endpoints) && project
+ if options.key?(:issue_endpoints) && project
url_helper = Gitlab::Routing.url_helpers
- json.merge!(issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
- toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self))
+ issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference
+
+ json.merge!(
+ reference_path: issue_reference,
+ real_path: url_helper.project_issue_path(project, self),
+ issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
+ toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self)
+ )
end
if options.key?(:labels)
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index b75387e236e..1c2e57bb01f 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -17,7 +17,7 @@ class MergeRequestDiffCommit < ActiveRecord::Base
commit_hash.merge(
merge_request_diff_id: merge_request_diff_id,
relative_order: index,
- sha: sha_attribute.type_cast_for_database(sha),
+ sha: sha_attribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize
authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]),
committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date])
)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index dafae58d121..a66a0015827 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -34,8 +34,8 @@ class Milestone < ActiveRecord::Base
scope :for_projects_and_groups, -> (project_ids, group_ids) do
conditions = []
- conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any?
- conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any?
+ conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any?
+ conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any?
where(conditions.reduce(:or))
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index e350b675639..2b63aa33222 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -252,6 +252,10 @@ class Namespace < ActiveRecord::Base
[]
end
+ def refresh_project_authorizations
+ owner.refresh_authorized_projects
+ end
+
private
def path_or_parent_changed?
diff --git a/app/models/project.rb b/app/models/project.rb
index 1b29cbf28d2..3f805dd1fc9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -222,6 +222,8 @@ class Project < ActiveRecord::Base
has_many :environments
has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
+ has_many :project_deploy_tokens
+ has_many :deploy_tokens, through: :project_deploy_tokens
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -1472,7 +1474,9 @@ class Project < ActiveRecord::Base
end
def rename_repo_notify!
- send_move_instructions(full_path_was)
+ # When we import a project overwriting the original project, there
+ # is a move operation. In that case we don't want to send the instructions.
+ send_move_instructions(full_path_was) unless started?
expires_full_path_cache
self.old_path_with_namespace = full_path_was
diff --git a/app/models/project_deploy_token.rb b/app/models/project_deploy_token.rb
new file mode 100644
index 00000000000..ab4482f0c0b
--- /dev/null
+++ b/app/models/project_deploy_token.rb
@@ -0,0 +1,8 @@
+class ProjectDeployToken < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :deploy_token, inverse_of: :project_deploy_tokens
+
+ validates :deploy_token, presence: true
+ validates :project, presence: true
+ validates :deploy_token_id, uniqueness: { scope: [:project_id] }
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7b6857a0d34..2b95be3f888 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -700,10 +700,6 @@ class User < ActiveRecord::Base
projects_limit - personal_projects_count
end
- def personal_projects_count
- @personal_projects_count ||= personal_projects.count
- end
-
def recent_push(project = nil)
service = Users::LastPushEventService.new(self)
@@ -1046,9 +1042,10 @@ class User < ActiveRecord::Base
end
end
- def update_cache_counts
- assigned_open_merge_requests_count(force: true)
- assigned_open_issues_count(force: true)
+ def personal_projects_count(force: false)
+ Rails.cache.fetch(['users', id, 'personal_projects_count'], force: force, expires_in: 24.hours, raw: true) do
+ personal_projects.count
+ end.to_i
end
def update_todos_count_cache
@@ -1061,6 +1058,7 @@ class User < ActiveRecord::Base
invalidate_merge_request_cache_counts
invalidate_todos_done_count
invalidate_todos_pending_count
+ invalidate_personal_projects_count
end
def invalidate_issue_cache_counts
@@ -1079,6 +1077,10 @@ class User < ActiveRecord::Base
Rails.cache.delete(['users', id, 'todos_pending_count'])
end
+ def invalidate_personal_projects_count
+ Rails.cache.delete(['users', id, 'personal_projects_count'])
+ end
+
# This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth
# flow means we don't call that automatically (and can't conveniently do so).
#
diff --git a/app/policies/deploy_token_policy.rb b/app/policies/deploy_token_policy.rb
new file mode 100644
index 00000000000..7aa9106e8b1
--- /dev/null
+++ b/app/policies/deploy_token_policy.rb
@@ -0,0 +1,11 @@
+class DeployTokenPolicy < BasePolicy
+ with_options scope: :subject, score: 0
+ condition(:master) { @subject.project.team.master?(@user) }
+
+ rule { anonymous }.prevent_all
+
+ rule { master }.policy do
+ enable :create_deploy_token
+ enable :update_deploy_token
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index b1ed034cd00..21bb0934dee 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -143,7 +143,7 @@ class ProjectPolicy < BasePolicy
end
# These abilities are not allowed to admins that are not members of the project,
- # that's why they are defined separatly.
+ # that's why they are defined separately.
rule { guest & can?(:download_code) }.enable :build_download_code
rule { guest & can?(:read_container_image) }.enable :build_read_container_image
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 2b77f6be72a..8f050072f74 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -109,7 +109,7 @@ module Auth
case requested_action
when 'pull'
- build_can_pull?(requested_project) || user_can_pull?(requested_project)
+ build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
when '*'
@@ -123,22 +123,33 @@ module Auth
Gitlab.config.registry
end
+ def can_user?(ability, project)
+ user = current_user.is_a?(User) ? current_user : nil
+ can?(user, ability, project)
+ end
+
def build_can_pull?(requested_project)
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
has_authentication_ability?(:build_read_container_image) &&
- (requested_project == project || can?(current_user, :build_read_container_image, requested_project))
+ (requested_project == project || can_user?(:build_read_container_image, requested_project))
end
def user_can_admin?(requested_project)
has_authentication_ability?(:admin_container_image) &&
- can?(current_user, :admin_container_image, requested_project)
+ can_user?(:admin_container_image, requested_project)
end
def user_can_pull?(requested_project)
has_authentication_ability?(:read_container_image) &&
- can?(current_user, :read_container_image, requested_project)
+ can_user?(:read_container_image, requested_project)
+ end
+
+ def deploy_token_can_pull?(requested_project)
+ has_authentication_ability?(:read_container_image) &&
+ current_user.is_a?(DeployToken) &&
+ current_user.has_access_to?(requested_project)
end
##
@@ -154,7 +165,7 @@ module Auth
def user_can_push?(requested_project)
has_authentication_ability?(:create_container_image) &&
- can?(current_user, :create_container_image, requested_project)
+ can_user?(:create_container_image, requested_project)
end
def error(code, status:, message: '')
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index ecd74b74f8a..ac70a99c2c5 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -35,6 +35,7 @@ module Boards
def filter_params
set_parent
set_state
+ set_scope
params
end
@@ -51,6 +52,10 @@ module Boards
params[:state] = list && list.closed? ? 'closed' : 'opened'
end
+ def set_scope
+ params[:include_subgroups] = board.group_board?
+ end
+
def board_label_ids
@board_label_ids ||= board.lists.movable.pluck(:label_id)
end
diff --git a/app/services/deploy_tokens/create_service.rb b/app/services/deploy_tokens/create_service.rb
new file mode 100644
index 00000000000..52f545947af
--- /dev/null
+++ b/app/services/deploy_tokens/create_service.rb
@@ -0,0 +1,7 @@
+module DeployTokens
+ class CreateService < BaseService
+ def execute
+ @project.deploy_tokens.create(params)
+ end
+ end
+end
diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb
index 7197a426a72..0b1a33518c6 100644
--- a/app/services/issuable/destroy_service.rb
+++ b/app/services/issuable/destroy_service.rb
@@ -4,6 +4,7 @@ module Issuable
TodoService.new.destroy_target(issuable) do |issuable|
if issuable.destroy
issuable.update_project_counter_caches
+ issuable.assignees.each(&:invalidate_cache_counts)
end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 91ec702fbc6..1f67e3ecf9d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -51,9 +51,10 @@ class IssuableBaseService < BaseService
return unless milestone_id
params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
+ group_ids = project.group&.self_and_ancestors&.pluck(:id)
milestone =
- Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
+ Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
params[:milestone_id] = '' unless milestone
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 4161932ad2a..1374f10c586 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -90,7 +90,7 @@ module Issues
issue =
if board_group_id
- IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
+ IssuesFinder.new(current_user, group_id: board_group_id, include_subgroups: true).find_by(id: id)
else
project.issues.find(id)
end
diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb
new file mode 100644
index 00000000000..e8fd3ef57e5
--- /dev/null
+++ b/app/services/projects/base_move_relations_service.rb
@@ -0,0 +1,22 @@
+module Projects
+ class BaseMoveRelationsService < BaseService
+ attr_reader :source_project
+ def execute(source_project, remove_remaining_elements: true)
+ return if source_project.blank?
+
+ @source_project = source_project
+
+ true
+ end
+
+ private
+
+ def prepare_relation(relation, id_param = :id)
+ if Gitlab::Database.postgresql?
+ relation
+ else
+ relation.model.where("#{id_param}": relation.pluck(id_param))
+ end
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 633e2c8236c..d361d070993 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -96,6 +96,8 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create)
setup_authorizations
+
+ current_user.invalidate_personal_projects_count
end
# Refresh the current user's authorizations inline (so they can access the
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 4b8f955ae69..aa14206db3b 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -34,6 +34,8 @@ module Projects
system_hook_service.execute_hooks_for(project, :destroy)
log_info("Project \"#{project.full_path}\" was removed")
+ current_user.invalidate_personal_projects_count
+
true
rescue => error
attempt_rollback(project, error.message)
@@ -44,6 +46,20 @@ module Projects
raise
end
+ def attempt_repositories_rollback
+ return unless @project
+
+ flush_caches(@project)
+
+ unless mv_repository(removal_path(repo_path), repo_path)
+ raise_error('Failed to restore project repository. Please contact the administrator.')
+ end
+
+ unless mv_repository(removal_path(wiki_path), wiki_path)
+ raise_error('Failed to restore wiki repository. Please contact the administrator.')
+ end
+ end
+
private
def repo_path
@@ -68,12 +84,9 @@ module Projects
# Skip repository removal. We use this flag when remove user or group
return true if params[:skip_repo] == true
- # There is a possibility project does not have repository or wiki
- return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git')
-
new_path = removal_path(path)
- if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path)
+ if mv_repository(path, new_path)
log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
project.run_after_commit do
@@ -85,6 +98,13 @@ module Projects
end
end
+ def mv_repository(from_path, to_path)
+ # There is a possibility project does not have repository or wiki
+ return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git')
+
+ gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path)
+ end
+
def attempt_rollback(project, message)
return unless project
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index fb4afb85588..a16268f4fd2 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -15,9 +15,18 @@ module Projects
file = params.delete(:file)
FileUtils.copy_entry(file.path, import_upload_path)
+ @overwrite = params.delete(:overwrite)
+ data = {}
+ data[:override_params] = @override_params if @override_params
+
+ if overwrite_project?
+ data[:original_path] = params[:path]
+ params[:path] += "-#{tmp_filename}"
+ end
+
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
- params[:import_data] = { data: { override_params: @override_params } } if @override_params
+ params[:import_data] = { data: data } if data.present?
::Projects::CreateService.new(current_user, params).execute
end
@@ -31,5 +40,17 @@ module Projects
def tmp_filename
SecureRandom.hex
end
+
+ def overwrite_project?
+ @overwrite && project_with_same_full_path?
+ end
+
+ def project_with_same_full_path?
+ Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
+ end
+
+ def current_namespace
+ @current_namespace ||= Namespace.find_by(id: params[:namespace_id])
+ end
end
end
diff --git a/app/services/projects/move_access_service.rb b/app/services/projects/move_access_service.rb
new file mode 100644
index 00000000000..3af3a22d486
--- /dev/null
+++ b/app/services/projects/move_access_service.rb
@@ -0,0 +1,25 @@
+module Projects
+ class MoveAccessService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ @project.with_transaction_returning_status do
+ if @project.namespace != source_project.namespace
+ @project.run_after_commit do
+ source_project.namespace.refresh_project_authorizations
+ self.namespace.refresh_project_authorizations
+ end
+ end
+
+ ::Projects::MoveProjectMembersService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+ ::Projects::MoveProjectGroupLinksService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+ ::Projects::MoveProjectAuthorizationsService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+
+ success
+ end
+ end
+ end
+end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
new file mode 100644
index 00000000000..dde420655b0
--- /dev/null
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -0,0 +1,31 @@
+module Projects
+ class MoveDeployKeysProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_deploy_keys_projects
+ remove_remaining_deploy_keys_projects if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_deploy_keys_projects
+ prepare_relation(non_existent_deploy_keys_projects)
+ .update_all(project_id: @project.id)
+ end
+
+ def non_existent_deploy_keys_projects
+ source_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .where.not(keys: { fingerprint: @project.deploy_keys.select(:fingerprint) })
+ end
+
+ def remove_remaining_deploy_keys_projects
+ source_project.deploy_keys_projects.destroy_all
+ end
+ end
+end
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
new file mode 100644
index 00000000000..d2901ea1457
--- /dev/null
+++ b/app/services/projects/move_forks_service.rb
@@ -0,0 +1,42 @@
+module Projects
+ class MoveForksService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super && source_project.fork_network
+
+ Project.transaction(requires_new: true) do
+ move_forked_project_links
+ move_fork_network_members
+ update_root_project
+ refresh_forks_count
+
+ success
+ end
+ end
+
+ private
+
+ def move_forked_project_links
+ # Update ancestor
+ ForkedProjectLink.where(forked_to_project: source_project)
+ .update_all(forked_to_project_id: @project.id)
+
+ # Update the descendants
+ ForkedProjectLink.where(forked_from_project: source_project)
+ .update_all(forked_from_project_id: @project.id)
+ end
+
+ def move_fork_network_members
+ ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
+ ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
+ end
+
+ def update_root_project
+ # Update root network project
+ ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
+ end
+
+ def refresh_forks_count
+ Projects::ForksCountService.new(@project).refresh_cache
+ end
+ end
+end
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
new file mode 100644
index 00000000000..298da5f1a82
--- /dev/null
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -0,0 +1,29 @@
+module Projects
+ class MoveLfsObjectsProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_lfs_objects_projects
+ remove_remaining_lfs_objects_project if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_lfs_objects_projects
+ prepare_relation(non_existent_lfs_objects_projects)
+ .update_all(project_id: @project.lfs_storage_project.id)
+ end
+
+ def remove_remaining_lfs_objects_project
+ source_project.lfs_objects_projects.destroy_all
+ end
+
+ def non_existent_lfs_objects_projects
+ source_project.lfs_objects_projects.where.not(lfs_object: @project.lfs_objects)
+ end
+ end
+end
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
new file mode 100644
index 00000000000..f7be461a5da
--- /dev/null
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -0,0 +1,38 @@
+module Projects
+ class MoveNotificationSettingsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_notification_settings
+ remove_remaining_notification_settings if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_notification_settings
+ prepare_relation(non_existent_notifications)
+ .update_all(source_id: @project.id)
+ end
+
+ # Remove remaining notification settings from source_project
+ def remove_remaining_notification_settings
+ source_project.notification_settings.destroy_all
+ end
+
+ # Get users of current notification_settings
+ def users_in_target_project
+ @project.notification_settings.select(:user_id)
+ end
+
+ # Look for notification_settings in source_project that are not in the target project
+ def non_existent_notifications
+ source_project.notification_settings
+ .select(:id)
+ .where.not(user_id: users_in_target_project)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb
new file mode 100644
index 00000000000..5ef12fc49e5
--- /dev/null
+++ b/app/services/projects/move_project_authorizations_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectAuthorizationsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_project_authorizations
+
+ remove_remaining_authorizations if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_project_authorizations
+ prepare_relation(non_existent_authorization, :user_id)
+ .update_all(project_id: @project.id)
+ end
+
+ def remove_remaining_authorizations
+ # I think because the Project Authorization table does not have a primary key
+ # it brings a lot a problems/bugs. First, Rails raises PG::SyntaxException if we use
+ # destroy_all instead of delete_all.
+ source_project.project_authorizations.delete_all(:delete_all)
+ end
+
+ # Look for authorizations in source_project that are not in the target project
+ def non_existent_authorization
+ source_project.project_authorizations
+ .select(:user_id)
+ .where.not(user: @project.authorized_users)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
new file mode 100644
index 00000000000..dbeffd7dae9
--- /dev/null
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectGroupLinksService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_group_links
+ remove_remaining_project_group_links if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_group_links
+ prepare_relation(non_existent_group_links)
+ .update_all(project_id: @project.id)
+ end
+
+ # Remove remaining project group links from source_project
+ def remove_remaining_project_group_links
+ source_project.reload.project_group_links.destroy_all
+ end
+
+ def group_links_in_target_project
+ @project.project_group_links.select(:group_id)
+ end
+
+ # Look for groups in source_project that are not in the target project
+ def non_existent_group_links
+ source_project.project_group_links
+ .where.not(group_id: group_links_in_target_project)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
new file mode 100644
index 00000000000..22a5f0a3fe6
--- /dev/null
+++ b/app/services/projects/move_project_members_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectMembersService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_project_members
+ remove_remaining_members if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_project_members
+ prepare_relation(non_existent_members).update_all(source_id: @project.id)
+ end
+
+ def remove_remaining_members
+ # Remove remaining members and authorizations from source_project
+ source_project.project_members.destroy_all
+ end
+
+ def project_members_in_target_project
+ @project.project_members.select(:user_id)
+ end
+
+ # Look for members in source_project that are not in the target project
+ def non_existent_members
+ source_project.members
+ .select(:id)
+ .where.not(user_id: @project.project_members.select(:user_id))
+ end
+ end
+end
diff --git a/app/services/projects/move_users_star_projects_service.rb b/app/services/projects/move_users_star_projects_service.rb
new file mode 100644
index 00000000000..079fd5b9685
--- /dev/null
+++ b/app/services/projects/move_users_star_projects_service.rb
@@ -0,0 +1,20 @@
+module Projects
+ class MoveUsersStarProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ user_stars = source_project.users_star_projects
+
+ return unless user_stars.any?
+
+ Project.transaction(requires_new: true) do
+ user_stars.update_all(project_id: @project.id)
+
+ Project.reset_counters @project.id, :users_star_projects
+ Project.reset_counters source_project.id, :users_star_projects
+
+ success
+ end
+ end
+ end
+end
diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb
new file mode 100644
index 00000000000..ce94f147aa9
--- /dev/null
+++ b/app/services/projects/overwrite_project_service.rb
@@ -0,0 +1,69 @@
+module Projects
+ class OverwriteProjectService < BaseService
+ def execute(source_project)
+ return unless source_project && source_project.namespace == @project.namespace
+
+ Project.transaction do
+ move_before_destroy_relationships(source_project)
+ destroy_old_project(source_project)
+ rename_project(source_project.name, source_project.path)
+
+ @project
+ end
+ # Projects::DestroyService can raise Exceptions, but we don't want
+ # to pass that kind of exception to the caller. Instead, we change it
+ # for a StandardError exception
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ attempt_restore_repositories(source_project)
+
+ if e.class == Exception
+ raise StandardError, e.message
+ else
+ raise
+ end
+ end
+
+ private
+
+ def move_before_destroy_relationships(source_project)
+ options = { remove_remaining_elements: false }
+
+ ::Projects::MoveUsersStarProjectsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveAccessService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveDeployKeysProjectsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveNotificationSettingsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveForksService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveLfsObjectsProjectsService.new(@project, @current_user).execute(source_project, options)
+ add_source_project_to_fork_network(source_project)
+ end
+
+ def destroy_old_project(source_project)
+ # Delete previous project (synchronously) and unlink relations
+ ::Projects::DestroyService.new(source_project, @current_user).execute
+ end
+
+ def rename_project(name, path)
+ # Update de project's name and path to the original name/path
+ ::Projects::UpdateService.new(@project,
+ @current_user,
+ { name: name, path: path })
+ .execute
+ end
+
+ def attempt_restore_repositories(project)
+ ::Projects::DestroyService.new(project, @current_user).attempt_repositories_rollback
+ end
+
+ def add_source_project_to_fork_network(source_project)
+ return unless @project.fork_network
+
+ # Because he have moved all references in the fork network from the source_project
+ # we won't be able to query the database (only through its cached data),
+ # for its former relationships. That's why we're adding it to the network
+ # as a fork of the target project
+ ForkNetworkMember.create!(fork_network: @project.fork_network,
+ project: source_project,
+ forked_from_project: @project)
+ end
+ end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 26765e5c3f3..5a23f0f0a62 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -24,6 +24,8 @@ module Projects
transfer(project)
+ current_user.invalidate_personal_projects_count
+
true
rescue Projects::TransferService::TransferError => ex
project.reload
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 7e228d1833d..de77f6bf585 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -74,25 +74,13 @@ module Projects
end
def extract_archive!(temp_path)
- if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz')
- extract_tar_archive!(temp_path)
- elsif artifacts.ends_with?('.zip')
+ if artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path)
else
raise InvaildStateError, 'unsupported artifacts format'
end
end
- def extract_tar_archive!(temp_path)
- build.artifacts_file.use_file do |artifacts_path|
- results = Open3.pipeline(%W(gunzip -c #{artifacts_path}),
- %W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
- %W(tar -x -C #{temp_path} #{SITE_PATH}),
- err: '/dev/null')
- raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?)
- end
- end
-
def extract_zip_archive!(temp_path)
raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 3e85535dae0..bb472b4c900 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,15 +1,19 @@
- @hide_top_links = true
-- page_title "Issues"
-- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
+- page_title _("Issues")
+- @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
.top-area
- = render 'shared/issuable/nav', type: :issues
+ = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues
-= render 'shared/issues'
+
+- if current_user && @no_filters_set
+ = render 'shared/dashboard/no_filter_selected'
+- else
+ = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 53cd1130299..61aae31be60 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,11 +1,15 @@
- @hide_top_links = true
-- page_title "Merge Requests"
-- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
+- page_title _("Merge Requests")
+- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
.top-area
- = render 'shared/issuable/nav', type: :merge_requests
+ = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
.nav-controls
= 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
-= render 'shared/merge_requests'
+
+- if current_user && @no_filters_set
+ = render 'shared/dashboard/no_filter_selected'
+- else
+ = render 'shared/merge_requests'
diff --git a/app/views/groups/settings/badges/index.html.haml b/app/views/groups/settings/badges/index.html.haml
new file mode 100644
index 00000000000..c7afb25d0f8
--- /dev/null
+++ b/app/views/groups/settings/badges/index.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _('Project Badges')
+- page_title _('Project Badges')
+
+= render 'shared/badges/badge_settings'
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 5ea19c9882d..517d9aa3d99 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -112,7 +112,7 @@
%span.nav-item-name
Settings
%ul.sidebar-sub-level-items
- = nav_link(path: %w[groups#projects groups#edit ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
= link_to edit_group_path(@group) do
%strong.fly-out-top-item-name
#{ _('Settings') }
@@ -122,6 +122,12 @@
%span
General
+ = nav_link(controller: :badges) do
+ = link_to group_settings_badges_path(@group), title: _('Project Badges') do
+ %span
+ = _('Project Badges')
+
+
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects' do
%span
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 5c90d13420f..93f674b9d3c 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -258,7 +258,7 @@
#{ _('Snippets') }
- if project_nav_tab? :settings
- = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
+ = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show]) do
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
.nav-icon-container
= sprite_icon('settings')
@@ -268,7 +268,7 @@
%ul.sidebar-sub-level-items
- can_edit = can?(current_user, :admin_project, @project)
- if can_edit
- = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show], html_options: { class: "fly-out-top-item" } ) do
= link_to edit_project_path(@project) do
%strong.fly-out-top-item-name
#{ _('Settings') }
@@ -282,6 +282,11 @@
%span
Members
- if can_edit
+ = nav_link(controller: :badges) do
+ = link_to project_settings_badges_path(@project), title: _('Badges') do
+ %span
+ = _('Badges')
+ - if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 02263095599..9c95b6281ba 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -57,20 +57,8 @@
= succeed '.' do
= link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8
- = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
- .form-group
- = f.label :username, "Path", class: "label-light"
- .input-group
- .input-group-addon
- = root_url
- = f.text_field :username, required: true, class: 'form-control'
- .help-block
- Current path:
- #{root_url}#{current_user.username}
- .prepend-top-default
- = f.button class: "btn btn-warning", type: "submit" do
- = icon "spinner spin", class: "hidden loading-username"
- Update username
+ - data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
+ #update-username{ data: data }
%hr
.row.prepend-top-default
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index b96251cd982..9b87a7aaca8 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -2,7 +2,6 @@
- page_title "Personal Access Tokens"
- @content_class = "limit-container-width" unless fluid_layout
-
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index a2ecfddb163..043057e79ee 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -23,11 +23,14 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) }
- .project-badges
+ .project-badges.prepend-top-default.append-bottom-default
- @project.badges.each do |badge|
- - badge_link_url = badge.rendered_link_url(@project)
- %a{ href: badge_link_url, target: '_blank', rel: 'noopener noreferrer' }
- %img{ src: badge.rendered_image_url(@project), alt: badge_link_url }
+ %a.append-right-8{ href: badge.rendered_link_url(@project),
+ target: '_blank',
+ rel: 'noopener noreferrer' }>
+ %img.project-badge{ src: badge.rendered_image_url(@project),
+ 'aria-hidden': true,
+ alt: '' }>
.project-repo-buttons
.count-buttons
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index fa9a9bfc8f7..f49f6e630d2 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,6 +1,7 @@
- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
- if !project.empty_repo? && can?(current_user, :download_code, project)
+ - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}"
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
= sprite_icon('download')
@@ -10,16 +11,16 @@
%li.dropdown-header
#{ _('Source code') }
%li
- = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
%span= _('Download zip')
%li
- = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
%span= _('Download tar.gz')
%li
- = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
%span= _('Download tar.bz2')
%li
- = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
%span= _('Download tar')
- if pipeline && pipeline.latest_builds_with_artifacts.any?
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 078bd0eee63..163432c9263 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -22,7 +22,10 @@
.commit-detail.flex-list
.commit-content
- = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
+ - if view_details && merge_request
+ = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
+ - else
+ = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
@@ -52,9 +55,9 @@
= render_commit_status(commit, ref: ref)
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
- = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
- = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
- = link_to_browse_code(project, commit)
- - if view_details && merge_request
- = link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
+ .commit-sha-group
+ .label.label-monospace
+ = commit.short_id
+ = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
new file mode 100644
index 00000000000..f8db30df7b4
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -0,0 +1,29 @@
+%p.profile-settings-content
+ = s_("DeployTokens|Pick a name for the application, and we'll give you a unique deploy token.")
+
+= form_for token, url: create_deploy_token_namespace_project_settings_repository_path(project.namespace, project), method: :post do |f|
+ = form_errors(token)
+
+ .form-group
+ = f.label :name, class: 'label-light'
+ = f.text_field :name, class: 'form-control', required: true
+
+ .form-group
+ = f.label :expires_at, class: 'label-light'
+ = f.text_field :expires_at, class: 'datepicker form-control', value: f.object.expires_at
+
+ .form-group
+ = f.label :scopes, class: 'label-light'
+ %fieldset
+ = f.check_box :read_repository
+ = label_tag ("deploy_token_read_repository"), 'read_repository'
+ %span= s_('DeployTokens|Allows read-only access to the repository')
+
+ - if container_registry_enabled?(project)
+ %fieldset
+ = f.check_box :read_registry
+ = label_tag ("deploy_token_read_registry"), 'read_registry'
+ %span= s_('DeployTokens|Allows read-only access to the registry images')
+
+ .prepend-top-default
+ = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success'
diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml
new file mode 100644
index 00000000000..50e5950ced4
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_index.html.haml
@@ -0,0 +1,18 @@
+- expanded = expand_deploy_tokens_section?(@new_deploy_token)
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= s_('DeployTokens|Deploy Tokens')
+ %button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = s_('DeployTokens|Deploy tokens allow read-only access to your repository and registry images.')
+ .settings-content
+ - if @new_deploy_token.persisted?
+ = render 'projects/deploy_tokens/new_deploy_token', deploy_token: @new_deploy_token
+ - else
+ %h5.prepend-top-0
+ = s_('DeployTokens|Add a deploy token')
+ = render 'projects/deploy_tokens/form', project: @project, token: @new_deploy_token, presenter: @deploy_tokens
+ %hr
+ = render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens
diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
new file mode 100644
index 00000000000..1e715681e59
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
@@ -0,0 +1,14 @@
+.created-deploy-token-container
+ %h5.prepend-top-0
+ = s_('DeployTokens|Your New Deploy Token')
+
+ .form-group
+ = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left')
+ %span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.")
+
+ .form-group
+ = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left')
+ %span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
+%hr
diff --git a/app/views/projects/deploy_tokens/_revoke_modal.html.haml b/app/views/projects/deploy_tokens/_revoke_modal.html.haml
new file mode 100644
index 00000000000..085964fe22e
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_revoke_modal.html.haml
@@ -0,0 +1,17 @@
+.modal{ id: "revoke-modal-#{token.id}" }
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %h4.modal-title.pull-left
+ = s_('DeployTokens|Revoke')
+ %b #{token.name}?
+ %button.close{ 'aria-label' => _('Close'), 'data-dismiss' => 'modal', type: 'button' }
+ %span{ 'aria-hidden' => 'true' } &times;
+ .modal-body
+ %p
+ = s_('DeployTokens|You are about to revoke')
+ %b #{token.name}.
+ = s_('DeployTokens|This action cannot be undone.')
+ .modal-footer
+ %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' }= _('Cancel')
+ = link_to s_('DeployTokens|Revoke %{name}') % { name: token.name }, revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger'
diff --git a/app/views/projects/deploy_tokens/_table.html.haml b/app/views/projects/deploy_tokens/_table.html.haml
new file mode 100644
index 00000000000..5013a9b250d
--- /dev/null
+++ b/app/views/projects/deploy_tokens/_table.html.haml
@@ -0,0 +1,31 @@
+%h5= s_("DeployTokens|Active Deploy Tokens (%{active_tokens})") % { active_tokens: active_tokens.length }
+
+- if active_tokens.present?
+ .table-responsive.deploy-tokens
+ %table.table
+ %thead
+ %tr
+ %th= s_('DeployTokens|Name')
+ %th= s_('DeployTokens|Username')
+ %th= s_('DeployTokens|Created')
+ %th= s_('DeployTokens|Expires')
+ %th= s_('DeployTokens|Scopes')
+ %th
+ %tbody
+ - active_tokens.each do |token|
+ %tr
+ %td= token.name
+ %td= token.username
+ %td= token.created_at.to_date.to_s(:medium)
+ %td
+ - if token.expires?
+ %span{ class: ('text-warning' if token.expires_soon?) }
+ In #{distance_of_time_in_words_to_now(token.expires_at)}
+ - else
+ %span.token-never-expires-label Never
+ %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
+ %td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger pull-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
+ = render 'projects/deploy_tokens/revoke_modal', token: token, project: project
+- else
+ .settings-message.text-center
+ = s_('DeployTokens|This project has no active Deploy Tokens.')
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index cdfc3e232c5..816f2fa816d 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -8,4 +8,5 @@
%section.js-vue-notes-event
#js-vue-notes{ data: { notes_data: notes_data(@issue),
noteable_data: serialize_issuable(@issue),
+ noteable_type: 'issue',
current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
index c66313bdbf3..311934d9c33 100644
--- a/app/views/projects/jobs/_empty_state.html.haml
+++ b/app/views/projects/jobs/_empty_state.html.haml
@@ -1,7 +1,7 @@
- illustration = local_assigns.fetch(:illustration)
- illustration_size = local_assigns.fetch(:illustration_size)
- title = local_assigns.fetch(:title)
-- content = local_assigns.fetch(:content)
+- content = local_assigns.fetch(:content, nil)
- action = local_assigns.fetch(:action, nil)
.row.empty-state
@@ -11,7 +11,8 @@
.col-xs-12
.text-content
%h4.text-center= title
- %p= content
+ - if content
+ %p= content
- if action
.text-center
= action
diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml
new file mode 100644
index 00000000000..e5198d047df
--- /dev/null
+++ b/app/views/projects/jobs/_empty_states.html.haml
@@ -0,0 +1,9 @@
+- detailed_status = @build.detailed_status(current_user)
+- illustration = detailed_status.illustration
+
+= render 'empty_state',
+ illustration: illustration[:image],
+ illustration_size: illustration[:size],
+ title: illustration[:title],
+ content: illustration[:content],
+ action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index dece4dfe167..8beb4ffef45 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -54,7 +54,8 @@
Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- - if @build.started?
+
+ - if @build.has_trace?
.build-trace-container.prepend-top-default
.top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden-xs.pull-left.hidden<
@@ -88,25 +89,9 @@
%pre.build-trace#build-trace
%code.bash.js-build-output
.build-loader-animation.js-build-refresh
- - elsif @build.playable?
- = render 'empty_state',
- illustration: 'illustrations/manual_action.svg',
- illustration_size: 'svg-394',
- title: _('This job requires a manual action'),
- content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'),
- action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') )
- - elsif @build.created?
- = render 'empty_state',
- illustration: 'illustrations/job_not_triggered.svg',
- illustration_size: 'svg-306',
- title: _('This job has not been triggered yet'),
- content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
- else
- = render 'empty_state',
- illustration: 'illustrations/pending_job_empty.svg',
- illustration_size: 'svg-430',
- title: _('This job has not started yet'),
- content: _('This job is in pending state and is waiting to be picked by a runner')
+ = render "empty_states"
+
= render "sidebar", builds: @builds
.js-build-options{ data: javascript_build_options }
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 9866cc716ee..15a0e4d7ef5 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -80,6 +80,7 @@
- if has_vue_discussions_cookie?
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request),
noteable_data: serialize_issuable(@merge_request),
+ noteable_type: 'merge_request',
current_user_data: UserSerializer.new.represent(current_user).to_json} }
#commits.commits.tab-pane
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 12d56e244ce..2c80f7c3fa3 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -29,6 +29,10 @@
docker login #{Gitlab.config.registry.host_port}
%br
%p
+ - deploy_token = link_to(_('deploy token'), help_page_path('user/projects/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank')
+ = s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token }
+ %br
+ %p
= s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe }
%pre
:plain
diff --git a/app/views/projects/settings/badges/index.html.haml b/app/views/projects/settings/badges/index.html.haml
new file mode 100644
index 00000000000..b68ed70de89
--- /dev/null
+++ b/app/views/projects/settings/badges/index.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _('Badges')
+- page_title _('Badges')
+
+= render 'shared/badges/badge_settings'
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 6bef4d19434..f57590a908f 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -9,3 +9,4 @@
= render "projects/protected_branches/index"
= render "projects/protected_tags/index"
= render @deploy_keys
+= render "projects/deploy_tokens/index"
diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml
index 93a4301f366..a0ba1afc284 100644
--- a/app/views/shared/_recaptcha_form.html.haml
+++ b/app/views/shared/_recaptcha_form.html.haml
@@ -10,7 +10,7 @@
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
- = recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
+ = recaptcha_tags script: script, callback: 'recaptchaDialogCallback' unless Rails.env.test?
-# Yields a block with given extra params.
= yield
diff --git a/app/views/shared/badges/_badge_settings.html.haml b/app/views/shared/badges/_badge_settings.html.haml
new file mode 100644
index 00000000000..b7c250d3b1c
--- /dev/null
+++ b/app/views/shared/badges/_badge_settings.html.haml
@@ -0,0 +1,4 @@
+#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
+ docs_url: help_page_path('user/project/badges')} }
+ .text-center.prepend-top-default
+ = icon('spinner spin 2x')
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 8e5e32e9f16..b385cc3f962 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -22,6 +22,6 @@
= render "shared/boards/components/sidebar/labels"
= render "shared/boards/components/sidebar/notifications"
%remove-btn{ ":issue" => "issue",
- ":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'",
+ ":issue-update" => "issue.sidebarInfoEndpoint",
":list" => "list",
"v-if" => "canRemove" }
diff --git a/app/views/shared/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 3d2e8471a60..1374da9d82c 100644
--- a/app/views/shared/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -21,8 +21,7 @@
.dropdown
- dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data,
- ":data-issuable-id" => "issue.iid",
- ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+ ":data-issuable-id" => "issue.iid" }
= dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index db794d6f855..d13b998e6f4 100644
--- a/app/views/shared/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -22,8 +22,7 @@
":value" => "issue.dueDate" }
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
- data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
- ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+ data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" } }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index dfc0f9be321..87e6b52f46e 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -26,8 +26,7 @@
project_id: @project&.try(:id),
labels: labels_filter_path(false),
namespace_path: @namespace_path,
- project_path: @project.try(:path) },
- ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+ project_path: @project.try(:path) } }
%span.dropdown-toggle-text
Label
= icon('chevron-down')
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index d09c7c218e0..f51c4a97f2b 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -18,8 +18,7 @@
.dropdown
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
- ":data-issuable-id" => "issue.iid",
- ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
+ ":data-issuable-id" => "issue.iid" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml
new file mode 100644
index 00000000000..b2e6967f6aa
--- /dev/null
+++ b/app/views/shared/dashboard/_no_filter_selected.html.haml
@@ -0,0 +1,8 @@
+.row.empty-state.text-center
+ .col-xs-12
+ .svg-130.prepend-top-default
+ = image_tag 'illustrations/issue-dashboard_results-without-filter.svg'
+ .col-xs-12
+ .text-content
+ %h4
+ = _("Please select at least one filter to see results")
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 7704c88905b..1bd5b4164b1 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -24,12 +24,9 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
- - if issuable_filter_present?
- .filter-item.inline.reset-filters
- %a{ href: page_filter_path(without: issuable_filter_params) } Reset filters
-
- .pull-right
- = render 'shared/sort_dropdown'
+ - unless @no_filters_set
+ .pull-right
+ = render 'shared/sort_dropdown'
- has_labels = @labels && @labels.any?
.row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 4d8109eb90c..a5f40ea934b 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -1,22 +1,23 @@
- type = local_assigns.fetch(:type, :issues)
- page_context_word = type.to_s.humanize(capitalize: false)
+- display_count = local_assigns.fetch(:display_count, :true)
%ul.nav-links.issues-state-filters.mobile-separator
%li{ class: active_when(params[:state] == 'opened') }>
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
- #{issuables_state_counter_text(type, :opened)}
+ #{issuables_state_counter_text(type, :opened, display_count)}
- if type == :merge_requests
%li{ class: active_when(params[:state] == 'merged') }>
= link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do
- #{issuables_state_counter_text(type, :merged)}
+ #{issuables_state_counter_text(type, :merged, display_count)}
%li{ class: active_when(params[:state] == 'closed') }>
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do
- #{issuables_state_counter_text(type, :closed)}
+ #{issuables_state_counter_text(type, :closed, display_count)}
- else
%li{ class: active_when(params[:state] == 'closed') }>
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do
- #{issuables_state_counter_text(type, :closed)}
+ #{issuables_state_counter_text(type, :closed, display_count)}
- = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all)
+ = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
diff --git a/changelogs/unreleased/17939-osw-patch-support-gfm.yml b/changelogs/unreleased/17939-osw-patch-support-gfm.yml
new file mode 100644
index 00000000000..576581e25d6
--- /dev/null
+++ b/changelogs/unreleased/17939-osw-patch-support-gfm.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for patch link extension for commit links on GitLab Flavored Markdown
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml
new file mode 100644
index 00000000000..5546d26d0fb
--- /dev/null
+++ b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml
@@ -0,0 +1,5 @@
+---
+title: Create Deploy Tokens to allow permanent access to repository and registry
+merge_request: 17894
+author:
+type: added
diff --git a/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml b/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml
new file mode 100644
index 00000000000..36e79ea1ed4
--- /dev/null
+++ b/changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml
@@ -0,0 +1,5 @@
+---
+title: Added confirmation modal for changing username
+merge_request: 17405
+author:
+type: added
diff --git a/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml b/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml
new file mode 100644
index 00000000000..77d1ebf69df
--- /dev/null
+++ b/changelogs/unreleased/42448-change-commit-row-actions-and-sha-design-for-project-commit-list.yml
@@ -0,0 +1,6 @@
+---
+title: Improved visual styles and consistency for commit hash and possible actions
+ across commit lists
+merge_request: 17406
+author:
+type: changed
diff --git a/changelogs/unreleased/42568-pipeline-empty-state.yml b/changelogs/unreleased/42568-pipeline-empty-state.yml
new file mode 100644
index 00000000000..d36edcf1b37
--- /dev/null
+++ b/changelogs/unreleased/42568-pipeline-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Improve empty state for canceled job
+merge_request: 17646
+author:
+type: fixed
diff --git a/changelogs/unreleased/43246-checkfilter.yml b/changelogs/unreleased/43246-checkfilter.yml
new file mode 100644
index 00000000000..e6c0e716213
--- /dev/null
+++ b/changelogs/unreleased/43246-checkfilter.yml
@@ -0,0 +1,6 @@
+---
+title: Require at least one filter when listing issues or merge requests on dashboard
+ page
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml b/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml
new file mode 100644
index 00000000000..55069b1f2d2
--- /dev/null
+++ b/changelogs/unreleased/ab-37462-cache-personal-projects-count.yml
@@ -0,0 +1,5 @@
+---
+title: Cache personal projects count.
+merge_request: 18197
+author:
+type: performance
diff --git a/changelogs/unreleased/add-cpu-mem-totals.yml b/changelogs/unreleased/add-cpu-mem-totals.yml
new file mode 100644
index 00000000000..bc8babab731
--- /dev/null
+++ b/changelogs/unreleased/add-cpu-mem-totals.yml
@@ -0,0 +1,5 @@
+---
+title: Add Total CPU/Memory consumption metrics for Kubernetes
+merge_request: 17731
+author:
+type: added
diff --git a/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml b/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml
new file mode 100644
index 00000000000..de09f87a7c9
--- /dev/null
+++ b/changelogs/unreleased/da-gitaly-calculate-repository-checksum.yml
@@ -0,0 +1,5 @@
+---
+title: Repository checksum calculation is handled by Gitaly when feature is enabled
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml b/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml
new file mode 100644
index 00000000000..e21554f091a
--- /dev/null
+++ b/changelogs/unreleased/fix-500-error-when-mr-ref-is-not-yet-fetched.yml
@@ -0,0 +1,6 @@
+---
+title: Fix 500 error when a merge request from a fork has conflicts and has not yet
+ been updated
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-dashboard-sorting.yml b/changelogs/unreleased/fix-dashboard-sorting.yml
new file mode 100644
index 00000000000..2ba13a93fa9
--- /dev/null
+++ b/changelogs/unreleased/fix-dashboard-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Prioritize weight over title when sorting charts
+merge_request: 18233
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml b/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml
new file mode 100644
index 00000000000..0553cc684ce
--- /dev/null
+++ b/changelogs/unreleased/fj-41900-import-endpoint-with-overwrite-support.yml
@@ -0,0 +1,5 @@
+---
+title: Extend API for importing a project export with overwrite support
+merge_request: 17883
+author:
+type: added
diff --git a/changelogs/unreleased/fl-fix-annoying-actions.yml b/changelogs/unreleased/fl-fix-annoying-actions.yml
new file mode 100644
index 00000000000..fe17f9a8978
--- /dev/null
+++ b/changelogs/unreleased/fl-fix-annoying-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Stop redirecting the page in pipeline main actions
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_42443.yml b/changelogs/unreleased/issue_42443.yml
new file mode 100644
index 00000000000..954fbd98a4b
--- /dev/null
+++ b/changelogs/unreleased/issue_42443.yml
@@ -0,0 +1,5 @@
+---
+title: Include subgroup issues when searching for group issues using the API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/issue_44270.yml b/changelogs/unreleased/issue_44270.yml
new file mode 100644
index 00000000000..6234162be30
--- /dev/null
+++ b/changelogs/unreleased/issue_44270.yml
@@ -0,0 +1,5 @@
+---
+title: Show issues of subgroups in group-level issue board
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
new file mode 100644
index 00000000000..c5cdbcf7b40
--- /dev/null
+++ b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Add average and maximum summary statistics to the prometheus dashboard
+merge_request: 17921
+author:
+type: changed
diff --git a/changelogs/unreleased/jramsay-38830-tarball.yml b/changelogs/unreleased/jramsay-38830-tarball.yml
new file mode 100644
index 00000000000..6d40c305614
--- /dev/null
+++ b/changelogs/unreleased/jramsay-38830-tarball.yml
@@ -0,0 +1,5 @@
+---
+title: Add alternate archive route for simplified packaging
+merge_request: 17225
+author:
+type: added
diff --git a/changelogs/unreleased/remove-pages-tar-support.yml b/changelogs/unreleased/remove-pages-tar-support.yml
new file mode 100644
index 00000000000..73448687912
--- /dev/null
+++ b/changelogs/unreleased/remove-pages-tar-support.yml
@@ -0,0 +1,5 @@
+---
+title: Remove support for legacy tar.gz pages artifacts
+merge_request: 18090
+author:
+type: deprecated
diff --git a/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml b/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml
new file mode 100644
index 00000000000..81b48fc255b
--- /dev/null
+++ b/changelogs/unreleased/sh-add-cleanup-rpc-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Automatically cleanup stale worktrees and lock files upon a push
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ui-mr-counter-cache.yml b/changelogs/unreleased/ui-mr-counter-cache.yml
new file mode 100644
index 00000000000..5e241bfe93f
--- /dev/null
+++ b/changelogs/unreleased/ui-mr-counter-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Deleting a MR you are assigned to should decrements counter
+merge_request: 17951
+author: m b
+type: fixed
diff --git a/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml b/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
new file mode 100644
index 00000000000..14114eca2b2
--- /dev/null
+++ b/changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Projects and groups badges settings UI
+merge_request: 17114
+author:
+type: added
diff --git a/config/initializers/active_record_array_type_casting.rb b/config/initializers/active_record_array_type_casting.rb
index d94d592add6..a149e048ee2 100644
--- a/config/initializers/active_record_array_type_casting.rb
+++ b/config/initializers/active_record_array_type_casting.rb
@@ -1,20 +1,23 @@
-module ActiveRecord
- class PredicateBuilder
- class ArrayHandler
- module TypeCasting
- def call(attribute, value)
- # This is necessary because by default ActiveRecord does not respect
- # custom type definitions (like our `ShaAttribute`) when providing an
- # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
- model = attribute.relation&.engine
- type = model.user_provided_columns[attribute.name] if model
- value = value.map { |value| type.type_cast_for_database(value) } if type
+# Remove this initializer when upgraded to Rails 5.0
+unless Gitlab.rails5?
+ module ActiveRecord
+ class PredicateBuilder
+ class ArrayHandler
+ module TypeCasting
+ def call(attribute, value)
+ # This is necessary because by default ActiveRecord does not respect
+ # custom type definitions (like our `ShaAttribute`) when providing an
+ # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
+ model = attribute.relation&.engine
+ type = model.user_provided_columns[attribute.name] if model
+ value = value.map { |value| type.type_cast_for_database(value) } if type
- super(attribute, value)
+ super(attribute, value)
+ end
end
- end
- prepend TypeCasting
+ prepend TypeCasting
+ end
end
end
end
diff --git a/config/karma.config.js b/config/karma.config.js
index 7ede745b591..c378e621953 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -39,7 +39,7 @@ module.exports = function(config) {
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
- { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)', included: false },
+ { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false },
],
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index c4f60eb2687..10ca612b246 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -26,7 +26,7 @@
weight: 1
queries:
- query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
- label: Average
+ label: Pod average
unit: ms
- title: "HTTP Error Rate"
y_label: "HTTP 500 Errors / Sec"
@@ -139,21 +139,39 @@
- group: System metrics (Kubernetes)
priority: 5
metrics:
- - title: "Memory Usage"
+ - title: "Memory Usage (Total)"
+ y_label: "Total Memory Used"
+ required_metrics:
+ - container_memory_usage_bytes
+ weight: 4
+ queries:
+ - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
+ label: Total
+ unit: GB
+ - title: "Core Usage (Total)"
+ y_label: "Total Cores"
+ required_metrics:
+ - container_cpu_usage_seconds_total
+ weight: 3
+ queries:
+ - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
+ label: Total
+ unit: "cores"
+ - title: "Memory Usage (Pod average)"
y_label: "Memory Used per Pod"
required_metrics:
- container_memory_usage_bytes
- weight: 1
+ weight: 2
queries:
- - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- label: Average
+ - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+ label: Pod average
unit: MB
- - title: "CPU Usage"
+ - title: "Core Usage (Pod average)"
y_label: "Cores per Pod"
required_metrics:
- container_cpu_usage_seconds_total
weight: 1
queries:
- - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
- label: Average
+ - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
+ label: Pod average
unit: "cores" \ No newline at end of file
diff --git a/config/routes/group.rb b/config/routes/group.rb
index d89a714c7d6..170508e893d 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -24,6 +24,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd'
+ resources :badges, only: [:index]
end
resource :variables, only: [:show, :update]
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 0f2ea1c01d1..2a1bcb8cde2 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -88,6 +88,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
+ member do
+ put :revoke
+ end
+ end
+
resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
@@ -249,6 +255,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
scope '-' do
+ get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
+
resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
@@ -424,7 +432,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :reset_cache
end
resource :integrations, only: [:show]
- resource :repository, only: [:show], controller: :repository
+ resource :repository, only: [:show], controller: :repository do
+ post :create_deploy_token, path: 'deploy_token/create'
+ end
+ resources :badges, only: [:index]
end
# Since both wiki and repository routing contains wildcard characters
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index eace3a615b4..9e506a1a43a 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -2,10 +2,11 @@
resource :repository, only: [:create] do
member do
- get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
-
# deprecated since GitLab 9.5
- get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
+ get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative', defaults: { append_sha: true }
+
+ # deprecated since GitLab 10.7
+ get ':id/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+/ }, action: 'archive', as: 'archive_deprecated', defaults: { append_sha: true }
end
end
diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb
new file mode 100644
index 00000000000..d129459ea0a
--- /dev/null
+++ b/db/migrate/20180319190020_create_deploy_tokens.rb
@@ -0,0 +1,19 @@
+class CreateDeployTokens < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :deploy_tokens do |t|
+ t.boolean :revoked, default: false
+ t.boolean :read_repository, null: false, default: false
+ t.boolean :read_registry, null: false, default: false
+
+ t.datetime_with_timezone :expires_at, null: false
+ t.datetime_with_timezone :created_at, null: false
+
+ t.string :name, null: false
+ t.string :token, index: { unique: true }, null: false
+
+ t.index [:token, :expires_at, :id], where: "(revoked IS FALSE)"
+ end
+ end
+end
diff --git a/db/migrate/20180405142733_create_project_deploy_tokens.rb b/db/migrate/20180405142733_create_project_deploy_tokens.rb
new file mode 100644
index 00000000000..9d8f89243a8
--- /dev/null
+++ b/db/migrate/20180405142733_create_project_deploy_tokens.rb
@@ -0,0 +1,16 @@
+class CreateProjectDeployTokens < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :project_deploy_tokens do |t|
+ t.integer :project_id, null: false
+ t.integer :deploy_token_id, null: false
+ t.datetime_with_timezone :created_at, null: false
+
+ t.foreign_key :deploy_tokens, column: :deploy_token_id, on_delete: :cascade
+ t.foreign_key :projects, column: :project_id, on_delete: :cascade
+
+ t.index [:project_id, :deploy_token_id], unique: true
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2cd51b200b3..fd75b176318 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: 20180405101928) do
+ActiveRecord::Schema.define(version: 20180405142733) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -683,6 +683,19 @@ ActiveRecord::Schema.define(version: 20180405101928) do
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
+ create_table "deploy_tokens", force: :cascade do |t|
+ t.boolean "revoked", default: false
+ t.boolean "read_repository", default: false, null: false
+ t.boolean "read_registry", default: false, null: false
+ t.datetime_with_timezone "expires_at", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.string "name", null: false
+ t.string "token", null: false
+ end
+
+ add_index "deploy_tokens", ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)", using: :btree
+ add_index "deploy_tokens", ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
+
create_table "deployments", force: :cascade do |t|
t.integer "iid", null: false
t.integer "project_id", null: false
@@ -1430,6 +1443,14 @@ ActiveRecord::Schema.define(version: 20180405101928) do
add_index "project_custom_attributes", ["key", "value"], name: "index_project_custom_attributes_on_key_and_value", using: :btree
add_index "project_custom_attributes", ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
+ create_table "project_deploy_tokens", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.integer "deploy_token_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ end
+
+ add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
+
create_table "project_features", force: :cascade do |t|
t.integer "project_id"
t.integer "merge_requests_access_level"
@@ -2137,6 +2158,8 @@ ActiveRecord::Schema.define(version: 20180405101928) do
add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
+ add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
+ add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
diff --git a/doc/administration/index.md b/doc/administration/index.md
index c8f27719ce9..0906821d6a3 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -39,6 +39,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
[source installations](../install/installation.md#installation-from-source).
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
+- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
#### Customizing GitLab's appearance
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index f43c89dad87..3d24812c66a 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -62,7 +62,14 @@ To change the address/port that Prometheus listens on:
```
Replace `localhost:9090` with the address/port you want Prometheus to
- listen on.
+ listen on. If you would like to allow access to Prometheus to hosts other
+ than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
+
+ ```ruby
+ prometheus['listen_address'] = ':9090'
+ # or
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md
index c91ac3012b9..3ae41638ac3 100644
--- a/doc/administration/plugins.md
+++ b/doc/administration/plugins.md
@@ -1,66 +1,80 @@
-# Plugins
+# GitLab Plugin system
-**Note:** Plugins must be configured on the filesystem of the GitLab
-server. Only GitLab server administrators will be able to complete these tasks.
-Please explore [system hooks] or [webhooks] as an option if you do not
-have filesystem access.
+> Introduced in GitLab 10.6.
-Introduced in GitLab 10.6.
+With custom plugins, GitLab administrators can introduce custom integrations
+without modifying GitLab's source code.
-A plugin will run on each event so it's up to you to filter events or projects within a plugin code. You can have as many plugins as you want. Each plugin will be triggered by GitLab asynchronously in case of an event. For a list of events please see [system hooks] documentation.
+NOTE: **Note:**
+Instead of writing and supporting your own plugin you can make changes
+directly to the GitLab source code and contribute back upstream. This way we can
+ensure functionality is preserved across versions and covered by tests.
+
+NOTE: **Note:**
+Plugins must be configured on the filesystem of the GitLab server. Only GitLab
+server administrators will be able to complete these tasks. Explore
+[system hooks] or [webhooks] as an option if you do not have filesystem access.
+
+A plugin will run on each event so it's up to you to filter events or projects
+within a plugin code. You can have as many plugins as you want. Each plugin will
+be triggered by GitLab asynchronously in case of an event. For a list of events
+see the [system hooks] documentation.
## Setup
-Plugins must be placed directly into `plugins` directory, subdirectories will be ignored.
-There is an `example` directory inside `plugins` where you can find some basic examples.
+The plugins must be placed directly into the `plugins` directory, subdirectories
+will be ignored. There is an
+[`example` directory inside `plugins`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/plugins/examples)
+where you can find some basic examples.
Follow the steps below to set up a custom hook:
-1. On the GitLab server, navigate to the project's plugin directory.
+1. On the GitLab server, navigate to the plugin directory.
For an installation from source the path is usually
`/home/git/gitlab/plugins/`. For Omnibus installs the path is
usually `/opt/gitlab/embedded/service/gitlab-rails/plugins`.
-1. Inside the `plugins` directory, create a file with a name of your choice, but without spaces or special characters.
+1. Inside the `plugins` directory, create a file with a name of your choice,
+ without spaces or special characters.
1. Make the hook file executable and make sure it's owned by the git user.
-1. Write the code to make the plugin function as expected. Plugin can be
- in any language. Ensure the 'shebang' at the top properly reflects the language
- type. For example, if the script is in Ruby the shebang will probably be
- `#!/usr/bin/env ruby`.
-1. The data to the plugin will be provided as JSON on STDIN. It will be exactly same as one for [system hooks]
+1. Write the code to make the plugin function as expected. That can be
+ in any language, and ensure the 'shebang' at the top properly reflects the
+ language type. For example, if the script is in Ruby the shebang will
+ probably be `#!/usr/bin/env ruby`.
+1. The data to the plugin will be provided as JSON on STDIN. It will be exactly
+ same as for [system hooks]
-That's it! Assuming the plugin code is properly implemented the hook will fire
-as appropriate. Plugins file list is updated for each event. There is no need to restart GitLab to apply a new plugin.
+That's it! Assuming the plugin code is properly implemented, the hook will fire
+as appropriate. The plugins file list is updated for each event, there is no
+need to restart GitLab to apply a new plugin.
If a plugin executes with non-zero exit code or GitLab fails to execute it, a
message will be logged to `plugin.log`.
## Validation
-Writing own plugin can be tricky and its easier if you can check it without altering the system.
-We provided a rake task you can use with staging environment to test your plugin before using it in production.
-The rake task will use a sample data and execute each of plugins. By output you should be able to determine if
-system sees your plugin and if it was executed without errors.
+Writing your own plugin can be tricky and it's easier if you can check it
+without altering the system. A rake task is provided so that you can use it
+in a staging environment to test your plugin before using it in production.
+The rake task will use a sample data and execute each of plugin. The output
+should be enough to determine if the system sees your plugin and if it was
+executed without errors.
```bash
# Omnibus installations
sudo gitlab-rake plugins:validate
# Installations from source
+cd /home/git/gitlab
bundle exec rake plugins:validate RAILS_ENV=production
```
-Example of output can be next:
+Example of output:
```
--> bundle exec rake plugins:validate RAILS_ENV=production
Validating plugins from /plugins directory
* /home/git/gitlab/plugins/save_to_file.clj succeed (zero exit code)
* /home/git/gitlab/plugins/save_to_file.rb failure (non-zero exit code)
```
-[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks
[system hooks]: ../system_hooks/system_hooks.md
[webhooks]: ../user/project/integrations/webhooks.md
-[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073
-[93]: https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/93
-
diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md
index 3e0683f378d..0d7d0fd9c42 100644
--- a/doc/api/group_badges.md
+++ b/doc/api/group_badges.md
@@ -1,5 +1,8 @@
# Group badges API
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17082)
+in GitLab 10.6.
+
## Placeholder tokens
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
@@ -182,7 +185,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a
Example response:
```json
-{
+{
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
"image_url": "https://shields.io/my/badge",
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
diff --git a/doc/api/project_badges.md b/doc/api/project_badges.md
index 3f6e348b5b4..94389273e9c 100644
--- a/doc/api/project_badges.md
+++ b/doc/api/project_badges.md
@@ -1,5 +1,8 @@
# Project badges API
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17082)
+in GitLab 10.6.
+
## Placeholder tokens
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
@@ -179,7 +182,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a
Example response:
```json
-{
+{
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
"image_url": "https://shields.io/my/badge",
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index 995f10571d0..d8f61852b11 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -111,6 +111,7 @@ POST /projects/import
| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
| `file` | string | yes | The file to be uploaded |
| `path` | string | yes | Name and path for new project |
+| `overwrite` | boolean | no | If there is a project with the same path the import will overwrite it. Default to false |
| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md)] |
The override params passed will take precendence over all values defined inside the export file.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 58c4a71cef9..b3d9f0bc96c 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -247,10 +247,19 @@ declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
the basis of [Review apps](review_apps/index.md).
>**Note:**
-The `name` and `url` parameters can use any of the defined CI variables,
+The `name` and `url` parameters can use most of the defined CI variables,
including predefined, secure variables and `.gitlab-ci.yml`
-[`variables`](yaml/README.md#variables).
-You however cannot use variables defined under `script` or on the Runner's side.
+[`variables`](yaml/README.md#variables). You however cannot use variables
+defined under `script` or on the Runner's side. There are other variables that
+are unsupported in environment name context:
+- `CI_JOB_ID`
+- `CI_JOB_TOKEN`
+- `CI_BUILD_ID`
+- `CI_BUILD_TOKEN`
+- `CI_REGISTRY_USER`
+- `CI_REGISTRY_PASSWORD`
+- `CI_REPOSITORY_URL`
+- `CI_ENVIRONMENT_URL`
GitLab Runner exposes various [environment variables][variables] when a job runs,
and as such, you can use them as environment names. Let's add another job in
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 9f268f47e6f..4a504a98902 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -454,8 +454,8 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
> Variables expressions were added in GitLab 10.7.
It is possible to use variables expressions with only / except policies in
-`.gitlab-ci.yml`. By using this approach you can limit what builds are going to
-be created within a pipeline after pushing code to GitLab.
+`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to
+be created within a pipeline after pushing a code to GitLab.
This is particularly useful in combination with secret variables and triggered
pipeline variables.
@@ -470,22 +470,21 @@ deploy:
- $STAGING
```
-Each provided variables expression is going to be evaluated before creating
-a pipeline.
+Each expression provided is going to be evaluated before creating a pipeline.
If any of the conditions in `variables` evaluates to truth when using `only`,
a new job is going to be created. If any of the expressions evaluates to truth
when `except` is being used, a job is not going to be created.
-This follows usual rules for `only` / `except` policies.
+This follows usual rules for [`only` / `except` policies][builds-policies].
### Supported syntax
-Below you can find currently supported syntax reference:
+Below you can find supported syntax reference:
1. Equality matching using a string
- Example: `$VARIABLE == "some value"`
+ > Example: `$VARIABLE == "some value"`
You can use equality operator `==` to compare a variable content to a
string. We support both, double quotes and single quotes to define a string
@@ -494,26 +493,62 @@ Below you can find currently supported syntax reference:
1. Checking for an undefined value
- It sometimes happens that you want to check whether variable is defined or
- not. To do that, you can compare variable to `null` value, like
+ > Example: `$VARIABLE == null`
+
+ It sometimes happens that you want to check whether a variable is defined
+ or not. To do that, you can compare a variable to `null` keyword, like
`$VARIABLE == null`. This expression is going to evaluate to truth if
- variable is not set.
+ variable is not defined.
1. Checking for an empty variable
+ > Example: `$VARIABLE == ""`
+
If you want to check whether a variable is defined, but is empty, you can
simply compare it against an empty string, like `$VAR == ''`.
1. Comparing two variables
- It is possible to compare two variables. `$VARIABLE_1 == $VARIABLE_2`.
+ > Example: `$VARIABLE_1 == $VARIABLE_2`
+
+ It is possible to compare two variables. This is going to compare values
+ of these variables.
1. Variable presence check
+ > Example: `$STAGING`
+
If you only want to create a job when there is some variable present,
which means that it is defined and non-empty, you can simply use
variable name as an expression, like `$STAGING`. If `$STAGING` variable
is defined, and is non empty, expression will evaluate to truth.
+ `$STAGING` value needs to a string, with length higher than zero.
+ Variable that contains only whitespace characters is not an empty variable.
+
+### Unsupported predefined variables
+
+Because GitLab evaluates variables before creating jobs, we do not support a
+few variables that depend on persistence layer, like `$CI_JOB_ID`.
+
+Environments (like `production` or `staging`) are also being created based on
+what jobs pipeline consists of, thus some environment-specific variables are
+not supported as well.
+
+We do not support variables containing tokens because of security reasons.
+
+You can find a full list of unsupported variables below:
+
+- `CI_JOB_ID`
+- `CI_JOB_TOKEN`
+- `CI_BUILD_ID`
+- `CI_BUILD_TOKEN`
+- `CI_REGISTRY_USER`
+- `CI_REGISTRY_PASSWORD`
+- `CI_REPOSITORY_URL`
+- `CI_ENVIRONMENT_URL`
+
+These variables are also not supported in a contex of a
+[dynamic environment name][dynamic-environments].
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables"
[eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium"
@@ -525,3 +560,5 @@ Below you can find currently supported syntax reference:
[triggered]: ../triggers/README.md
[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
[subgroups]: ../../user/group/subgroups/index.md
+[builds-policies]: ../yaml/README.md#only-and-except-complex
+[dynamic-environments]: ../environments.md#dynamic-environments
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index be114e7008e..68aa64b3834 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -354,7 +354,7 @@ deploy:
- $STAGING
```
-Learn more about variables expressions on a separate page.
+Learn more about variables expressions on [a separate page][variables-expressions].
## `tags`
@@ -1574,3 +1574,4 @@ CI with various languages.
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
[schedules]: ../../user/project/pipelines/schedules.md
+[variables-expressions]: ../variables/README.md#variables-expressions
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 1962392a9eb..d5a4acff481 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -44,6 +44,7 @@ the `author` field. GitLab team members **should not**.
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
Example: "Fixed a typo on the search results page. (Jane Smith)"
+- Performance improvements **should** have a changelog entry.
## Writing good changelog entries
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index d5f77191938..7baccb796c6 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -72,15 +72,23 @@ The maximum size your Git repository is allowed to be including LFS.
## Shared Runners
Shared Runners on GitLab.com run in [autoscale mode] and powered by
-DigitalOcean. Autoscaling means reduced waiting times to spin up builds,
-and isolated VMs for each project, thus maximizing security.
+Google Cloud Platform and DigitalOcean. Autoscaling means reduced
+waiting times to spin up CI/CD jobs, and isolated VMs for each project,
+thus maximizing security.
They're free to use for public open source projects and limited to 2000 CI
minutes per month per group for private projects. Read about all
[GitLab.com plans](https://about.gitlab.com/pricing/).
-All your builds run on 2GB (RAM) ephemeral instances, with CoreOS and the latest
-Docker Engine installed. The default region of the VMs is NYC.
+In case of DigitalOcean based Runners, all your CI/CD jobs run on ephemeral
+instances with 2GB of RAM, CoreOS and the latest Docker Engine installed.
+Instances provide 2 vCPUs and 60GB of SSD disk space. The default region of the
+VMs is NYC1.
+
+In case of Google Cloud Platform based Runners, all your CI/CD jobs run on
+ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine
+installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
+region of the VMs is US East1.
Below are the shared Runners settings.
@@ -88,52 +96,116 @@ Below are the shared Runners settings.
| ----------- | ----------------- | ---------- |
| [GitLab Runner] | [Runner versions dashboard][ci_version_dashboard] | - |
| Executor | `docker+machine` | - |
-| Default Docker image | `ruby:2.1` | - |
+| Default Docker image | `ruby:2.5` | - |
| `privileged` (run [Docker in Docker]) | `true` | `false` |
-[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?refresh=5m&orgId=1&panelId=12&fullscreen&from=now-1h&to=now&var-runner_type=All&var-cache_server=All&var-gl_monitor_fqdn=postgres-01.db.prd.gitlab.com&var-has_minutes=yes&var-hanging_droplets_cleaner=All&var-droplet_zero_machines_cleaner=All&var-runner_job_failure_reason=All&theme=light
+[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?from=now-1h&to=now&refresh=5m&orgId=1&panelId=12&fullscreen&theme=light
### `config.toml`
The full contents of our `config.toml` are:
+**DigitalOcean**
+
```toml
+concurrent = X
+check_interval = 1
+metrics_server = "X"
+sentry_dsn = "X"
+
[[runners]]
name = "docker-auto-scale"
- limit = X
request_concurrency = X
- url = "https://gitlab.com/ci"
+ url = "https://gitlab.com/"
token = "SHARED_RUNNER_TOKEN"
executor = "docker+machine"
environment = [
"DOCKER_DRIVER=overlay2"
]
+ limit = X
[runners.docker]
- image = "ruby:2.1"
+ image = "ruby:2.5"
privileged = true
[runners.machine]
- IdleCount = 40
+ IdleCount = 20
IdleTime = 1800
+ OffPeakPeriods = ["* * * * * sat,sun *"]
+ OffPeakTimezone = "UTC"
+ OffPeakIdleCount = 5
+ OffPeakIdleTime = 1800
MaxBuilds = 1
+ MachineName = "srm-%s"
MachineDriver = "digitalocean"
- MachineName = "machine-%s-digital-ocean-2gb"
MachineOptions = [
- "digitalocean-image=coreos-stable",
+ "digitalocean-image=X",
"digitalocean-ssh-user=core",
- "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN",
"digitalocean-region=nyc1",
- "digitalocean-size=2gb",
+ "digitalocean-size=s-2vcpu-2gb",
"digitalocean-private-networking",
- "digitalocean-userdata=/etc/gitlab-runner/cloudinit.sh",
- "engine-registry-mirror=http://IP_TO_OUR_REGISTRY_MIRROR"
+ "digitalocean-tags=shared_runners,gitlab_com",
+ "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR",
+ "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN",
]
[runners.cache]
Type = "s3"
- ServerAddress = "IP_TO_OUR_CACHE_SERVER"
+ BucketName = "runner"
+ Insecure = true
+ Shared = true
+ ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
AccessKey = "ACCESS_KEY"
SecretKey = "ACCESS_SECRET_KEY"
+```
+
+**Google Cloud Platform**
+
+```toml
+concurrent = X
+check_interval = 1
+metrics_server = "X"
+sentry_dsn = "X"
+
+[[runners]]
+ name = "docker-auto-scale"
+ request_concurrency = X
+ url = "https://gitlab.com/"
+ token = "SHARED_RUNNER_TOKEN"
+ executor = "docker+machine"
+ environment = [
+ "DOCKER_DRIVER=overlay2"
+ ]
+ limit = X
+ [runners.docker]
+ image = "ruby:2.5"
+ privileged = true
+ [runners.machine]
+ IdleCount = 20
+ IdleTime = 1800
+ OffPeakPeriods = ["* * * * * sat,sun *"]
+ OffPeakTimezone = "UTC"
+ OffPeakIdleCount = 5
+ OffPeakIdleTime = 1800
+ MaxBuilds = 1
+ MachineName = "srm-%s"
+ MachineDriver = "google"
+ MachineOptions = [
+ "google-project=PROJECT",
+ "google-disk-size=25",
+ "google-machine-type=n1-standard-1",
+ "google-username=core",
+ "google-tags=gitlab-com,srm",
+ "google-use-internal-ip",
+ "google-zone=us-east1-d",
+ "google-machine-image=PROJECT/global/images/IMAGE",
+ "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR"
+ ]
+ [runners.cache]
+ Type = "s3"
BucketName = "runner"
+ Insecure = true
Shared = true
+ ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
+ AccessKey = "ACCESS_KEY"
+ SecretKey = "ACCESS_SECRET_KEY"
```
## Sidekiq
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
new file mode 100644
index 00000000000..c4e59444ef7
--- /dev/null
+++ b/doc/user/project/badges.md
@@ -0,0 +1,73 @@
+# Badges
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41174)
+in GitLab 10.7.
+
+Badges are a unified way to present condensed pieces of information about your
+projects. They consist of a small image and additionally a URL that the image
+points to. Examples for badges can be the [pipeline status], [test coverage],
+or ways to contact the project maintainers.
+
+![Badges on Project overview page](img/project_overview_badges.png)
+
+## Project badges
+
+Badges can be added to a project and will then be visible on the project's overview page.
+If you find that you have to add the same badges to several projects, you may want to add them at the [group level](#group-badges).
+
+To add a new badge to a project:
+
+1. Navigate to your project's **Settings > Badges**.
+1. Under "Link", enter the URL that the badges should point to and under
+ "Badge image URL" the URL of the image that should be displayed.
+1. Submit the badge by clicking the **Add badge** button.
+
+After adding a badge to a project, you can see it in the list below the form.
+You can edit it by clicking on the pen icon next to it or to delete it by
+clicking on the trash icon.
+
+Badges associated with a group can only be edited or deleted on the
+[group level](#group-badges).
+
+## Group badges
+
+Badges can be added to a group and will then be visible on every project's
+overview page that's under that group. In this case, they cannot be edited or
+deleted on the project level. If you need to have individual badges for each
+project, consider adding them on the [project level](#project-badges) or use
+[placeholders](#placeholders).
+
+To add a new badge to a group:
+
+1. Navigate to your group's **Settings > Project Badges**.
+1. Under "Link", enter the URL that the badges should point to and under
+ "Badge image URL" the URL of the image that should be displayed.
+1. Submit the badge by clicking the **Add badge** button.
+
+After adding a badge to a group, you can see it in the list below the form.
+You can edit the badge by clicking on the pen icon next to it or to delete it
+by clicking on the trash icon.
+
+Badges directly associated with a project can be configured on the
+[project level](#project-badges).
+
+## Placeholders
+
+The URL a badge points to, as well as the image URL, can contain placeholders
+which will be evaluated when displaying the badge. The following placeholders
+are available:
+
+- `%{project_path}`: Path of a project including the parent groups
+- `%{project_id}`: Database ID associated with a project
+- `%{default_branch}`: Default branch name configured for a project's repository
+- `%{commit_sha}`: ID of the most recent commit to the default branch of a
+ project's repository
+
+## API
+
+You can also configure badges via the GitLab API. As in the settings, there is
+a distinction between endpoints for badges on the
+[project level](../../api/project_badges.md) and [group level](../../api/group_badges.md).
+
+[pipeline status]: pipelines/settings.md#pipeline-status-badge
+[test coverage]: pipelines/settings.md#test-coverage-report-badge
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 394aa9209e4..9c5e3509046 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -115,15 +115,16 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do
## Using with private projects
-> [Introduced][ce-11845] in GitLab 9.3.
+> Personal Access tokens were [introduced][ce-11845] in GitLab 9.3.
+> Project Deploy Tokens were [introduced][ce-17894] in GitLab 10.7
If a project is private, credentials will need to be provided for authorization.
-The preferred way to do this, is by using [personal access tokens][pat].
-The minimal scope needed is `read_registry`.
+The preferred way to do this, is either by using a [personal access tokens][pat] or a [project deploy token][pdt].
+The minimal scope needed for both of them is `read_registry`.
Example of using a personal access token:
```
-docker login registry.example.com -u <your_username> -p <your_personal_access_token>
+docker login registry.example.com -u <your_username> -p <your_access_token>
```
## Troubleshooting the GitLab Container Registry
@@ -270,5 +271,7 @@ Once the right permissions were set, the error will go away.
[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894
[docker-docs]: https://docs.docker.com/engine/userguide/intro/
[pat]: ../profile/personal_access_tokens.md
+[pdt]: ../project/deploy_tokens/index.md
diff --git a/doc/user/project/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png
new file mode 100644
index 00000000000..7e2d67a3120
--- /dev/null
+++ b/doc/user/project/deploy_tokens/img/deploy_tokens.png
Binary files differ
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
new file mode 100644
index 00000000000..86fc58020e8
--- /dev/null
+++ b/doc/user/project/deploy_tokens/index.md
@@ -0,0 +1,76 @@
+# Deploy Tokens
+
+> [Introduced][ce-17894] in GitLab 10.7.
+
+Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password.
+
+Please note, that the expiration of deploy tokens happens on the date you define,
+at midnight UTC and that they can be only managed by [masters](https://docs.gitlab.com/ee/user/permissions.html).
+
+## Creating a Deploy Token
+
+You can create as many deploy tokens as you like from the settings of your project:
+
+1. Log in to your GitLab account.
+1. Go to the project you want to create Deploy Tokens for.
+1. Go to **Settings** > **Repository**
+1. Click on "Expand" on **Deploy Tokens** section
+1. Choose a name and optionally an expiry date for the token.
+1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token).
+1. Click on **Create deploy token**.
+1. Save the deploy token somewhere safe. Once you leave or refresh
+ the page, **you won't be able to access it again**.
+
+![Personal access tokens page](img/deploy_tokens.png)
+
+## Revoking a personal access token
+
+At any time, you can revoke any deploy token by just clicking the
+respective **Revoke** button under the 'Active deploy tokens' area.
+
+## Limiting scopes of a deploy token
+
+Deploy tokens can be created with two different scopes that allow various
+actions that a given token can perform. The available scopes are depicted in
+the following table.
+
+| Scope | Description |
+| ----- | ----------- |
+| `read_repository` | Allows read-access to the repository through `git clone` |
+| `read_registry` | Allows read-access to [container registry] images if a project is private and authorization is required. |
+
+## Usage
+
+### Git clone a repository
+
+To download a repository using a Deploy Token, you just need to:
+
+1. Create a Deploy Token with `read_repository` as a scope.
+2. Take note of your `username` and `token`
+3. `git clone` the project using the Deploy Token:
+
+
+```bash
+git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
+```
+
+Just replace `<username>` and `<deploy_token>` with the proper values
+
+### Read container registry images
+
+To read the container registry images, you'll need to:
+
+1. Create a Deploy Token with `read_registry` as a scope.
+2. Take note of your `username` and `token`
+3. Log in to GitLab’s Container Registry using the deploy token:
+
+```
+docker login registry.example.com -u <username> -p <deploy_token>
+```
+
+Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply
+pull images from your Container Registry.
+
+[ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894
+[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[container registry]: ../container_registry.md
diff --git a/doc/user/project/img/project_overview_badges.png b/doc/user/project/img/project_overview_badges.png
new file mode 100644
index 00000000000..3067a7dfa13
--- /dev/null
+++ b/doc/user/project/img/project_overview_badges.png
Binary files differ
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index f94e93dd7d8..5ce4ebfa811 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -27,6 +27,7 @@ integrated platform
- [Protected tags](protected_tags.md): Control over who has
permission to create tags, and prevent accidental update or deletion
- [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
+ - [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**Starter/Premium**): Ask for approval before
@@ -73,6 +74,7 @@ website with GitLab Pages
- [Cycle Analytics](cycle_analytics.md): Review your development lifecycle
- [Syntax highlighting](highlighting.md): An alternative to customize
your code blocks, overriding GitLab's default choice of language
+- [Badges](badges.md): Badges for the project overview
### Project's integrations
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index b4a842f33d6..7eab825fa32 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -240,8 +240,7 @@ Issue Board, that is create/delete lists and drag issues around.
>Introduced in GitLab 10.6
Group issue board is analogous to project-level issue board and it is accessible at the group
-navigation level. A group-level issue board allows you to view all issues from all projects in that group
-(currently, it does not see issues from projects in subgroups). Similarly, you can only filter by group labels for these
+navigation level. A group-level issue board allows you to view all issues from all projects in that group or descendant subgroups. Similarly, you can only filter by group labels for these
boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only
group-level objects are available.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 6cead7b9961..14f2e522f01 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -106,7 +106,7 @@ If you want to auto-cancel all pending non-HEAD pipelines on branch, when
new pipeline will be created (after your git push or manually from UI),
check **Auto-cancel pending pipelines** checkbox and save the changes.
-## Badges
+## Pipeline Badges
In the pipelines settings page you can find pipeline status and test coverage
badges for your project. The latest successful pipeline will be used to read
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index a3e4459f169..f5950145348 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -11,7 +11,7 @@ module SharedBuilds
step 'project has a recent build' do
@pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
- @build = create(:ci_build, :running, :coverage, pipeline: @pipeline)
+ @build = create(:ci_build, :running, :coverage, :trace_artifact, pipeline: @pipeline)
end
step 'recent build is successful' do
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index 334948b2995..8ceffe9c5ef 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -127,6 +127,7 @@ module API
end
destroy_conditionally!(badge)
+ body false
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a582aa0ec2c..61dab1dd5cb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -468,8 +468,8 @@ module API
header(*Gitlab::Workhorse.send_git_blob(repository, blob))
end
- def send_git_archive(repository, ref:, format:)
- header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+ def send_git_archive(repository, **kwargs)
+ header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
end
def send_artifacts_entry(build, entry)
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index f74b3b26802..88e7f46c92c 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -97,7 +97,7 @@ module API
get ":id/issues" do
group = find_group!(params[:id])
- issues = paginate(find_issues(group_id: group.id))
+ issues = paginate(find_issues(group_id: group.id, include_subgroups: true))
options = {
with: Entities::IssueBasic,
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 303b58a5942..bc5152e539f 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -26,6 +26,7 @@ module API
requires :path, type: String, desc: 'The new project path and name'
requires :file, type: File, desc: 'The project export file to be imported'
optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
+ optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it'
optional :override_params,
type: Hash,
desc: 'New project params to override values in the export' do
@@ -50,7 +51,8 @@ module API
project_params = {
path: import_params[:path],
namespace_id: namespace.id,
- file: import_params[:file]['tempfile']
+ file: import_params[:file]['tempfile'],
+ overwrite: import_params[:overwrite]
}
override_params = import_params.delete(:override_params)
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 9638c53a1df..2396dc73f0e 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -88,7 +88,7 @@ module API
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
- send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue
not_found!('File')
end
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
index 5b54734bb45..f701d64e886 100644
--- a/lib/api/v3/repositories.rb
+++ b/lib/api/v3/repositories.rb
@@ -75,7 +75,7 @@ module API
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
- send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue
not_found!('File')
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 6efaed7e624..a848154b2d4 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -215,6 +215,10 @@ module Banzai
extras << "comment #{$1}"
end
+ extension = matches[:extension] if matches.names.include?("extension")
+
+ extras << extension if extension
+
extras
end
diff --git a/lib/forever.rb b/lib/forever.rb
new file mode 100644
index 00000000000..7df17912544
--- /dev/null
+++ b/lib/forever.rb
@@ -0,0 +1,13 @@
+class Forever
+ POSTGRESQL_DATE = DateTime.new(3000, 1, 1)
+ MYSQL_DATE = DateTime.new(2038, 01, 19)
+
+ # MySQL timestamp has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
+ def self.date
+ if Gitlab::Database.postgresql?
+ POSTGRESQL_DATE
+ else
+ MYSQL_DATE
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 6af763faf10..2a44e11efb6 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -5,7 +5,7 @@ module Gitlab
REGISTRY_SCOPES = [:read_registry].freeze
# Scopes used for GitLab API access
- API_SCOPES = [:api, :read_user, :sudo].freeze
+ API_SCOPES = [:api, :read_user, :sudo, :read_repository].freeze
# Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze
@@ -26,6 +26,7 @@ module Gitlab
lfs_token_check(login, password, project) ||
oauth_access_token_check(login, password) ||
personal_access_token_check(password) ||
+ deploy_token_check(login, password) ||
user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new
@@ -163,7 +164,8 @@ module Gitlab
def abilities_for_scopes(scopes)
abilities_by_scope = {
api: full_authentication_abilities,
- read_registry: [:read_container_image]
+ read_registry: [:read_container_image],
+ read_repository: [:download_code]
}
scopes.flat_map do |scope|
@@ -171,6 +173,22 @@ module Gitlab
end.uniq
end
+ def deploy_token_check(login, password)
+ return unless password.present?
+
+ token =
+ DeployToken.active.find_by(token: password)
+
+ return unless token && login
+ return if login != token.username
+
+ scopes = abilities_for_scopes(token.scopes)
+
+ if valid_scoped_token?(token, available_scopes)
+ Gitlab::Auth::Result.new(token, token.project, :deploy_token, scopes)
+ end
+ end
+
def lfs_token_check(login, password, project)
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
index fd5cbf76e47..a357538a885 100644
--- a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
+++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
@@ -96,7 +96,7 @@ module Gitlab
commit_hash.merge(
merge_request_diff_id: merge_request_diff.id,
relative_order: index,
- sha: sha_attribute.type_cast_for_database(sha)
+ sha: sha_attribute.serialize(sha)
)
end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 2d9166d6bdd..024047d4983 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -23,6 +23,10 @@ module Gitlab
'Cancel'
end
+ def action_button_title
+ _('Cancel this job')
+ end
+
def self.matches?(build, user)
build.cancelable?
end
diff --git a/lib/gitlab/ci/status/build/canceled.rb b/lib/gitlab/ci/status/build/canceled.rb
new file mode 100644
index 00000000000..c83e2734a73
--- /dev/null
+++ b/lib/gitlab/ci/status/build/canceled.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Canceled < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/canceled-job_empty.svg',
+ size: 'svg-430',
+ title: _('This job has been canceled')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.canceled?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/created.rb b/lib/gitlab/ci/status/build/created.rb
new file mode 100644
index 00000000000..5be8e9de425
--- /dev/null
+++ b/lib/gitlab/ci/status/build/created.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Created < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: _('This job has not been triggered yet'),
+ content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.created?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/erased.rb b/lib/gitlab/ci/status/build/erased.rb
new file mode 100644
index 00000000000..3a5113b16b6
--- /dev/null
+++ b/lib/gitlab/ci/status/build/erased.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Erased < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: _('Job has been erased')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.erased?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index 20a319caf86..2b26ebb45a1 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -4,7 +4,13 @@ module Gitlab
module Build
class Factory < Status::Factory
def self.extended_statuses
- [[Status::Build::Cancelable,
+ [[Status::Build::Erased,
+ Status::Build::Manual,
+ Status::Build::Canceled,
+ Status::Build::Created,
+ Status::Build::Pending,
+ Status::Build::Skipped],
+ [Status::Build::Cancelable,
Status::Build::Retryable],
[Status::Build::Failed],
[Status::Build::FailedAllowed,
diff --git a/lib/gitlab/ci/status/build/manual.rb b/lib/gitlab/ci/status/build/manual.rb
new file mode 100644
index 00000000000..042da6392d3
--- /dev/null
+++ b/lib/gitlab/ci/status/build/manual.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Manual < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/manual_action.svg',
+ size: 'svg-394',
+ title: _('This job requires a manual action'),
+ content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.playable?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/pending.rb b/lib/gitlab/ci/status/build/pending.rb
new file mode 100644
index 00000000000..9dd9a27ad57
--- /dev/null
+++ b/lib/gitlab/ci/status/build/pending.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Pending < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/pending_job_empty.svg',
+ size: 'svg-430',
+ title: _('This job has not started yet'),
+ content: _('This job is in pending state and is waiting to be picked by a runner')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.pending?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index b7b45466d3b..a8b9ebf0803 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -19,6 +19,10 @@ module Gitlab
'Play'
end
+ def action_button_title
+ _('Trigger this manual action')
+ end
+
def action_path
play_project_job_path(subject.project, subject)
end
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 44ffe783e50..5aeb8e51480 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -15,6 +15,10 @@ module Gitlab
'Retry'
end
+ def action_button_title
+ _('Retry this job')
+ end
+
def action_path
retry_project_job_path(subject.project, subject)
end
diff --git a/lib/gitlab/ci/status/build/skipped.rb b/lib/gitlab/ci/status/build/skipped.rb
new file mode 100644
index 00000000000..3e678d0baee
--- /dev/null
+++ b/lib/gitlab/ci/status/build/skipped.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Skipped < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: _('This job has been skipped')
+ }
+ end
+
+ def self.matches?(build, user)
+ build.skipped?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 46e730797e4..dea838bfa39 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -19,6 +19,10 @@ module Gitlab
'Stop'
end
+ def action_button_title
+ _('Stop this environment')
+ end
+
def action_path
play_project_job_path(subject.project, subject)
end
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index daab6bb2de5..9d6a2f51c11 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -22,6 +22,10 @@ module Gitlab
raise NotImplementedError
end
+ def illustration
+ raise NotImplementedError
+ end
+
def label
raise NotImplementedError
end
@@ -58,6 +62,10 @@ module Gitlab
raise NotImplementedError
end
+ def action_button_title
+ raise NotImplementedError
+ end
+
# Hint that appears on all the pipeline graph tooltips and builds on the right sidebar in Job detail view
def status_tooltip
label
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index d9400e04b83..b2d8ee81977 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -1,12 +1,20 @@
module Gitlab
module Database
- BINARY_TYPE = if Gitlab::Database.postgresql?
- # PostgreSQL defines its own class with slightly different
- # behaviour from the default Binary type.
- ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
- else
- ActiveRecord::Type::Binary
- end
+ BINARY_TYPE =
+ if Gitlab::Database.postgresql?
+ # PostgreSQL defines its own class with slightly different
+ # behaviour from the default Binary type.
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
+ else
+ # In Rails 5.0 `Type` has been moved from `ActiveRecord` to `ActiveModel`
+ # https://github.com/rails/rails/commit/9cc8c6f3730df3d94c81a55be9ee1b7b4ffd29f6#diff-f8ba7983a51d687976e115adcd95822b
+ # Remove this method and leave just `ActiveModel::Type::Binary` when removing Gitlab.rails5? code.
+ if Gitlab.rails5?
+ ActiveModel::Type::Binary
+ else
+ ActiveRecord::Type::Binary
+ end
+ end
# Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
#
@@ -16,18 +24,39 @@ module Gitlab
class ShaAttribute < BINARY_TYPE
PACK_FORMAT = 'H*'.freeze
- # Casts binary data to a SHA1 in hexadecimal.
+ # It is called from activerecord-4.2.10/lib/active_record internal methods.
+ # Remove this method when removing Gitlab.rails5? code.
def type_cast_from_database(value)
- value = super
+ unpack_sha(super)
+ end
+
+ # It is called from activerecord-4.2.10/lib/active_record internal methods.
+ # Remove this method when removing Gitlab.rails5? code.
+ def type_cast_for_database(value)
+ serialize(value)
+ end
+ # It is called from activerecord-5.0.6/lib/active_record/attribute.rb
+ # Remove this method when removing Gitlab.rails5? code..
+ def deserialize(value)
+ value = Gitlab.rails5? ? super : method(:type_cast_from_database).super_method.call(value)
+
+ unpack_sha(value)
+ end
+
+ # Rename this method to `deserialize(value)` removing Gitlab.rails5? code.
+ # Casts binary data to a SHA1 in hexadecimal.
+ def unpack_sha(value)
+ # Uncomment this line when removing Gitlab.rails5? code.
+ # value = super
value ? value.unpack(PACK_FORMAT)[0] : nil
end
# Casts a SHA1 in hexadecimal to the proper binary format.
- def type_cast_for_database(value)
+ def serialize(value)
arg = value ? [value].pack(PACK_FORMAT) : nil
- super(arg)
+ Gitlab.rails5? ? super(arg) : method(:type_cast_for_database).super_method.call(arg)
end
end
end
diff --git a/lib/gitlab/git/checksum.rb b/lib/gitlab/git/checksum.rb
deleted file mode 100644
index 3ef0f0a8854..00000000000
--- a/lib/gitlab/git/checksum.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-module Gitlab
- module Git
- class Checksum
- include Gitlab::Git::Popen
-
- EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
-
- Failure = Class.new(StandardError)
-
- attr_reader :path, :relative_path, :storage, :storage_path
-
- def initialize(storage, relative_path)
- @storage = storage
- @storage_path = Gitlab.config.repositories.storages[storage].legacy_disk_path
- @relative_path = "#{relative_path}.git"
- @path = File.join(storage_path, @relative_path)
- end
-
- def calculate
- unless repository_exists?
- failure!(Gitlab::Git::Repository::NoRepository, 'No repository for such path')
- end
-
- calculate_checksum_by_shelling_out
- end
-
- private
-
- def repository_exists?
- raw_repository.exists?
- end
-
- def calculate_checksum_by_shelling_out
- args = %W(--git-dir=#{path} show-ref --heads --tags)
- output, status = run_git(args)
-
- if status&.zero?
- refs = output.split("\n")
-
- result = refs.inject(nil) do |checksum, ref|
- value = Digest::SHA1.hexdigest(ref).hex
-
- if checksum.nil?
- value
- else
- checksum ^ value
- end
- end
-
- result.to_s(16)
- else
- # Empty repositories return with a non-zero status and an empty output.
- if output&.empty?
- EMPTY_REPOSITORY_CHECKSUM
- else
- failure!(Gitlab::Git::Checksum::Failure, output)
- end
- end
- end
-
- def failure!(klass, message)
- Gitlab::GitLogger.error("'git show-ref --heads --tags' in #{path}: #{message}")
-
- raise klass.new("Could not calculate the checksum for #{path}: #{message}")
- end
-
- def circuit_breaker
- @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
- end
-
- def raw_repository
- Gitlab::Git::Repository.new(storage, relative_path, nil)
- end
-
- def run_git(args)
- circuit_breaker.perform do
- popen([Gitlab.config.git.bin_path, *args], path)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb
index 07b7e811a34..c3cb0264112 100644
--- a/lib/gitlab/git/conflict/resolver.rb
+++ b/lib/gitlab/git/conflict/resolver.rb
@@ -23,7 +23,7 @@ module Gitlab
end
rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
- rescue Rugged::OdbError, GRPC::BadStatus => e
+ rescue Rugged::ReferenceError, Rugged::OdbError, GRPC::BadStatus => e
raise Gitlab::Git::CommandError.new(e)
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8d97bfb0e6a..79cacd9f6f5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -23,6 +23,7 @@ module Gitlab
SQUASH_WORKTREE_PREFIX = 'squash'.freeze
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
+ EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
NoRepository = Class.new(StandardError)
InvalidBlobName = Class.new(StandardError)
@@ -31,6 +32,7 @@ module Gitlab
DeleteBranchError = Class.new(StandardError)
CreateTreeError = Class.new(StandardError)
TagExistsError = Class.new(StandardError)
+ ChecksumError = Class.new(StandardError)
class << self
# Unlike `new`, `create` takes the repository path
@@ -394,17 +396,24 @@ module Gitlab
nil
end
- def archive_prefix(ref, sha)
+ def archive_prefix(ref, sha, append_sha:)
+ append_sha = (ref != sha) if append_sha.nil?
+
project_name = self.name.chomp('.git')
- "#{project_name}-#{ref.tr('/', '-')}-#{sha}"
+ formatted_ref = ref.tr('/', '-')
+
+ prefix_segments = [project_name, formatted_ref]
+ prefix_segments << sha if append_sha
+
+ prefix_segments.join('-')
end
- def archive_metadata(ref, storage_path, format = "tar.gz")
+ def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
- prefix = archive_prefix(ref, commit.id)
+ prefix = archive_prefix(ref, commit.id, append_sha: append_sha)
{
'RepoPath' => path,
@@ -1362,6 +1371,18 @@ module Gitlab
raise CommandError.new(e)
end
+ def clean_stale_repository_files
+ gitaly_migrate(:repository_cleanup, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
+ gitaly_repository_client.cleanup if is_enabled && exists?
+ end
+ rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
+ Rails.logger.error("Unable to clean repository on storage #{storage} with path #{path}: #{e.message}")
+ Gitlab::Metrics.counter(
+ :failed_repository_cleanup_total,
+ 'Number of failed repository cleanup events'
+ ).increment
+ end
+
def branch_names_contains_sha(sha)
gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
if is_enabled
@@ -1456,6 +1477,43 @@ module Gitlab
run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"])
end
+ def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
+ base_args = %w(worktree add --detach)
+
+ # Note that we _don't_ want to test for `.present?` here: If the caller
+ # passes an non nil empty value it means it still wants sparse checkout
+ # but just isn't interested in any file, perhaps because it wants to
+ # checkout files in by a changeset but that changeset only adds files.
+ if sparse_checkout_files
+ # Create worktree without checking out
+ run_git!(base_args + ['--no-checkout', worktree_path], env: env)
+ worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp
+
+ configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
+
+ # After sparse checkout configuration, checkout `branch` in worktree
+ run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
+ else
+ # Create worktree and checkout `branch` in it
+ run_git!(base_args + [worktree_path, branch], env: env)
+ end
+
+ yield
+ ensure
+ FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
+ FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
+ end
+
+ def checksum
+ gitaly_migrate(:calculate_checksum) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.calculate_checksum
+ else
+ calculate_checksum_by_shelling_out
+ end
+ end
+ end
+
private
def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
@@ -1542,33 +1600,6 @@ module Gitlab
File.exist?(path) && !clean_stuck_worktree(path)
end
- def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
- base_args = %w(worktree add --detach)
-
- # Note that we _don't_ want to test for `.present?` here: If the caller
- # passes an non nil empty value it means it still wants sparse checkout
- # but just isn't interested in any file, perhaps because it wants to
- # checkout files in by a changeset but that changeset only adds files.
- if sparse_checkout_files
- # Create worktree without checking out
- run_git!(base_args + ['--no-checkout', worktree_path], env: env)
- worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp
-
- configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
-
- # After sparse checkout configuration, checkout `branch` in worktree
- run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
- else
- # Create worktree and checkout `branch` in it
- run_git!(base_args + [worktree_path, branch], env: env)
- end
-
- yield
- ensure
- FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
- FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
- end
-
def clean_stuck_worktree(path)
return false unless File.mtime(path) < 15.minutes.ago
@@ -2401,6 +2432,34 @@ module Gitlab
def sha_from_ref(ref)
rev_parse_target(ref).oid
end
+
+ def calculate_checksum_by_shelling_out
+ raise NoRepository unless exists?
+
+ args = %W(--git-dir=#{path} show-ref --heads --tags)
+ output, status = run_git(args)
+
+ if status.nil? || !status.zero?
+ # Empty repositories return with a non-zero status and an empty output.
+ return EMPTY_REPOSITORY_CHECKSUM if output&.empty?
+
+ raise ChecksumError, output
+ end
+
+ refs = output.split("\n")
+
+ result = refs.inject(nil) do |checksum, ref|
+ value = Digest::SHA1.hexdigest(ref).hex
+
+ if checksum.nil?
+ value
+ else
+ checksum ^ value
+ end
+ end
+
+ result.to_s(16)
+ end
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 6a01957184d..0d1ee73ca1a 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -208,6 +208,7 @@ module Gitlab
def check_download_access!
passed = deploy_key? ||
+ deploy_token? ||
user_can_download_code? ||
build_can_download_code? ||
guest_can_download_code?
@@ -238,6 +239,11 @@ module Gitlab
end
def check_change_access!(changes)
+ # If there are worktrees with a HEAD pointing to a non-existent object,
+ # calls to `git rev-list --all` will fail in git 2.15+. This should also
+ # clear stale lock files.
+ project.repository.clean_stale_repository_files
+
changes_list = Gitlab::ChangesList.new(changes)
# Iterate over all changes to find if user allowed all of them to be applied
@@ -269,6 +275,14 @@ module Gitlab
actor.is_a?(DeployKey)
end
+ def deploy_token
+ actor if deploy_token?
+ end
+
+ def deploy_token?
+ actor.is_a?(DeployToken)
+ end
+
def ci?
actor == :ci
end
@@ -276,6 +290,8 @@ module Gitlab
def can_read_project?
if deploy_key?
deploy_key.has_access_to?(project)
+ elsif deploy_token?
+ deploy_token.has_access_to?(project)
elsif user
user.can?(:read_project, project)
elsif ci?
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index e1bc2f9ab61..6441065f5fe 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -19,6 +19,11 @@ module Gitlab
response.exists
end
+ def cleanup
+ request = Gitaly::CleanupRequest.new(repository: @gitaly_repo)
+ GitalyClient.call(@storage, :repository_service, :cleanup, request)
+ end
+
def garbage_collect(create_bitmap)
request = Gitaly::GarbageCollectRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap)
GitalyClient.call(@storage, :repository_service, :garbage_collect, request)
@@ -257,6 +262,12 @@ module Gitlab
response.license_short_name.presence
end
+
+ def calculate_checksum
+ request = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request)
+ response.checksum.presence
+ end
end
end
end
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index c490bf059d2..63cab07324a 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -1,6 +1,9 @@
module Gitlab
module ImportExport
class Importer
+ include Gitlab::Allowable
+ include Gitlab::Utils::StrongMemoize
+
def self.imports_repository?
true
end
@@ -13,12 +16,14 @@ module Gitlab
end
def execute
- if import_file && check_version! && restorers.all?(&:restore)
+ if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
project_tree.restored_project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
-
+ rescue => e
+ raise Projects::ImportService::Error.new(e.message)
+ ensure
remove_import_file
end
@@ -26,7 +31,7 @@ module Gitlab
def restorers
[repo_restorer, wiki_restorer, project_tree, avatar_restorer,
- uploads_restorer, lfs_restorer]
+ uploads_restorer, lfs_restorer, statistics_restorer]
end
def import_file
@@ -69,6 +74,10 @@ module Gitlab
Gitlab::ImportExport::LfsRestorer.new(project: project_tree.restored_project, shared: @shared)
end
+ def statistics_restorer
+ Gitlab::ImportExport::StatisticsRestorer.new(project: project_tree.restored_project, shared: @shared)
+ end
+
def path_with_namespace
File.join(@project.namespace.full_path, @project.path)
end
@@ -84,6 +93,33 @@ module Gitlab
def remove_import_file
FileUtils.rm_rf(@archive_file)
end
+
+ def overwrite_project
+ project = project_tree.restored_project
+
+ return unless can?(@current_user, :admin_namespace, project.namespace)
+
+ if overwrite_project?
+ ::Projects::OverwriteProjectService.new(project, @current_user)
+ .execute(project_to_overwrite)
+ end
+
+ true
+ end
+
+ def original_path
+ @project.import_data&.data&.fetch('original_path', nil)
+ end
+
+ def overwrite_project?
+ original_path.present? && project_to_overwrite.present?
+ end
+
+ def project_to_overwrite
+ strong_memoize(:project_to_overwrite) do
+ Project.find_by_full_path("#{@project.namespace.full_path}/#{original_path}")
+ end
+ end
end
end
end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 2c315207298..d5590dde40f 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -92,7 +92,7 @@ module Gitlab
end
def override_params
- return {} unless params = @project.import_data&.data&.fetch('override_params')
+ return {} unless params = @project.import_data&.data&.fetch('override_params', nil)
@override_params ||= params.select do |key, _value|
Project.column_names.include?(key.to_s) &&
diff --git a/lib/gitlab/import_export/statistics_restorer.rb b/lib/gitlab/import_export/statistics_restorer.rb
new file mode 100644
index 00000000000..bcdd9c12c85
--- /dev/null
+++ b/lib/gitlab/import_export/statistics_restorer.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module ImportExport
+ class StatisticsRestorer
+ def initialize(project:, shared:)
+ @project = project
+ @shared = shared
+ end
+
+ def restore
+ @project.statistics.refresh!
+ rescue => e
+ @shared.error(e)
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
index aad76e335af..f5879de1e94 100644
--- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -79,7 +79,7 @@ module Gitlab
def common_query_context(environment, timeframe_start:, timeframe_end:)
base_query_context(timeframe_start, timeframe_end).merge({
ci_environment_slug: environment.slug,
- kube_namespace: environment.project.deployment_platform&.actual_namespace || '',
+ kube_namespace: environment.deployment_platform&.actual_namespace || '',
environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
})
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 2faeaf16d55..153cb2a8bb1 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -59,10 +59,10 @@ module Gitlab
]
end
- def send_git_archive(repository, ref:, format:)
+ def send_git_archive(repository, ref:, format:, append_sha:)
format ||= 'tar.gz'
format.downcase!
- params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
+ params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
raise "Repository or ref not found" if params.empty?
if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
diff --git a/rubocop/cop/rspec/factories_in_migration_specs.rb b/rubocop/cop/rspec/factories_in_migration_specs.rb
new file mode 100644
index 00000000000..0c5aa838a2c
--- /dev/null
+++ b/rubocop/cop/rspec/factories_in_migration_specs.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helpers'
+
+module RuboCop
+ module Cop
+ module RSpec
+ # This cop checks for the usage of factories in migration specs
+ #
+ # @example
+ #
+ # # bad
+ # let(:user) { create(:user) }
+ #
+ # # good
+ # let(:users) { table(:users) }
+ # let(:user) { users.create!(name: 'User 1', username: 'user1') }
+ class FactoriesInMigrationSpecs < RuboCop::Cop::Cop
+ include SpecHelpers
+
+ MESSAGE = "Don't use FactoryBot.%s in migration specs, use `table` instead.".freeze
+ FORBIDDEN_METHODS = %i[build build_list create create_list].freeze
+
+ def_node_search :forbidden_factory_usage?, <<~PATTERN
+ (send {(const nil? :FactoryBot) nil?} {#{FORBIDDEN_METHODS.map(&:inspect).join(' ')}} ...)
+ PATTERN
+
+ # Following is what node.children looks like on a match:
+ # - Without FactoryBot namespace: [nil, :build, s(:sym, :user)]
+ # - With FactoryBot namespace: [s(:const, nil, :FactoryBot), :build, s(:sym, :user)]
+ def on_send(node)
+ return unless in_migration_spec?(node)
+ return unless forbidden_factory_usage?(node)
+
+ method = node.children[1]
+
+ add_offense(node, location: :expression, message: MESSAGE % method)
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 0b4c7d31442..406ec95ffc9 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -21,4 +21,5 @@ require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
require_relative 'cop/rspec/env_assignment'
+require_relative 'cop/rspec/factories_in_migration_specs'
require_relative 'cop/sidekiq_options_queue'
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index a702a083958..6c0f0193b1a 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -6,7 +6,18 @@ module RuboCop
def in_spec?(node)
path = node.location.expression.source_buffer.name
- !SPEC_HELPERS.include?(File.basename(path)) && path.start_with?(File.join(Dir.pwd, 'spec'))
+ !SPEC_HELPERS.include?(File.basename(path)) &&
+ path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
+ end
+
+ # Returns true if the given node originated from a migration spec.
+ def in_migration_spec?(node)
+ path = node.location.expression.source_buffer.name
+
+ in_spec?(node) &&
+ path.start_with?(
+ File.join(Dir.pwd, 'spec', 'migrations'),
+ File.join(Dir.pwd, 'ee', 'spec', 'migrations'))
end
end
end
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 97c2c3fb940..3458d679107 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -11,9 +11,11 @@ describe DashboardController do
describe 'GET issues' do
it_behaves_like 'issuables list meta-data', :issue, :issues
+ it_behaves_like 'issuables requiring filter', :issues
end
describe 'GET merge requests' do
it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+ it_behaves_like 'issuables requiring filter', :merge_requests
end
end
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 891485406c6..de6ef919221 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -84,6 +84,28 @@ describe ProfilesController, :request_store do
expect(user.username).to eq(new_username)
end
+ it 'updates a username using JSON request' do
+ sign_in(user)
+
+ put :update_username,
+ user: { username: new_username },
+ format: :json
+
+ expect(response.status).to eq(200)
+ expect(json_response['message']).to eq('Username successfully changed')
+ end
+
+ it 'renders an error message when the username was not updated' do
+ sign_in(user)
+
+ put :update_username,
+ user: { username: 'invalid username.git' },
+ format: :json
+
+ expect(response.status).to eq(422)
+ expect(json_response['message']).to match(/Username change failed/)
+ end
+
it 'raises a correct error when the username is missing' do
sign_in(user)
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 04d16e98913..31b1b52fdd1 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
- get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master'
+ get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
@@ -22,18 +22,25 @@ describe Projects::RepositoriesController do
end
it "uses Gitlab::Workhorse" do
- get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
+ it 'responds with redirect to the short name archive if fully qualified' do
+ get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip"
+
+ expect(assigns(:ref)).to eq("master")
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
+ end
+
context "when the service raises an error" do
before do
allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
end
it "renders Not Found" do
- get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
new file mode 100644
index 00000000000..5fea4a9d5a6
--- /dev/null
+++ b/spec/factories/deploy_tokens.rb
@@ -0,0 +1,14 @@
+FactoryBot.define do
+ factory :deploy_token do
+ token { SecureRandom.hex(50) }
+ sequence(:name) { |n| "PDT #{n}" }
+ read_repository true
+ read_registry true
+ revoked false
+ expires_at { 5.days.from_now }
+
+ trait :revoked do
+ revoked true
+ end
+ end
+end
diff --git a/spec/factories/project_deploy_tokens.rb b/spec/factories/project_deploy_tokens.rb
new file mode 100644
index 00000000000..4866cb58d88
--- /dev/null
+++ b/spec/factories/project_deploy_tokens.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :project_deploy_token do
+ project
+ deploy_token
+ end
+end
diff --git a/spec/factories/users_star_projects.rb b/spec/factories/users_star_projects.rb
new file mode 100644
index 00000000000..6afd08a2084
--- /dev/null
+++ b/spec/factories/users_star_projects.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :users_star_project do
+ project
+ user
+ end
+end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index d673bac4995..fb6c71ce997 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -13,17 +13,26 @@ describe "Dashboard Issues Feed" do
end
describe "atom feed" do
- it "renders atom feed via personal access token" do
+ it "returns 400 if no filter is used" do
personal_access_token = create(:personal_access_token, user: user)
visit issues_dashboard_path(:atom, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
+ expect(page.status_code).to eq(400)
+ end
+
+ it "renders atom feed via personal access token" do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit issues_dashboard_path(:atom, private_token: personal_access_token.token, assignee_id: user.id)
+
+ expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues")
end
it "renders atom feed via RSS token" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id)
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues")
@@ -44,7 +53,7 @@ describe "Dashboard Issues Feed" do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
@@ -67,7 +76,7 @@ describe "Dashboard Issues Feed" do
end
it "renders issue label and milestone info" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index 029fc45c791..bab34ac9346 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -17,6 +17,12 @@ feature 'Dashboard Issues filtering', :js do
visit_issues
end
+ context 'without any filter' do
+ it 'shows error message' do
+ expect(page).to have_content 'Please select at least one filter to see results'
+ end
+ end
+
context 'filtering by milestone' do
it 'shows all issues with no milestone' do
show_milestone_dropdown
@@ -27,15 +33,6 @@ feature 'Dashboard Issues filtering', :js do
expect(page).to have_selector('.issue', count: 1)
end
- it 'shows all issues with any milestone' do
- show_milestone_dropdown
-
- click_link 'Any Milestone'
-
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- expect(page).to have_selector('.issue', count: 2)
- end
-
it 'shows all issues with the selected milestone' do
show_milestone_dropdown
@@ -68,13 +65,6 @@ feature 'Dashboard Issues filtering', :js do
let(:label) { create(:label, project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
- it 'shows all issues without filter' do
- page.within 'ul.content-list' do
- expect(page).to have_content issue.title
- expect(page).to have_content issue2.title
- end
- end
-
it 'shows all issues with the selected label' do
page.within '.labels-filter' do
find('.dropdown').click
@@ -89,9 +79,13 @@ feature 'Dashboard Issues filtering', :js do
end
context 'sorting' do
- it 'shows sorted issues' do
+ before do
+ visit_issues(assignee_id: user.id)
+ end
+
+ it 'remembers last sorting value' do
sort_by('Created date')
- visit_issues
+ visit_issues(assignee_id: user.id)
expect(find('.issues-filters')).to have_content('Created date')
end
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 8d1d5a51750..e41a2e4ce09 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -51,15 +51,6 @@ RSpec.describe 'Dashboard Issues' do
expect(page).not_to have_content(other_issue.title)
end
- it 'shows all issues' do
- click_link('Reset filters')
-
- expect(page).to have_content(authored_issue.title)
- expect(page).to have_content(authored_issue_on_public_project.title)
- expect(page).to have_content(assigned_issue.title)
- expect(page).to have_content(other_issue.title)
- end
-
it 'state filter tabs work' do
find('#state-closed').click
expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true)
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 4a9344115d2..0965b745c03 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -103,15 +103,11 @@ feature 'Dashboard Merge Requests' do
expect(page).not_to have_content(other_merge_request.title)
end
- it 'shows all merge requests', :js do
+ it 'shows error message without filter', :js do
filter_item_select('Any Assignee', '.js-assignee-search')
filter_item_select('Any Author', '.js-author-search')
- expect(page).to have_content(authored_merge_request.title)
- expect(page).to have_content(authored_merge_request_from_fork.title)
- expect(page).to have_content(assigned_merge_request.title)
- expect(page).to have_content(assigned_merge_request_from_fork.title)
- expect(page).to have_content(other_merge_request.title)
+ expect(page).to have_content('Please select at least one filter to see results')
end
it 'shows sorted merge requests' do
diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb
new file mode 100644
index 00000000000..92217294446
--- /dev/null
+++ b/spec/features/groups/settings/group_badges_spec.rb
@@ -0,0 +1,124 @@
+require 'spec_helper'
+
+feature 'Group Badges' do
+ include WaitForRequests
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
+ let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
+ let!(:badge_1) { create(:group_badge, group: group) }
+ let!(:badge_2) { create(:group_badge, group: group) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+
+ visit(group_settings_badges_path(group))
+ end
+
+ it 'shows a list of badges', :js do
+ page.within '.badge-settings' do
+ wait_for_requests
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ expect(rows[0]).to have_content badge_1.link_url
+ expect(rows[1]).to have_content badge_2.link_url
+ end
+ end
+
+ context 'adding a badge', :js do
+ it 'user can preview a badge' do
+ page.within '.badge-settings form' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+ within '#badge-preview' do
+ expect(find('a')[:href]).to eq badge_link_url
+ expect(find('a img')[:src]).to eq badge_image_url
+ end
+ end
+ end
+
+ it do
+ page.within '.badge-settings' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+
+ click_button 'Add badge'
+ wait_for_requests
+
+ within '.panel-body' do
+ expect(find('a')[:href]).to eq badge_link_url
+ expect(find('a img')[:src]).to eq badge_image_url
+ end
+ end
+ end
+ end
+
+ context 'editing a badge', :js do
+ it 'form is shown when clicking edit button in list' do
+ page.within '.badge-settings' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ rows[1].find('[aria-label="Edit"]').click
+
+ within 'form' do
+ expect(find('#badge-link-url').value).to eq badge_2.link_url
+ expect(find('#badge-image-url').value).to eq badge_2.image_url
+ end
+ end
+ end
+
+ it 'updates a badge when submitting the edit form' do
+ page.within '.badge-settings' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ rows[1].find('[aria-label="Edit"]').click
+ within 'form' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+
+ click_button 'Save changes'
+ wait_for_requests
+ end
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ expect(rows[1]).to have_content badge_link_url
+ end
+ end
+ end
+
+ context 'deleting a badge', :js do
+ def click_delete_button(badge_row)
+ badge_row.find('[aria-label="Delete"]').click
+ end
+
+ it 'shows a modal when deleting a badge' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+
+ click_delete_button(rows[1])
+
+ expect(find('.modal .modal-title')).to have_content 'Delete badge?'
+ end
+
+ it 'deletes a badge when confirming the modal' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ click_delete_button(rows[1])
+
+ find('.modal .btn-danger').click
+ wait_for_requests
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 1
+ expect(rows[0]).to have_content badge_1.link_url
+ end
+ end
+end
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index a75ca1d42b3..73022afbda2 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -34,9 +34,6 @@ describe 'New issue', :js do
click_button 'Submit issue'
- # reCAPTCHA alerts when it can't contact the server, so just accept it and move on
- page.driver.browser.switch_to.alert.accept
-
# it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha
# recaptcha verification is skipped in test environment and it always returns true
expect(page).not_to have_content('issue title')
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 99e1fb30d5b..3e05e7b7f38 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -115,17 +115,17 @@ feature 'Labels Hierarchy', :js, :nested_groups do
it 'filters by descendant group labels' do
wait_for_requests
- if board
- pending("Waiting for https://gitlab.com/gitlab-org/gitlab-ce/issues/44270")
+ select_label_on_dropdown(group_label_3.title)
- select_label_on_dropdown(group_label_3.title)
+ if board
+ expect(page).to have_selector('.card-title') do |card|
+ expect(card).not_to have_selector('a', text: labeled_issue_2.title)
+ end
expect(page).to have_selector('.card-title') do |card|
expect(card).to have_selector('a', text: labeled_issue_3.title)
end
else
- select_label_on_dropdown(group_label_3.title)
-
expect_issues_list_count(1)
expect(page).to have_selector('span.issue-title-text', text: labeled_issue_3.title)
end
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index a43ba05c64c..fd1629746ef 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -9,6 +9,7 @@ describe 'Merge request < User sees mini pipeline graph', :js do
before do
build.run
+ build.trace.set('hello')
sign_in(user)
visit_merge_request
end
@@ -26,15 +27,15 @@ describe 'Merge request < User sees mini pipeline graph', :js do
let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
before do
- create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
- create(:ci_build, pipeline: pipeline, when: 'manual')
+ create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
+ create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
it 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
- create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
- create(:ci_build, pipeline: pipeline, when: 'manual')
+ create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
+ create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 0848857ed1e..15dcb30cbdd 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -97,9 +97,13 @@ describe 'Profile account page', :js do
end
it 'changes my username' do
- fill_in 'user_username', with: 'new-username'
+ fill_in 'username-change-input', with: 'new-username'
- click_button('Update username')
+ page.find('[data-target="#username-change-confirmation-modal"]').click
+
+ page.within('.modal') do
+ find('.js-modal-primary-action').click
+ end
expect(page).to have_content('new-username')
end
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index e8eb0d17ca4..215b658eb7b 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Profile > Account' do
+feature 'Profile > Account', :js do
given(:user) { create(:user, username: 'foo') }
before do
@@ -59,6 +59,12 @@ end
def update_username(new_username)
allow(user.namespace).to receive(:move_dir)
visit profile_account_path
- fill_in 'user_username', with: new_username
- click_button 'Update username'
+
+ fill_in 'username-change-input', with: new_username
+
+ page.find('[data-target="#username-change-confirmation-modal"]').click
+
+ page.within('.modal') do
+ find('.js-modal-primary-action').click
+ end
end
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index b7eee39052a..bff5bbe99af 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -1,16 +1,15 @@
require 'spec_helper'
describe 'User browses a job', :js do
- let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) }
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
- let(:project) { create(:project, :repository, namespace: user.namespace) }
let(:user) { create(:user) }
+ let(:user_access_level) { :developer }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) }
before do
project.add_master(user)
project.enable_ci
- build.success
- build.trace.set('job trace')
sign_in(user)
@@ -21,7 +20,9 @@ describe 'User browses a job', :js do
expect(page).to have_content("Job ##{build.id}")
expect(page).to have_css('#build-trace')
- accept_confirm { click_link('Erase') }
+ # scroll to the top of the page first
+ execute_script "window.scrollTo(0,0)"
+ accept_confirm { find('.js-erase-link').click }
expect(page).to have_no_css('.artifacts')
expect(build).not_to have_trace
@@ -36,7 +37,7 @@ describe 'User browses a job', :js do
end
context 'with a failed job' do
- let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
+ let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason' do
within('.builds-container') do
@@ -47,7 +48,7 @@ describe 'User browses a job', :js do
end
context 'when a failed job has been retried' do
- let!(:build) { create(:ci_build, :failed, :retried, pipeline: pipeline) }
+ let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason and retried label' do
within('.builds-container') do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 5d311f2dde3..749a1b81872 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -113,7 +113,7 @@ feature 'Jobs' do
describe "GET /:project/jobs/:id" do
context "Job from project" do
- let(:job) { create(:ci_build, :success, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) }
before do
visit project_job_path(project, job)
@@ -136,7 +136,7 @@ feature 'Jobs' do
end
context 'when job is not running', :js do
- let(:job) { create(:ci_build, :success, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
before do
visit project_job_path(project, job)
@@ -153,7 +153,7 @@ feature 'Jobs' do
end
context 'if job failed' do
- let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
before do
visit project_job_path(project, job)
@@ -339,7 +339,7 @@ feature 'Jobs' do
context 'job is successfull and has deployment' do
let(:deployment) { create(:deployment) }
- let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
it 'shows a link for the job' do
visit project_job_path(project, job)
@@ -349,7 +349,7 @@ feature 'Jobs' do
end
context 'job is complete and not successful' do
- let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
visit project_job_path(project, job)
@@ -360,7 +360,7 @@ feature 'Jobs' do
context 'job creates a new deployment' do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
- let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
visit project_job_path(project, job)
@@ -379,6 +379,7 @@ feature 'Jobs' do
end
it 'shows manual action empty state' do
+ expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job requires a manual action')
expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
expect(page).to have_link('Trigger this manual action')
@@ -402,6 +403,7 @@ feature 'Jobs' do
end
it 'shows empty state' do
+ expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job has not been triggered yet')
expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
end
@@ -415,10 +417,53 @@ feature 'Jobs' do
end
it 'shows pending empty state' do
+ expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job has not started yet')
expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner')
end
end
+
+ context 'Canceled job' do
+ context 'with log' do
+ let(:job) { create(:ci_build, :canceled, :trace_artifact, pipeline: pipeline) }
+
+ before do
+ visit project_job_path(project, job)
+ end
+
+ it 'renders job log' do
+ expect(page).to have_selector('.js-build-output')
+ end
+ end
+
+ context 'without log' do
+ let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
+
+ before do
+ visit project_job_path(project, job)
+ end
+
+ it 'renders empty state' do
+ expect(page).to have_content(job.detailed_status(user).illustration[:title])
+ expect(page).not_to have_selector('.js-build-output')
+ expect(page).to have_content('This job has been canceled')
+ end
+ end
+ end
+
+ context 'Skipped job' do
+ let(:job) { create(:ci_build, :skipped, pipeline: pipeline) }
+
+ before do
+ visit project_job_path(project, job)
+ end
+
+ it 'renders empty state' do
+ expect(page).to have_content(job.detailed_status(user).illustration[:title])
+ expect(page).not_to have_selector('.js-build-output')
+ expect(page).to have_content('This job has been skipped')
+ end
+ end
end
describe "POST /:project/jobs/:id/cancel", :js do
diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb
new file mode 100644
index 00000000000..cc3551a4c21
--- /dev/null
+++ b/spec/features/projects/settings/project_badges_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+feature 'Project Badges' do
+ include WaitForRequests
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
+ let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
+ let!(:project_badge) { create(:project_badge, project: project) }
+ let!(:group_badge) { create(:group_badge, group: group) }
+
+ before do
+ group.add_master(user)
+ sign_in(user)
+
+ visit(project_settings_badges_path(project))
+ end
+
+ it 'shows a list of badges', :js do
+ page.within '.badge-settings' do
+ wait_for_requests
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ expect(rows[0]).to have_content group_badge.link_url
+ expect(rows[1]).to have_content project_badge.link_url
+ end
+ end
+
+ context 'adding a badge', :js do
+ it 'user can preview a badge' do
+ page.within '.badge-settings form' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+ within '#badge-preview' do
+ expect(find('a')[:href]).to eq badge_link_url
+ expect(find('a img')[:src]).to eq badge_image_url
+ end
+ end
+ end
+
+ it do
+ page.within '.badge-settings' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+
+ click_button 'Add badge'
+ wait_for_requests
+
+ within '.panel-body' do
+ expect(find('a')[:href]).to eq badge_link_url
+ expect(find('a img')[:src]).to eq badge_image_url
+ end
+ end
+ end
+ end
+
+ context 'editing a badge', :js do
+ it 'form is shown when clicking edit button in list' do
+ page.within '.badge-settings' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ rows[1].find('[aria-label="Edit"]').click
+
+ within 'form' do
+ expect(find('#badge-link-url').value).to eq project_badge.link_url
+ expect(find('#badge-image-url').value).to eq project_badge.image_url
+ end
+ end
+ end
+
+ it 'updates a badge when submitting the edit form' do
+ page.within '.badge-settings' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ rows[1].find('[aria-label="Edit"]').click
+ within 'form' do
+ fill_in 'badge-link-url', with: badge_link_url
+ fill_in 'badge-image-url', with: badge_image_url
+
+ click_button 'Save changes'
+ wait_for_requests
+ end
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ expect(rows[1]).to have_content badge_link_url
+ end
+ end
+ end
+
+ context 'deleting a badge', :js do
+ def click_delete_button(badge_row)
+ badge_row.find('[aria-label="Delete"]').click
+ end
+
+ it 'shows a modal when deleting a badge' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+
+ click_delete_button(rows[1])
+
+ expect(find('.modal .modal-title')).to have_content 'Delete badge?'
+ end
+
+ it 'deletes a badge when confirming the modal' do
+ wait_for_requests
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 2
+ click_delete_button(rows[1])
+
+ find('.modal .btn-danger').click
+ wait_for_requests
+
+ rows = all('.panel-body > div')
+ expect(rows.length).to eq 1
+ expect(rows[0]).to have_content group_badge.link_url
+ end
+ end
+end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 14670e91006..f2c371b7df5 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -88,5 +88,32 @@ feature 'Repository settings' do
expect(page).not_to have_content(private_deploy_key.title)
end
end
+
+ context 'Deploy tokens' do
+ let!(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ visit project_settings_repository_path(project)
+ end
+
+ scenario 'view deploy tokens' do
+ within('.deploy-tokens') do
+ expect(page).to have_content(deploy_token.name)
+ expect(page).to have_content('read_repository')
+ expect(page).to have_content('read_registry')
+ end
+ end
+
+ scenario 'add a new deploy token' do
+ fill_in 'deploy_token_name', with: 'new_deploy_key'
+ fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s
+ check 'deploy_token_read_repository'
+ check 'deploy_token_read_registry'
+ click_button 'Create deploy token'
+
+ expect(page).to have_content('Your new project deploy token has been created')
+ end
+ end
end
end
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 5ddea36add5..a9128104b87 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -9,49 +9,25 @@ describe 'User uses header search field' do
before do
project.add_reporter(user)
sign_in(user)
-
- visit(project_path(project))
- end
-
- it 'starts searching by pressing the enter key', :js do
- fill_in('search', with: 'gitlab')
- find('#search').native.send_keys(:enter)
-
- page.within('.breadcrumbs-sub-title') do
- expect(page).to have_content('Search')
- end
end
- it 'contains location badge' do
- expect(page).to have_selector('.has-location-badge')
- end
-
- context 'when clicking the search field', :js do
+ context 'when user is in a global scope', :js do
before do
+ visit(root_path)
page.find('#search').click
end
- it 'shows category search dropdown' do
- expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
- end
-
context 'when clicking issues' do
- let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
-
it 'shows assigned issues' do
- find('.dropdown-menu').click_link('Issues assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
+ expect(find('.js-assignee-search')).to have_content(user.name)
end
it 'shows created issues' do
- find('.dropdown-menu').click_link("Issues I've created")
+ find('.search-input-container .dropdown-menu').click_link("Issues I've created")
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
+ expect(find('.js-author-search')).to have_content(user.name)
end
end
@@ -59,32 +35,97 @@ describe 'User uses header search field' do
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
it 'shows assigned merge requests' do
- find('.dropdown-menu').click_link('Merge requests assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
- expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
+ expect(find('.js-assignee-search')).to have_content(user.name)
end
it 'shows created merge requests' do
- find('.dropdown-menu').click_link("Merge requests I've created")
+ find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
- expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
+ expect(find('.js-author-search')).to have_content(user.name)
end
end
end
- context 'when entering text into the search field', :js do
+ context 'when user is in a project scope' do
before do
- page.within('.search-input-wrap') do
- fill_in('search', with: project.name[0..3])
+ visit(project_path(project))
+ end
+
+ it 'starts searching by pressing the enter key', :js do
+ fill_in('search', with: 'gitlab')
+ find('#search').native.send_keys(:enter)
+
+ page.within('.breadcrumbs-sub-title') do
+ expect(page).to have_content('Search')
end
end
- it 'does not display the category search dropdown' do
- expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ it 'contains location badge' do
+ expect(page).to have_selector('.has-location-badge')
+ end
+
+ context 'when clicking the search field', :js do
+ before do
+ page.find('#search').click
+ end
+
+ it 'shows category search dropdown' do
+ expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+ end
+
+ context 'when clicking issues' do
+ let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
+
+ it 'shows assigned issues' do
+ find('.dropdown-menu').click_link('Issues assigned to me')
+
+ expect(page).to have_selector('.filtered-search')
+ expect_tokens([assignee_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+
+ it 'shows created issues' do
+ find('.dropdown-menu').click_link("Issues I've created")
+
+ expect(page).to have_selector('.filtered-search')
+ expect_tokens([author_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+ end
+
+ context 'when clicking merge requests' do
+ let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
+
+ it 'shows assigned merge requests' do
+ find('.dropdown-menu').click_link('Merge requests assigned to me')
+
+ expect(page).to have_selector('.merge-requests-holder')
+ expect_tokens([assignee_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+
+ it 'shows created merge requests' do
+ find('.dropdown-menu').click_link("Merge requests I've created")
+
+ expect(page).to have_selector('.merge-requests-holder')
+ expect_tokens([author_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+ end
+ end
+
+ context 'when entering text into the search field', :js do
+ before do
+ page.within('.search-input-wrap') do
+ fill_in('search', with: project.name[0..3])
+ end
+ end
+
+ it 'does not display the category search dropdown' do
+ expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ end
end
end
end
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index b579e32c9aa..8833825e3fb 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -15,6 +15,8 @@
"relative_position": { "type": "integer" },
"issue_sidebar_endpoint": { "type": "string" },
"toggle_subscription_endpoint": { "type": "string" },
+ "reference_path": { "type": "string" },
+ "real_path": { "type": "string" },
"project": {
"id": { "type": "integer" },
"path": { "type": "string" }
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 2fecd1a3d27..4224cea4652 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -40,22 +40,22 @@ describe IssuablesHelper do
end
it 'returns "Open" when state is :opened' do
- expect(helper.issuables_state_counter_text(:issues, :opened))
+ expect(helper.issuables_state_counter_text(:issues, :opened, true))
.to eq('<span>Open</span> <span class="badge">42</span>')
end
it 'returns "Closed" when state is :closed' do
- expect(helper.issuables_state_counter_text(:issues, :closed))
+ expect(helper.issuables_state_counter_text(:issues, :closed, true))
.to eq('<span>Closed</span> <span class="badge">42</span>')
end
it 'returns "Merged" when state is :merged' do
- expect(helper.issuables_state_counter_text(:merge_requests, :merged))
+ expect(helper.issuables_state_counter_text(:merge_requests, :merged, true))
.to eq('<span>Merged</span> <span class="badge">42</span>')
end
it 'returns "All" when state is :all' do
- expect(helper.issuables_state_counter_text(:merge_requests, :all))
+ expect(helper.issuables_state_counter_text(:merge_requests, :all, true))
.to eq('<span>All</span> <span class="badge">42</span>')
end
end
@@ -101,27 +101,6 @@ describe IssuablesHelper do
end
end
- describe '#issuable_filter_present?' do
- it 'returns true when any key is present' do
- allow(helper).to receive(:params).and_return(
- ActionController::Parameters.new(milestone_title: 'Velit consectetur asperiores natus delectus.',
- project_id: 'gitlabhq',
- scope: 'all')
- )
-
- expect(helper.issuable_filter_present?).to be_truthy
- end
-
- it 'returns false when no key is present' do
- allow(helper).to receive(:params).and_return(
- ActionController::Parameters.new(project_id: 'gitlabhq',
- scope: 'all')
- )
-
- expect(helper.issuable_filter_present?).to be_falsey
- end
- end
-
describe '#updated_at_by' do
let(:user) { create(:user) }
let(:unedited_issuable) { create(:issue) }
diff --git a/spec/javascripts/badges/components/badge_form_spec.js b/spec/javascripts/badges/components/badge_form_spec.js
new file mode 100644
index 00000000000..dd21ec279cb
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_form_spec.js
@@ -0,0 +1,171 @@
+import Vue from 'vue';
+import store from '~/badges/store';
+import BadgeForm from '~/badges/components/badge_form.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeForm component', () => {
+ const Component = Vue.extend(BadgeForm);
+ let vm;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="dummy-element"></div>
+ `);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('methods', () => {
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ props: {
+ isEditing: false,
+ },
+ });
+ });
+
+ describe('onCancel', () => {
+ it('calls stopEditing', () => {
+ spyOn(vm, 'stopEditing');
+
+ vm.onCancel();
+
+ expect(vm.stopEditing).toHaveBeenCalled();
+ });
+ });
+
+ describe('onSubmit', () => {
+ describe('if isEditing is true', () => {
+ beforeEach(() => {
+ spyOn(vm, 'saveBadge').and.returnValue(Promise.resolve());
+ store.replaceState({
+ ...store.state,
+ isSaving: false,
+ badgeInEditForm: createDummyBadge(),
+ });
+ vm.isEditing = true;
+ });
+
+ it('returns immediately if imageUrl is empty', () => {
+ store.state.badgeInEditForm.imageUrl = '';
+
+ vm.onSubmit();
+
+ expect(vm.saveBadge).not.toHaveBeenCalled();
+ });
+
+ it('returns immediately if linkUrl is empty', () => {
+ store.state.badgeInEditForm.linkUrl = '';
+
+ vm.onSubmit();
+
+ expect(vm.saveBadge).not.toHaveBeenCalled();
+ });
+
+ it('returns immediately if isSaving is true', () => {
+ store.state.isSaving = true;
+
+ vm.onSubmit();
+
+ expect(vm.saveBadge).not.toHaveBeenCalled();
+ });
+
+ it('calls saveBadge', () => {
+ vm.onSubmit();
+
+ expect(vm.saveBadge).toHaveBeenCalled();
+ });
+ });
+
+ describe('if isEditing is false', () => {
+ beforeEach(() => {
+ spyOn(vm, 'addBadge').and.returnValue(Promise.resolve());
+ store.replaceState({
+ ...store.state,
+ isSaving: false,
+ badgeInAddForm: createDummyBadge(),
+ });
+ vm.isEditing = false;
+ });
+
+ it('returns immediately if imageUrl is empty', () => {
+ store.state.badgeInAddForm.imageUrl = '';
+
+ vm.onSubmit();
+
+ expect(vm.addBadge).not.toHaveBeenCalled();
+ });
+
+ it('returns immediately if linkUrl is empty', () => {
+ store.state.badgeInAddForm.linkUrl = '';
+
+ vm.onSubmit();
+
+ expect(vm.addBadge).not.toHaveBeenCalled();
+ });
+
+ it('returns immediately if isSaving is true', () => {
+ store.state.isSaving = true;
+
+ vm.onSubmit();
+
+ expect(vm.addBadge).not.toHaveBeenCalled();
+ });
+
+ it('calls addBadge', () => {
+ vm.onSubmit();
+
+ expect(vm.addBadge).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('if isEditing is false', () => {
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ props: {
+ isEditing: false,
+ },
+ });
+ });
+
+ it('renders one button', () => {
+ const buttons = vm.$el.querySelectorAll('.row-content-block button');
+ expect(buttons.length).toBe(1);
+ const buttonAddElement = buttons[0];
+ expect(buttonAddElement).toBeVisible();
+ expect(buttonAddElement).toHaveText('Add badge');
+ });
+ });
+
+ describe('if isEditing is true', () => {
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ props: {
+ isEditing: true,
+ },
+ });
+ });
+
+ it('renders two buttons', () => {
+ const buttons = vm.$el.querySelectorAll('.row-content-block button');
+ expect(buttons.length).toBe(2);
+ const buttonSaveElement = buttons[0];
+ expect(buttonSaveElement).toBeVisible();
+ expect(buttonSaveElement).toHaveText('Save changes');
+ const buttonCancelElement = buttons[1];
+ expect(buttonCancelElement).toBeVisible();
+ expect(buttonCancelElement).toHaveText('Cancel');
+ });
+ });
+});
diff --git a/spec/javascripts/badges/components/badge_list_row_spec.js b/spec/javascripts/badges/components/badge_list_row_spec.js
new file mode 100644
index 00000000000..21bd00d82f0
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_list_row_spec.js
@@ -0,0 +1,97 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import BadgeListRow from '~/badges/components/badge_list_row.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeListRow component', () => {
+ const Component = Vue.extend(BadgeListRow);
+ let badge;
+ let vm;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="delete-badge-modal" class="modal"></div>
+ <div id="dummy-element"></div>
+ `);
+ store.replaceState({
+ ...store.state,
+ kind: PROJECT_BADGE,
+ });
+ badge = createDummyBadge();
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ props: { badge },
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders the badge', () => {
+ const badgeElement = vm.$el.querySelector('.project-badge');
+ expect(badgeElement).not.toBeNull();
+ expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
+ });
+
+ it('renders the badge link', () => {
+ expect(vm.$el).toContainText(badge.linkUrl);
+ });
+
+ it('renders the badge kind', () => {
+ expect(vm.$el).toContainText('Project Badge');
+ });
+
+ it('shows edit and delete buttons', () => {
+ const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+ expect(buttons).toHaveLength(2);
+ const buttonEditElement = buttons[0];
+ expect(buttonEditElement).toBeVisible();
+ expect(buttonEditElement).toHaveSpriteIcon('pencil');
+ const buttonDeleteElement = buttons[1];
+ expect(buttonDeleteElement).toBeVisible();
+ expect(buttonDeleteElement).toHaveSpriteIcon('remove');
+ });
+
+ it('calls editBadge when clicking then edit button', () => {
+ spyOn(vm, 'editBadge');
+
+ const editButton = vm.$el.querySelector('.table-button-footer button:first-of-type');
+ editButton.click();
+
+ expect(vm.editBadge).toHaveBeenCalled();
+ });
+
+ it('calls updateBadgeInModal and shows modal when clicking then delete button', done => {
+ spyOn(vm, 'updateBadgeInModal');
+ $('#delete-badge-modal').on('shown.bs.modal', () => done());
+
+ const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
+ deleteButton.click();
+
+ expect(vm.updateBadgeInModal).toHaveBeenCalled();
+ });
+
+ describe('for a group badge', () => {
+ beforeEach(done => {
+ badge.kind = GROUP_BADGE;
+
+ Vue.nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders the badge kind', () => {
+ expect(vm.$el).toContainText('Group Badge');
+ });
+
+ it('hides edit and delete buttons', () => {
+ const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+ expect(buttons).toHaveLength(0);
+ });
+ });
+});
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
new file mode 100644
index 00000000000..9439c578973
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -0,0 +1,88 @@
+import Vue from 'vue';
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import BadgeList from '~/badges/components/badge_list.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeList component', () => {
+ const Component = Vue.extend(BadgeList);
+ const numberOfDummyBadges = 3;
+ let vm;
+
+ beforeEach(() => {
+ setFixtures('<div id="dummy-element"></div>');
+ const badges = [];
+ for (let id = 0; id < numberOfDummyBadges; id += 1) {
+ badges.push({ id, ...createDummyBadge() });
+ }
+ store.replaceState({
+ ...store.state,
+ badges,
+ kind: PROJECT_BADGE,
+ isLoading: false,
+ });
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a header with the badge count', () => {
+ const header = vm.$el.querySelector('.panel-heading');
+ expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`));
+ });
+
+ it('renders a row for each badge', () => {
+ const rows = vm.$el.querySelectorAll('.gl-responsive-table-row');
+ expect(rows).toHaveLength(numberOfDummyBadges);
+ });
+
+ it('renders a message if no badges exist', done => {
+ store.state.badges = [];
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el).toContainText('This project has no badges');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows a loading icon when loading', done => {
+ store.state.isLoading = true;
+
+ Vue.nextTick()
+ .then(() => {
+ const loadingIcon = vm.$el.querySelector('.fa-spinner');
+ expect(loadingIcon).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('for group badges', () => {
+ beforeEach(done => {
+ store.state.kind = GROUP_BADGE;
+
+ Vue.nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders a message if no badges exist', done => {
+ store.state.badges = [];
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el).toContainText('This group has no badges');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/badges/components/badge_settings_spec.js b/spec/javascripts/badges/components/badge_settings_spec.js
new file mode 100644
index 00000000000..3db02982ad4
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_settings_spec.js
@@ -0,0 +1,109 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import store from '~/badges/store';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('BadgeSettings component', () => {
+ const Component = Vue.extend(BadgeSettings);
+ let vm;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="dummy-element"></div>
+ <button
+ id="dummy-modal-button"
+ type="button"
+ data-toggle="modal"
+ data-target="#delete-badge-modal"
+ >Show modal</button>
+ `);
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('displays modal if button is clicked', done => {
+ const badge = createDummyBadge();
+ store.state.badgeInModal = badge;
+ const modal = vm.$el.querySelector('#delete-badge-modal');
+ const button = document.getElementById('dummy-modal-button');
+
+ $(modal).on('shown.bs.modal', () => {
+ expect(modal).toContainText('Delete badge?');
+ const badgeElement = modal.querySelector('img.project-badge');
+ expect(badgeElement).not.toBe(null);
+ expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
+
+ done();
+ });
+
+ Vue.nextTick()
+ .then(() => {
+ button.click();
+ })
+ .catch(done.fail);
+ });
+
+ it('displays a form to add a badge', () => {
+ const form = vm.$el.querySelector('form:nth-of-type(2)');
+ expect(form).not.toBe(null);
+ const button = form.querySelector('.btn-success');
+ expect(button).not.toBe(null);
+ expect(button).toHaveText(/Add badge/);
+ });
+
+ it('displays badge list', () => {
+ const badgeListElement = vm.$el.querySelector('.panel');
+ expect(badgeListElement).not.toBe(null);
+ expect(badgeListElement).toBeVisible();
+ expect(badgeListElement).toContainText('Your badges');
+ });
+
+ describe('when editing', () => {
+ beforeEach(done => {
+ store.state.isEditing = true;
+
+ Vue.nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays a form to edit a badge', () => {
+ const form = vm.$el.querySelector('form:nth-of-type(1)');
+ expect(form).not.toBe(null);
+ const submitButton = form.querySelector('.btn-success');
+ expect(submitButton).not.toBe(null);
+ expect(submitButton).toHaveText(/Save changes/);
+ const cancelButton = form.querySelector('.btn-cancel');
+ expect(cancelButton).not.toBe(null);
+ expect(cancelButton).toHaveText(/Cancel/);
+ });
+
+ it('displays no badge list', () => {
+ const badgeListElement = vm.$el.querySelector('.panel');
+ expect(badgeListElement).toBeHidden();
+ });
+ });
+
+ describe('methods', () => {
+ describe('onSubmitModal', () => {
+ it('triggers ', () => {
+ spyOn(vm, 'deleteBadge').and.callFake(() => Promise.resolve());
+ const modal = vm.$el.querySelector('#delete-badge-modal');
+ const deleteButton = modal.querySelector('.btn-danger');
+
+ deleteButton.click();
+
+ const badge = store.state.badgeInModal;
+ expect(vm.deleteBadge).toHaveBeenCalledWith(badge);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
new file mode 100644
index 00000000000..fd1ecc9cdd8
--- /dev/null
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -0,0 +1,147 @@
+import Vue from 'vue';
+import Badge from '~/badges/components/badge.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
+
+describe('Badge component', () => {
+ const Component = Vue.extend(Badge);
+ const dummyProps = {
+ imageUrl: DUMMY_IMAGE_URL,
+ linkUrl: `${TEST_HOST}/badge/link/url`,
+ };
+ let vm;
+
+ const findElements = () => {
+ const buttons = vm.$el.querySelectorAll('button');
+ return {
+ badgeImage: vm.$el.querySelector('img.project-badge'),
+ loadingIcon: vm.$el.querySelector('.fa-spinner'),
+ reloadButton: buttons[buttons.length - 1],
+ };
+ };
+
+ const createComponent = (props, el = null) => {
+ vm = mountComponent(Component, props, el);
+ const { badgeImage } = findElements();
+ return new Promise(resolve => badgeImage.addEventListener('load', resolve)).then(() =>
+ Vue.nextTick(),
+ );
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('watchers', () => {
+ describe('imageUrl', () => {
+ it('sets isLoading and resets numRetries and hasError', done => {
+ const props = { ...dummyProps };
+ createComponent(props)
+ .then(() => {
+ expect(vm.isLoading).toBe(false);
+ vm.hasError = true;
+ vm.numRetries = 42;
+
+ vm.imageUrl = `${props.imageUrl}#something/else`;
+
+ return Vue.nextTick();
+ })
+ .then(() => {
+ expect(vm.isLoading).toBe(true);
+ expect(vm.numRetries).toBe(0);
+ expect(vm.hasError).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ beforeEach(done => {
+ createComponent({ ...dummyProps })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('onError resets isLoading and sets hasError', () => {
+ vm.hasError = false;
+ vm.isLoading = true;
+
+ vm.onError();
+
+ expect(vm.hasError).toBe(true);
+ expect(vm.isLoading).toBe(false);
+ });
+
+ it('onLoad sets isLoading', () => {
+ vm.isLoading = true;
+
+ vm.onLoad();
+
+ expect(vm.isLoading).toBe(false);
+ });
+
+ it('reloadImage resets isLoading and hasError and increases numRetries', () => {
+ vm.hasError = true;
+ vm.isLoading = false;
+ vm.numRetries = 0;
+
+ vm.reloadImage();
+
+ expect(vm.hasError).toBe(false);
+ expect(vm.isLoading).toBe(true);
+ expect(vm.numRetries).toBe(1);
+ });
+ });
+
+ describe('behavior', () => {
+ beforeEach(done => {
+ setFixtures('<div id="dummy-element"></div>');
+ createComponent({ ...dummyProps }, '#dummy-element')
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows a badge image after loading', () => {
+ expect(vm.isLoading).toBe(false);
+ expect(vm.hasError).toBe(false);
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
+ expect(badgeImage).toBeVisible();
+ expect(loadingIcon).toBeHidden();
+ expect(reloadButton).toBeHidden();
+ expect(vm.$el.innerText).toBe('');
+ });
+
+ it('shows a loading icon when loading', done => {
+ vm.isLoading = true;
+
+ Vue.nextTick()
+ .then(() => {
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
+ expect(badgeImage).toBeHidden();
+ expect(loadingIcon).toBeVisible();
+ expect(reloadButton).toBeHidden();
+ expect(vm.$el.innerText).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows an error and reload button if loading failed', done => {
+ vm.hasError = true;
+
+ Vue.nextTick()
+ .then(() => {
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
+ expect(badgeImage).toBeHidden();
+ expect(loadingIcon).toBeHidden();
+ expect(reloadButton).toBeVisible();
+ expect(reloadButton).toHaveSpriteIcon('retry');
+ expect(vm.$el.innerText.trim()).toBe('No badge image');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/badges/dummy_badge.js b/spec/javascripts/badges/dummy_badge.js
new file mode 100644
index 00000000000..6aaff21c503
--- /dev/null
+++ b/spec/javascripts/badges/dummy_badge.js
@@ -0,0 +1,23 @@
+import { PROJECT_BADGE } from '~/badges/constants';
+import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
+
+export const createDummyBadge = () => {
+ const id = Math.floor(1000 * Math.random());
+ return {
+ id,
+ imageUrl: `${TEST_HOST}/badges/${id}/image/url`,
+ isDeleting: false,
+ linkUrl: `${TEST_HOST}/badges/${id}/link/url`,
+ kind: PROJECT_BADGE,
+ renderedImageUrl: `${DUMMY_IMAGE_URL}?id=${id}`,
+ renderedLinkUrl: `${TEST_HOST}/badges/${id}/rendered/link/url`,
+ };
+};
+
+export const createDummyBadgeResponse = () => ({
+ image_url: `${TEST_HOST}/badge/image/url`,
+ link_url: `${TEST_HOST}/badge/link/url`,
+ kind: PROJECT_BADGE,
+ rendered_image_url: DUMMY_IMAGE_URL,
+ rendered_link_url: `${TEST_HOST}/rendered/badge/link/url`,
+});
diff --git a/spec/javascripts/badges/store/actions_spec.js b/spec/javascripts/badges/store/actions_spec.js
new file mode 100644
index 00000000000..bb6263c6de4
--- /dev/null
+++ b/spec/javascripts/badges/store/actions_spec.js
@@ -0,0 +1,607 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import actions, { transformBackendBadge } from '~/badges/store/actions';
+import mutationTypes from '~/badges/store/mutation_types';
+import createState from '~/badges/store/state';
+import { TEST_HOST } from 'spec/test_constants';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { createDummyBadge, createDummyBadgeResponse } from '../dummy_badge';
+
+describe('Badges store actions', () => {
+ const dummyEndpointUrl = `${TEST_HOST}/badges/endpoint`;
+ const dummyBadges = [{ ...createDummyBadge(), id: 5 }, { ...createDummyBadge(), id: 6 }];
+
+ let axiosMock;
+ let badgeId;
+ let state;
+
+ beforeEach(() => {
+ axiosMock = new MockAdapter(axios);
+ state = {
+ ...createState(),
+ apiEndpointUrl: dummyEndpointUrl,
+ badges: dummyBadges,
+ };
+ badgeId = state.badges[0].id;
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ });
+
+ describe('requestNewBadge', () => {
+ it('commits REQUEST_NEW_BADGE', done => {
+ testAction(
+ actions.requestNewBadge,
+ null,
+ state,
+ [{ type: mutationTypes.REQUEST_NEW_BADGE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveNewBadge', () => {
+ it('commits RECEIVE_NEW_BADGE', done => {
+ const newBadge = createDummyBadge();
+ testAction(
+ actions.receiveNewBadge,
+ newBadge,
+ state,
+ [{ type: mutationTypes.RECEIVE_NEW_BADGE, payload: newBadge }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveNewBadgeError', () => {
+ it('commits RECEIVE_NEW_BADGE_ERROR', done => {
+ testAction(
+ actions.receiveNewBadgeError,
+ null,
+ state,
+ [{ type: mutationTypes.RECEIVE_NEW_BADGE_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('addBadge', () => {
+ let badgeInAddForm;
+ let dispatch;
+ let endpointMock;
+
+ beforeEach(() => {
+ endpointMock = axiosMock.onPost(dummyEndpointUrl);
+ dispatch = jasmine.createSpy('dispatch');
+ badgeInAddForm = createDummyBadge();
+ state = {
+ ...state,
+ badgeInAddForm,
+ };
+ });
+
+ it('dispatches requestNewBadge and receiveNewBadge for successful response', done => {
+ const dummyResponse = createDummyBadgeResponse();
+
+ endpointMock.replyOnce(req => {
+ expect(req.data).toBe(
+ JSON.stringify({
+ image_url: badgeInAddForm.imageUrl,
+ link_url: badgeInAddForm.linkUrl,
+ }),
+ );
+ expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
+ dispatch.calls.reset();
+ return [200, dummyResponse];
+ });
+
+ const dummyBadge = transformBackendBadge(dummyResponse);
+ actions
+ .addBadge({ state, dispatch })
+ .then(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadge', dummyBadge]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestNewBadge and receiveNewBadgeError for error response', done => {
+ endpointMock.replyOnce(req => {
+ expect(req.data).toBe(
+ JSON.stringify({
+ image_url: badgeInAddForm.imageUrl,
+ link_url: badgeInAddForm.linkUrl,
+ }),
+ );
+ expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
+ dispatch.calls.reset();
+ return [500, ''];
+ });
+
+ actions
+ .addBadge({ state, dispatch })
+ .then(() => done.fail('Expected Ajax call to fail!'))
+ .catch(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadgeError']]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('requestDeleteBadge', () => {
+ it('commits REQUEST_DELETE_BADGE', done => {
+ testAction(
+ actions.requestDeleteBadge,
+ badgeId,
+ state,
+ [{ type: mutationTypes.REQUEST_DELETE_BADGE, payload: badgeId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveDeleteBadge', () => {
+ it('commits RECEIVE_DELETE_BADGE', done => {
+ testAction(
+ actions.receiveDeleteBadge,
+ badgeId,
+ state,
+ [{ type: mutationTypes.RECEIVE_DELETE_BADGE, payload: badgeId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveDeleteBadgeError', () => {
+ it('commits RECEIVE_DELETE_BADGE_ERROR', done => {
+ testAction(
+ actions.receiveDeleteBadgeError,
+ badgeId,
+ state,
+ [{ type: mutationTypes.RECEIVE_DELETE_BADGE_ERROR, payload: badgeId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('deleteBadge', () => {
+ let dispatch;
+ let endpointMock;
+
+ beforeEach(() => {
+ endpointMock = axiosMock.onDelete(`${dummyEndpointUrl}/${badgeId}`);
+ dispatch = jasmine.createSpy('dispatch');
+ });
+
+ it('dispatches requestDeleteBadge and receiveDeleteBadge for successful response', done => {
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
+ dispatch.calls.reset();
+ return [200, ''];
+ });
+
+ actions
+ .deleteBadge({ state, dispatch }, { id: badgeId })
+ .then(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadge', badgeId]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestDeleteBadge and receiveDeleteBadgeError for error response', done => {
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
+ dispatch.calls.reset();
+ return [500, ''];
+ });
+
+ actions
+ .deleteBadge({ state, dispatch }, { id: badgeId })
+ .then(() => done.fail('Expected Ajax call to fail!'))
+ .catch(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadgeError', badgeId]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('editBadge', () => {
+ it('commits START_EDITING', done => {
+ const dummyBadge = createDummyBadge();
+ testAction(
+ actions.editBadge,
+ dummyBadge,
+ state,
+ [{ type: mutationTypes.START_EDITING, payload: dummyBadge }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestLoadBadges', () => {
+ it('commits REQUEST_LOAD_BADGES', done => {
+ const dummyData = 'this is not real data';
+ testAction(
+ actions.requestLoadBadges,
+ dummyData,
+ state,
+ [{ type: mutationTypes.REQUEST_LOAD_BADGES, payload: dummyData }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveLoadBadges', () => {
+ it('commits RECEIVE_LOAD_BADGES', done => {
+ const badges = dummyBadges;
+ testAction(
+ actions.receiveLoadBadges,
+ badges,
+ state,
+ [{ type: mutationTypes.RECEIVE_LOAD_BADGES, payload: badges }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveLoadBadgesError', () => {
+ it('commits RECEIVE_LOAD_BADGES_ERROR', done => {
+ testAction(
+ actions.receiveLoadBadgesError,
+ null,
+ state,
+ [{ type: mutationTypes.RECEIVE_LOAD_BADGES_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('loadBadges', () => {
+ let dispatch;
+ let endpointMock;
+
+ beforeEach(() => {
+ endpointMock = axiosMock.onGet(dummyEndpointUrl);
+ dispatch = jasmine.createSpy('dispatch');
+ });
+
+ it('dispatches requestLoadBadges and receiveLoadBadges for successful response', done => {
+ const dummyData = 'this is just some data';
+ const dummyReponse = [
+ createDummyBadgeResponse(),
+ createDummyBadgeResponse(),
+ createDummyBadgeResponse(),
+ ];
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
+ dispatch.calls.reset();
+ return [200, dummyReponse];
+ });
+
+ actions
+ .loadBadges({ state, dispatch }, dummyData)
+ .then(() => {
+ const badges = dummyReponse.map(transformBackendBadge);
+ expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadges', badges]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestLoadBadges and receiveLoadBadgesError for error response', done => {
+ const dummyData = 'this is just some data';
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
+ dispatch.calls.reset();
+ return [500, ''];
+ });
+
+ actions
+ .loadBadges({ state, dispatch }, dummyData)
+ .then(() => done.fail('Expected Ajax call to fail!'))
+ .catch(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadgesError']]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('requestRenderedBadge', () => {
+ it('commits REQUEST_RENDERED_BADGE', done => {
+ testAction(
+ actions.requestRenderedBadge,
+ null,
+ state,
+ [{ type: mutationTypes.REQUEST_RENDERED_BADGE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveRenderedBadge', () => {
+ it('commits RECEIVE_RENDERED_BADGE', done => {
+ const dummyBadge = createDummyBadge();
+ testAction(
+ actions.receiveRenderedBadge,
+ dummyBadge,
+ state,
+ [{ type: mutationTypes.RECEIVE_RENDERED_BADGE, payload: dummyBadge }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveRenderedBadgeError', () => {
+ it('commits RECEIVE_RENDERED_BADGE_ERROR', done => {
+ testAction(
+ actions.receiveRenderedBadgeError,
+ null,
+ state,
+ [{ type: mutationTypes.RECEIVE_RENDERED_BADGE_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('renderBadge', () => {
+ let dispatch;
+ let endpointMock;
+ let badgeInForm;
+
+ beforeEach(() => {
+ badgeInForm = createDummyBadge();
+ state = {
+ ...state,
+ badgeInAddForm: badgeInForm,
+ };
+ const urlParameters = [
+ `link_url=${encodeURIComponent(badgeInForm.linkUrl)}`,
+ `image_url=${encodeURIComponent(badgeInForm.imageUrl)}`,
+ ].join('&');
+ endpointMock = axiosMock.onGet(`${dummyEndpointUrl}/render?${urlParameters}`);
+ dispatch = jasmine.createSpy('dispatch');
+ });
+
+ it('returns immediately if imageUrl is empty', done => {
+ spyOn(axios, 'get');
+ badgeInForm.imageUrl = '';
+
+ actions
+ .renderBadge({ state, dispatch })
+ .then(() => {
+ expect(axios.get).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('returns immediately if linkUrl is empty', done => {
+ spyOn(axios, 'get');
+ badgeInForm.linkUrl = '';
+
+ actions
+ .renderBadge({ state, dispatch })
+ .then(() => {
+ expect(axios.get).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('escapes user input', done => {
+ spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() }));
+ badgeInForm.imageUrl = '&make-sandwhich=true';
+ badgeInForm.linkUrl = '<script>I am dangerous!</script>';
+
+ actions
+ .renderBadge({ state, dispatch })
+ .then(() => {
+ expect(axios.get.calls.count()).toBe(1);
+ const url = axios.get.calls.argsFor(0)[0];
+ expect(url).toMatch(`^${dummyEndpointUrl}/render?`);
+ expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&');
+ expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestRenderedBadge and receiveRenderedBadge for successful response', done => {
+ const dummyReponse = createDummyBadgeResponse();
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
+ dispatch.calls.reset();
+ return [200, dummyReponse];
+ });
+
+ actions
+ .renderBadge({ state, dispatch })
+ .then(() => {
+ const renderedBadge = transformBackendBadge(dummyReponse);
+ expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadge', renderedBadge]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestRenderedBadge and receiveRenderedBadgeError for error response', done => {
+ endpointMock.replyOnce(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
+ dispatch.calls.reset();
+ return [500, ''];
+ });
+
+ actions
+ .renderBadge({ state, dispatch })
+ .then(() => done.fail('Expected Ajax call to fail!'))
+ .catch(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadgeError']]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('requestUpdatedBadge', () => {
+ it('commits REQUEST_UPDATED_BADGE', done => {
+ testAction(
+ actions.requestUpdatedBadge,
+ null,
+ state,
+ [{ type: mutationTypes.REQUEST_UPDATED_BADGE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveUpdatedBadge', () => {
+ it('commits RECEIVE_UPDATED_BADGE', done => {
+ const updatedBadge = createDummyBadge();
+ testAction(
+ actions.receiveUpdatedBadge,
+ updatedBadge,
+ state,
+ [{ type: mutationTypes.RECEIVE_UPDATED_BADGE, payload: updatedBadge }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveUpdatedBadgeError', () => {
+ it('commits RECEIVE_UPDATED_BADGE_ERROR', done => {
+ testAction(
+ actions.receiveUpdatedBadgeError,
+ null,
+ state,
+ [{ type: mutationTypes.RECEIVE_UPDATED_BADGE_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('saveBadge', () => {
+ let badgeInEditForm;
+ let dispatch;
+ let endpointMock;
+
+ beforeEach(() => {
+ badgeInEditForm = createDummyBadge();
+ state = {
+ ...state,
+ badgeInEditForm,
+ };
+ endpointMock = axiosMock.onPut(`${dummyEndpointUrl}/${badgeInEditForm.id}`);
+ dispatch = jasmine.createSpy('dispatch');
+ });
+
+ it('dispatches requestUpdatedBadge and receiveUpdatedBadge for successful response', done => {
+ const dummyResponse = createDummyBadgeResponse();
+
+ endpointMock.replyOnce(req => {
+ expect(req.data).toBe(
+ JSON.stringify({
+ image_url: badgeInEditForm.imageUrl,
+ link_url: badgeInEditForm.linkUrl,
+ }),
+ );
+ expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
+ dispatch.calls.reset();
+ return [200, dummyResponse];
+ });
+
+ const updatedBadge = transformBackendBadge(dummyResponse);
+ actions
+ .saveBadge({ state, dispatch })
+ .then(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadge', updatedBadge]]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('dispatches requestUpdatedBadge and receiveUpdatedBadgeError for error response', done => {
+ endpointMock.replyOnce(req => {
+ expect(req.data).toBe(
+ JSON.stringify({
+ image_url: badgeInEditForm.imageUrl,
+ link_url: badgeInEditForm.linkUrl,
+ }),
+ );
+ expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
+ dispatch.calls.reset();
+ return [500, ''];
+ });
+
+ actions
+ .saveBadge({ state, dispatch })
+ .then(() => done.fail('Expected Ajax call to fail!'))
+ .catch(() => {
+ expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadgeError']]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('stopEditing', () => {
+ it('commits STOP_EDITING', done => {
+ testAction(
+ actions.stopEditing,
+ null,
+ state,
+ [{ type: mutationTypes.STOP_EDITING }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('updateBadgeInForm', () => {
+ it('commits UPDATE_BADGE_IN_FORM', done => {
+ const dummyBadge = createDummyBadge();
+ testAction(
+ actions.updateBadgeInForm,
+ dummyBadge,
+ state,
+ [{ type: mutationTypes.UPDATE_BADGE_IN_FORM, payload: dummyBadge }],
+ [],
+ done,
+ );
+ });
+
+ describe('updateBadgeInModal', () => {
+ it('commits UPDATE_BADGE_IN_MODAL', done => {
+ const dummyBadge = createDummyBadge();
+ testAction(
+ actions.updateBadgeInModal,
+ dummyBadge,
+ state,
+ [{ type: mutationTypes.UPDATE_BADGE_IN_MODAL, payload: dummyBadge }],
+ [],
+ done,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/badges/store/mutations_spec.js b/spec/javascripts/badges/store/mutations_spec.js
new file mode 100644
index 00000000000..8d26f83339d
--- /dev/null
+++ b/spec/javascripts/badges/store/mutations_spec.js
@@ -0,0 +1,418 @@
+import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
+import store from '~/badges/store';
+import types from '~/badges/store/mutation_types';
+import createState from '~/badges/store/state';
+import { createDummyBadge } from '../dummy_badge';
+
+describe('Badges store mutations', () => {
+ let dummyBadge;
+
+ beforeEach(() => {
+ dummyBadge = createDummyBadge();
+ store.replaceState(createState());
+ });
+
+ describe('RECEIVE_DELETE_BADGE', () => {
+ beforeEach(() => {
+ const badges = [
+ { ...dummyBadge, id: dummyBadge.id - 1 },
+ dummyBadge,
+ { ...dummyBadge, id: dummyBadge.id + 1 },
+ ];
+
+ store.replaceState({
+ ...store.state,
+ badges,
+ });
+ });
+
+ it('removes deleted badge', () => {
+ const badgeCount = store.state.badges.length;
+
+ store.commit(types.RECEIVE_DELETE_BADGE, dummyBadge.id);
+
+ expect(store.state.badges.length).toBe(badgeCount - 1);
+ expect(store.state.badges.indexOf(dummyBadge)).toBe(-1);
+ });
+ });
+
+ describe('RECEIVE_DELETE_BADGE_ERROR', () => {
+ beforeEach(() => {
+ const badges = [
+ { ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
+ { ...dummyBadge, isDeleting: true },
+ { ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
+ ];
+
+ store.replaceState({
+ ...store.state,
+ badges,
+ });
+ });
+
+ it('sets isDeleting to false', () => {
+ const badgeCount = store.state.badges.length;
+
+ store.commit(types.RECEIVE_DELETE_BADGE_ERROR, dummyBadge.id);
+
+ expect(store.state.badges.length).toBe(badgeCount);
+ expect(store.state.badges[0].isDeleting).toBe(false);
+ expect(store.state.badges[1].isDeleting).toBe(false);
+ expect(store.state.badges[2].isDeleting).toBe(true);
+ });
+ });
+
+ describe('RECEIVE_LOAD_BADGES', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isLoading: 'not false',
+ });
+ });
+
+ it('sets badges and isLoading to false', () => {
+ const badges = [createDummyBadge()];
+ store.commit(types.RECEIVE_LOAD_BADGES, badges);
+
+ expect(store.state.isLoading).toBe(false);
+ expect(store.state.badges).toBe(badges);
+ });
+ });
+
+ describe('RECEIVE_LOAD_BADGES_ERROR', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isLoading: 'not false',
+ });
+ });
+
+ it('sets isLoading to false', () => {
+ store.commit(types.RECEIVE_LOAD_BADGES_ERROR);
+
+ expect(store.state.isLoading).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_NEW_BADGE', () => {
+ beforeEach(() => {
+ const badges = [
+ { ...dummyBadge, id: dummyBadge.id - 1, kind: GROUP_BADGE },
+ { ...dummyBadge, id: dummyBadge.id + 1, kind: GROUP_BADGE },
+ { ...dummyBadge, id: dummyBadge.id - 1, kind: PROJECT_BADGE },
+ { ...dummyBadge, id: dummyBadge.id + 1, kind: PROJECT_BADGE },
+ ];
+ store.replaceState({
+ ...store.state,
+ badgeInAddForm: createDummyBadge(),
+ badges,
+ isSaving: 'dummy value',
+ renderedBadge: createDummyBadge(),
+ });
+ });
+
+ it('resets the add form', () => {
+ store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+ expect(store.state.badgeInAddForm).toBe(null);
+ expect(store.state.isSaving).toBe(false);
+ expect(store.state.renderedBadge).toBe(null);
+ });
+
+ it('inserts group badge at correct position', () => {
+ const badgeCount = store.state.badges.length;
+ dummyBadge = { ...dummyBadge, kind: GROUP_BADGE };
+
+ store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+ expect(store.state.badges.length).toBe(badgeCount + 1);
+ expect(store.state.badges.indexOf(dummyBadge)).toBe(1);
+ });
+
+ it('inserts project badge at correct position', () => {
+ const badgeCount = store.state.badges.length;
+ dummyBadge = { ...dummyBadge, kind: PROJECT_BADGE };
+
+ store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
+
+ expect(store.state.badges.length).toBe(badgeCount + 1);
+ expect(store.state.badges.indexOf(dummyBadge)).toBe(3);
+ });
+ });
+
+ describe('RECEIVE_NEW_BADGE_ERROR', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isSaving: 'dummy value',
+ });
+ });
+
+ it('sets isSaving to false', () => {
+ store.commit(types.RECEIVE_NEW_BADGE_ERROR);
+
+ expect(store.state.isSaving).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_RENDERED_BADGE', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isRendering: 'dummy value',
+ renderedBadge: 'dummy value',
+ });
+ });
+
+ it('sets renderedBadge', () => {
+ store.commit(types.RECEIVE_RENDERED_BADGE, dummyBadge);
+
+ expect(store.state.isRendering).toBe(false);
+ expect(store.state.renderedBadge).toBe(dummyBadge);
+ });
+ });
+
+ describe('RECEIVE_RENDERED_BADGE_ERROR', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isRendering: 'dummy value',
+ });
+ });
+
+ it('sets isRendering to false', () => {
+ store.commit(types.RECEIVE_RENDERED_BADGE_ERROR);
+
+ expect(store.state.isRendering).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_UPDATED_BADGE', () => {
+ beforeEach(() => {
+ const badges = [
+ { ...dummyBadge, id: dummyBadge.id - 1 },
+ dummyBadge,
+ { ...dummyBadge, id: dummyBadge.id + 1 },
+ ];
+ store.replaceState({
+ ...store.state,
+ badgeInEditForm: createDummyBadge(),
+ badges,
+ isEditing: 'dummy value',
+ isSaving: 'dummy value',
+ renderedBadge: createDummyBadge(),
+ });
+ });
+
+ it('resets the edit form', () => {
+ store.commit(types.RECEIVE_UPDATED_BADGE, dummyBadge);
+
+ expect(store.state.badgeInAddForm).toBe(null);
+ expect(store.state.isSaving).toBe(false);
+ expect(store.state.renderedBadge).toBe(null);
+ });
+
+ it('replaces the updated badge', () => {
+ const badgeCount = store.state.badges.length;
+ const badgeIndex = store.state.badges.indexOf(dummyBadge);
+ const newBadge = { id: dummyBadge.id, dummy: 'value' };
+
+ store.commit(types.RECEIVE_UPDATED_BADGE, newBadge);
+
+ expect(store.state.badges.length).toBe(badgeCount);
+ expect(store.state.badges[badgeIndex]).toBe(newBadge);
+ });
+ });
+
+ describe('RECEIVE_UPDATED_BADGE_ERROR', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isSaving: 'dummy value',
+ });
+ });
+
+ it('sets isSaving to false', () => {
+ store.commit(types.RECEIVE_NEW_BADGE_ERROR);
+
+ expect(store.state.isSaving).toBe(false);
+ });
+ });
+
+ describe('REQUEST_DELETE_BADGE', () => {
+ beforeEach(() => {
+ const badges = [
+ { ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
+ { ...dummyBadge, isDeleting: false },
+ { ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
+ ];
+
+ store.replaceState({
+ ...store.state,
+ badges,
+ });
+ });
+
+ it('sets isDeleting to true', () => {
+ const badgeCount = store.state.badges.length;
+
+ store.commit(types.REQUEST_DELETE_BADGE, dummyBadge.id);
+
+ expect(store.state.badges.length).toBe(badgeCount);
+ expect(store.state.badges[0].isDeleting).toBe(false);
+ expect(store.state.badges[1].isDeleting).toBe(true);
+ expect(store.state.badges[2].isDeleting).toBe(true);
+ });
+ });
+
+ describe('REQUEST_LOAD_BADGES', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ apiEndpointUrl: 'some endpoint',
+ docsUrl: 'some url',
+ isLoading: 'dummy value',
+ kind: 'some kind',
+ });
+ });
+
+ it('sets isLoading to true and initializes the store', () => {
+ const dummyData = {
+ apiEndpointUrl: 'dummy endpoint',
+ docsUrl: 'dummy url',
+ kind: 'dummy kind',
+ };
+
+ store.commit(types.REQUEST_LOAD_BADGES, dummyData);
+
+ expect(store.state.isLoading).toBe(true);
+ expect(store.state.apiEndpointUrl).toBe(dummyData.apiEndpointUrl);
+ expect(store.state.docsUrl).toBe(dummyData.docsUrl);
+ expect(store.state.kind).toBe(dummyData.kind);
+ });
+ });
+
+ describe('REQUEST_NEW_BADGE', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isSaving: 'dummy value',
+ });
+ });
+
+ it('sets isSaving to true', () => {
+ store.commit(types.REQUEST_NEW_BADGE);
+
+ expect(store.state.isSaving).toBe(true);
+ });
+ });
+
+ describe('REQUEST_RENDERED_BADGE', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isRendering: 'dummy value',
+ });
+ });
+
+ it('sets isRendering to true', () => {
+ store.commit(types.REQUEST_RENDERED_BADGE);
+
+ expect(store.state.isRendering).toBe(true);
+ });
+ });
+
+ describe('REQUEST_UPDATED_BADGE', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ isSaving: 'dummy value',
+ });
+ });
+
+ it('sets isSaving to true', () => {
+ store.commit(types.REQUEST_NEW_BADGE);
+
+ expect(store.state.isSaving).toBe(true);
+ });
+ });
+
+ describe('START_EDITING', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ badgeInEditForm: 'dummy value',
+ isEditing: 'dummy value',
+ renderedBadge: 'dummy value',
+ });
+ });
+
+ it('initializes the edit form', () => {
+ store.commit(types.START_EDITING, dummyBadge);
+
+ expect(store.state.isEditing).toBe(true);
+ expect(store.state.badgeInEditForm).toEqual(dummyBadge);
+ expect(store.state.renderedBadge).toEqual(dummyBadge);
+ });
+ });
+
+ describe('STOP_EDITING', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ badgeInEditForm: 'dummy value',
+ isEditing: 'dummy value',
+ renderedBadge: 'dummy value',
+ });
+ });
+
+ it('resets the edit form', () => {
+ store.commit(types.STOP_EDITING);
+
+ expect(store.state.isEditing).toBe(false);
+ expect(store.state.badgeInEditForm).toBe(null);
+ expect(store.state.renderedBadge).toBe(null);
+ });
+ });
+
+ describe('UPDATE_BADGE_IN_FORM', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ badgeInAddForm: 'dummy value',
+ badgeInEditForm: 'dummy value',
+ });
+ });
+
+ it('sets badgeInEditForm if isEditing is true', () => {
+ store.state.isEditing = true;
+
+ store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
+
+ expect(store.state.badgeInEditForm).toBe(dummyBadge);
+ });
+
+ it('sets badgeInAddForm if isEditing is false', () => {
+ store.state.isEditing = false;
+
+ store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
+
+ expect(store.state.badgeInAddForm).toBe(dummyBadge);
+ });
+ });
+
+ describe('UPDATE_BADGE_IN_MODAL', () => {
+ beforeEach(() => {
+ store.replaceState({
+ ...store.state,
+ badgeInModal: 'dummy value',
+ });
+ });
+
+ it('sets badgeInModal', () => {
+ store.commit(types.UPDATE_BADGE_IN_MODAL, dummyBadge);
+
+ expect(store.state.badgeInModal).toBe(dummyBadge);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 37088a6421c..be1ea0b57b4 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -41,6 +41,8 @@ describe('Issue card component', () => {
confidential: false,
labels: [list.label],
assignees: [],
+ reference_path: '#1',
+ real_path: '/test/1',
});
component = new Vue({
diff --git a/spec/javascripts/fixtures/one_white_pixel.png b/spec/javascripts/fixtures/one_white_pixel.png
new file mode 100644
index 00000000000..073fcf40a18
--- /dev/null
+++ b/spec/javascripts/fixtures/one_white_pixel.png
Binary files differ
diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js
index 34acdfbfba9..effacbcff4e 100644
--- a/spec/javascripts/helpers/vue_mount_component_helper.js
+++ b/spec/javascripts/helpers/vue_mount_component_helper.js
@@ -3,6 +3,12 @@ export const createComponentWithStore = (Component, store, propsData = {}) => ne
propsData,
});
+export const mountComponentWithStore = (Component, { el, props, store }) =>
+ new Component({
+ store,
+ propsData: props || { },
+ }).$mount(el);
+
export default (Component, props = {}, el = null) => new Component({
propsData: props,
}).$mount(el);
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index e57a55fa71a..ae00fb76714 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -65,11 +65,15 @@ describe('text_utility', () => {
describe('stripHtml', () => {
it('replaces html tag with the default replacement', () => {
- expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
+ 'This is a text with html.',
+ );
});
it('replaces html tags with the provided replacement', () => {
- expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual(
+ 'This is a text with html .',
+ );
});
});
@@ -78,4 +82,10 @@ describe('text_utility', () => {
expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
});
});
+
+ describe('convertToSentenceCase', () => {
+ it('converts Sentence Case to Sentence case', () => {
+ expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world');
+ });
+ });
});
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
new file mode 100644
index 00000000000..7cc5e753c22
--- /dev/null
+++ b/spec/javascripts/matchers.js
@@ -0,0 +1,35 @@
+export default {
+ toHaveSpriteIcon: () => ({
+ compare(element, iconName) {
+ if (!iconName) {
+ throw new Error('toHaveSpriteIcon is missing iconName argument!');
+ }
+
+ if (!(element instanceof HTMLElement)) {
+ throw new Error(`${element} is not a DOM element!`);
+ }
+
+ const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
+ const matchingIcon = iconReferences.find(reference => reference.getAttribute('xlink:href').endsWith(`#${iconName}`));
+ const result = {
+ pass: !!matchingIcon,
+ };
+
+ if (result.pass) {
+ result.message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
+ } else {
+ result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
+
+ const existingIcons = iconReferences.map((reference) => {
+ const iconUrl = reference.getAttribute('xlink:href');
+ return `"${iconUrl.replace(/^.+#/, '')}"`;
+ });
+ if (existingIcons.length > 0) {
+ result.message += ` (only found ${existingIcons.join(',')})`;
+ }
+ }
+
+ return result;
+ },
+ }),
+};
diff --git a/spec/javascripts/monitoring/graph/axis_spec.js b/spec/javascripts/monitoring/graph/axis_spec.js
new file mode 100644
index 00000000000..c7adba00637
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/axis_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+import GraphAxis from '~/monitoring/components/graph/axis.vue';
+import measurements from '~/monitoring/utils/measurements';
+
+const createComponent = propsData => {
+ const Component = Vue.extend(GraphAxis);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+const defaultValuesComponent = {
+ graphWidth: 500,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ yAxisLabel: 'Values',
+ unitOfDisplay: 'MB',
+};
+
+function getTextFromNode(component, selector) {
+ return component.$el.querySelector(selector).firstChild.nodeValue.trim();
+}
+
+describe('Axis', () => {
+ describe('Computed props', () => {
+ it('textTransform', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
+ });
+
+ it('xPosition', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ expect(component.xPosition).toEqual(180);
+ });
+
+ it('yPosition', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ expect(component.yPosition).toEqual(240);
+ });
+
+ it('rectTransform', () => {
+ 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(defaultValuesComponent);
+
+ expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
+ });
+
+ it('contains text to signal the usage, title and time with multiple time series', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ expect(getTextFromNode(component, '.y-label-text')).toEqual('Values (MB)');
+ });
+});
diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js
index 145c8db28d5..abcc51aa077 100644
--- a/spec/javascripts/monitoring/graph/legend_spec.js
+++ b/spec/javascripts/monitoring/graph/legend_spec.js
@@ -1,106 +1,44 @@
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 mountComponent from 'spec/helpers/vue_mount_component_helper';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
-const createComponent = (propsData) => {
- const Component = Vue.extend(GraphLegend);
-
- return new Component({
- 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 defaultValuesComponent = {};
-const timeSeries = createTimeSeries(convertedMetrics[0].queries,
- defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
- defaultValuesComponent.graphHeightOffset);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
defaultValuesComponent.timeSeries = timeSeries;
-function getTextFromNode(component, selector) {
- return component.$el.querySelector(selector).firstChild.nodeValue.trim();
-}
-
-describe('GraphLegend', () => {
- describe('Computed props', () => {
- it('textTransform', () => {
- const component = createComponent(defaultValuesComponent);
-
- expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
- });
-
- it('xPosition', () => {
- const component = createComponent(defaultValuesComponent);
-
- expect(component.xPosition).toEqual(180);
- });
-
- it('yPosition', () => {
- const component = createComponent(defaultValuesComponent);
-
- expect(component.yPosition).toEqual(240);
- });
-
- it('rectTransform', () => {
- const component = createComponent(defaultValuesComponent);
+describe('Legend Component', () => {
+ let vm;
+ let Legend;
- expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
- });
+ beforeEach(() => {
+ Legend = Vue.extend(GraphLegend);
});
- 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);
+ describe('View', () => {
+ beforeEach(() => {
+ vm = mountComponent(Legend, {
+ legendTitle: 'legend',
+ timeSeries,
+ currentDataIndex: 0,
+ unitOfDisplay: 'Req/Sec',
+ });
});
- it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
- const component = createComponent(defaultValuesComponent);
+ it('should render the usage, title and time with multiple time series', () => {
+ const titles = vm.$el.querySelectorAll('.legend-metric-title');
- 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);
+ expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
+ expect(titles[1].textContent.indexOf('2xx')).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 with multiple time series', () => {
- const component = createComponent(defaultValuesComponent);
- const titles = component.$el.querySelectorAll('.legend-metric-title');
-
- expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
- expect(titles[1].textContent.indexOf('2xx')).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(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length);
+ it('should container the same number of rows in the table as time series', () => {
+ expect(vm.$el.querySelectorAll('.prometheus-table tr').length).toEqual(vm.timeSeries.length);
+ });
});
});
diff --git a/spec/javascripts/monitoring/graph/track_info_spec.js b/spec/javascripts/monitoring/graph/track_info_spec.js
new file mode 100644
index 00000000000..d3121d553f9
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/track_info_spec.js
@@ -0,0 +1,44 @@
+import Vue from 'vue';
+import TrackInfo from '~/monitoring/components/graph/track_info.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
+
+describe('TrackInfo component', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(TrackInfo);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Computed props', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { track: timeSeries[0] });
+ });
+
+ it('summaryMetrics', () => {
+ expect(vm.summaryMetrics).toEqual('Avg: 0.000 · Max: 0.000');
+ });
+ });
+
+ describe('Rendered output', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { track: timeSeries[0] });
+ });
+
+ it('contains metric tag and the summary metrics', () => {
+ const metricTag = vm.$el.querySelector('strong');
+
+ expect(metricTag.textContent.trim()).toEqual(vm.track.metricTag);
+ expect(vm.$el.textContent).toContain('Avg: 0.000 · Max: 0.000');
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/graph/track_line_spec.js b/spec/javascripts/monitoring/graph/track_line_spec.js
new file mode 100644
index 00000000000..45106830a67
--- /dev/null
+++ b/spec/javascripts/monitoring/graph/track_line_spec.js
@@ -0,0 +1,52 @@
+import Vue from 'vue';
+import TrackLine from '~/monitoring/components/graph/track_line.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
+
+describe('TrackLine component', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(TrackLine);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Computed props', () => {
+ it('stylizedLine for dashed lineStyles', () => {
+ vm = mountComponent(Component, { track: { ...timeSeries[0], lineStyle: 'dashed' } });
+
+ expect(vm.stylizedLine).toEqual('6, 3');
+ });
+
+ it('stylizedLine for dotted lineStyles', () => {
+ vm = mountComponent(Component, { track: { ...timeSeries[0], lineStyle: 'dotted' } });
+
+ expect(vm.stylizedLine).toEqual('3, 3');
+ });
+ });
+
+ describe('Rendered output', () => {
+ it('has an svg with a line', () => {
+ vm = mountComponent(Component, { track: { ...timeSeries[0] } });
+ const svgEl = vm.$el.querySelector('svg');
+ const lineEl = vm.$el.querySelector('svg line');
+
+ expect(svgEl.getAttribute('width')).toEqual('15');
+ expect(svgEl.getAttribute('height')).toEqual('6');
+
+ expect(lineEl.getAttribute('stroke-width')).toEqual('4');
+ expect(lineEl.getAttribute('x1')).toEqual('0');
+ expect(lineEl.getAttribute('x2')).toEqual('15');
+ expect(lineEl.getAttribute('y1')).toEqual('2');
+ expect(lineEl.getAttribute('y2')).toEqual('2');
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index b1d69752bad..1213c80ba3a 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -2,11 +2,15 @@ import Vue from 'vue';
import Graph from '~/monitoring/components/graph.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub';
-import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
+import {
+ deploymentData,
+ convertDatesMultipleSeries,
+ singleRowMetricsMultipleSeries,
+} from './mock_data';
const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags';
const projectPath = 'http://test.host/frontend-fixtures/environments-project';
-const createComponent = (propsData) => {
+const createComponent = propsData => {
const Component = Vue.extend(Graph);
return new Component({
@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
}).$mount();
};
-const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const convertedMetrics = convertDatesMultipleSeries(
+ singleRowMetricsMultipleSeries,
+);
describe('Graph', () => {
beforeEach(() => {
@@ -31,7 +37,9 @@ describe('Graph', () => {
projectPath,
});
- expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title);
+ expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(
+ component.graphData.title,
+ );
});
describe('Computed props', () => {
@@ -46,8 +54,9 @@ describe('Graph', () => {
});
const transformedHeight = `${component.graphHeight - 100}`;
- expect(component.axisTransform.indexOf(transformedHeight))
- .not.toEqual(-1);
+ expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual(
+ -1,
+ );
});
it('outerViewBox gets a width and height property based on the DOM size of the element', () => {
@@ -63,11 +72,11 @@ describe('Graph', () => {
const viewBoxArray = component.outerViewBox.split(' ');
expect(typeof component.outerViewBox).toEqual('string');
expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
- expect(viewBoxArray[3]).toEqual(component.graphHeight.toString());
+ expect(viewBoxArray[3]).toEqual((component.graphHeight - 50).toString());
});
});
- it('sends an event to the eventhub when it has finished resizing', (done) => {
+ it('sends an event to the eventhub when it has finished resizing', done => {
const component = createComponent({
graphData: convertedMetrics[1],
classType: 'col-md-6',
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index f30208b27b6..50da6da2e07 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -3,2426 +3,645 @@
export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
export const metricsGroupsAPIResponse = {
- 'success': true,
- 'data': [
+ success: true,
+ data: [
{
- 'group': 'Kubernetes',
- 'priority': 1,
- 'metrics': [
- {
- 'title': 'Memory usage',
- 'weight': 1,
- 'queries': [
+ group: 'Kubernetes',
+ priority: 1,
+ metrics: [
+ {
+ title: 'Memory usage',
+ weight: 1,
+ queries: [
+ {
+ query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
+ y_label: 'Memory',
+ unit: 'MiB',
+ result: [
{
- 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
- 'y_label': 'Memory',
- 'unit': 'MiB',
- 'result': [
- {
- 'metric': {},
- 'values': [
- [
- 1495700554.925,
- '8.0390625'
- ],
- [
- 1495700614.925,
- '8.0390625'
- ],
- [
- 1495700674.925,
- '8.0390625'
- ],
- [
- 1495700734.925,
- '8.0390625'
- ],
- [
- 1495700794.925,
- '8.0390625'
- ],
- [
- 1495700854.925,
- '8.0390625'
- ],
- [
- 1495700914.925,
- '8.0390625'
- ],
- [
- 1495700974.925,
- '8.0390625'
- ],
- [
- 1495701034.925,
- '8.0390625'
- ],
- [
- 1495701094.925,
- '8.0390625'
- ],
- [
- 1495701154.925,
- '8.0390625'
- ],
- [
- 1495701214.925,
- '8.0390625'
- ],
- [
- 1495701274.925,
- '8.0390625'
- ],
- [
- 1495701334.925,
- '8.0390625'
- ],
- [
- 1495701394.925,
- '8.0390625'
- ],
- [
- 1495701454.925,
- '8.0390625'
- ],
- [
- 1495701514.925,
- '8.0390625'
- ],
- [
- 1495701574.925,
- '8.0390625'
- ],
- [
- 1495701634.925,
- '8.0390625'
- ],
- [
- 1495701694.925,
- '8.0390625'
- ],
- [
- 1495701754.925,
- '8.0390625'
- ],
- [
- 1495701814.925,
- '8.0390625'
- ],
- [
- 1495701874.925,
- '8.0390625'
- ],
- [
- 1495701934.925,
- '8.0390625'
- ],
- [
- 1495701994.925,
- '8.0390625'
- ],
- [
- 1495702054.925,
- '8.0390625'
- ],
- [
- 1495702114.925,
- '8.0390625'
- ],
- [
- 1495702174.925,
- '8.0390625'
- ],
- [
- 1495702234.925,
- '8.0390625'
- ],
- [
- 1495702294.925,
- '8.0390625'
- ],
- [
- 1495702354.925,
- '8.0390625'
- ],
- [
- 1495702414.925,
- '8.0390625'
- ],
- [
- 1495702474.925,
- '8.0390625'
- ],
- [
- 1495702534.925,
- '8.0390625'
- ],
- [
- 1495702594.925,
- '8.0390625'
- ],
- [
- 1495702654.925,
- '8.0390625'
- ],
- [
- 1495702714.925,
- '8.0390625'
- ],
- [
- 1495702774.925,
- '8.0390625'
- ],
- [
- 1495702834.925,
- '8.0390625'
- ],
- [
- 1495702894.925,
- '8.0390625'
- ],
- [
- 1495702954.925,
- '8.0390625'
- ],
- [
- 1495703014.925,
- '8.0390625'
- ],
- [
- 1495703074.925,
- '8.0390625'
- ],
- [
- 1495703134.925,
- '8.0390625'
- ],
- [
- 1495703194.925,
- '8.0390625'
- ],
- [
- 1495703254.925,
- '8.03515625'
- ],
- [
- 1495703314.925,
- '8.03515625'
- ],
- [
- 1495703374.925,
- '8.03515625'
- ],
- [
- 1495703434.925,
- '8.03515625'
- ],
- [
- 1495703494.925,
- '8.03515625'
- ],
- [
- 1495703554.925,
- '8.03515625'
- ],
- [
- 1495703614.925,
- '8.03515625'
- ],
- [
- 1495703674.925,
- '8.03515625'
- ],
- [
- 1495703734.925,
- '8.03515625'
- ],
- [
- 1495703794.925,
- '8.03515625'
- ],
- [
- 1495703854.925,
- '8.03515625'
- ],
- [
- 1495703914.925,
- '8.03515625'
- ],
- [
- 1495703974.925,
- '8.03515625'
- ],
- [
- 1495704034.925,
- '8.03515625'
- ],
- [
- 1495704094.925,
- '8.03515625'
- ],
- [
- 1495704154.925,
- '8.03515625'
- ],
- [
- 1495704214.925,
- '7.9296875'
- ],
- [
- 1495704274.925,
- '7.9296875'
- ],
- [
- 1495704334.925,
- '7.9296875'
- ],
- [
- 1495704394.925,
- '7.9296875'
- ],
- [
- 1495704454.925,
- '7.9296875'
- ],
- [
- 1495704514.925,
- '7.9296875'
- ],
- [
- 1495704574.925,
- '7.9296875'
- ],
- [
- 1495704634.925,
- '7.9296875'
- ],
- [
- 1495704694.925,
- '7.9296875'
- ],
- [
- 1495704754.925,
- '7.9296875'
- ],
- [
- 1495704814.925,
- '7.9296875'
- ],
- [
- 1495704874.925,
- '7.9296875'
- ],
- [
- 1495704934.925,
- '7.9296875'
- ],
- [
- 1495704994.925,
- '7.9296875'
- ],
- [
- 1495705054.925,
- '7.9296875'
- ],
- [
- 1495705114.925,
- '7.9296875'
- ],
- [
- 1495705174.925,
- '7.9296875'
- ],
- [
- 1495705234.925,
- '7.9296875'
- ],
- [
- 1495705294.925,
- '7.9296875'
- ],
- [
- 1495705354.925,
- '7.9296875'
- ],
- [
- 1495705414.925,
- '7.9296875'
- ],
- [
- 1495705474.925,
- '7.9296875'
- ],
- [
- 1495705534.925,
- '7.9296875'
- ],
- [
- 1495705594.925,
- '7.9296875'
- ],
- [
- 1495705654.925,
- '7.9296875'
- ],
- [
- 1495705714.925,
- '7.9296875'
- ],
- [
- 1495705774.925,
- '7.9296875'
- ],
- [
- 1495705834.925,
- '7.9296875'
- ],
- [
- 1495705894.925,
- '7.9296875'
- ],
- [
- 1495705954.925,
- '7.9296875'
- ],
- [
- 1495706014.925,
- '7.9296875'
- ],
- [
- 1495706074.925,
- '7.9296875'
- ],
- [
- 1495706134.925,
- '7.9296875'
- ],
- [
- 1495706194.925,
- '7.9296875'
- ],
- [
- 1495706254.925,
- '7.9296875'
- ],
- [
- 1495706314.925,
- '7.9296875'
- ],
- [
- 1495706374.925,
- '7.9296875'
- ],
- [
- 1495706434.925,
- '7.9296875'
- ],
- [
- 1495706494.925,
- '7.9296875'
- ],
- [
- 1495706554.925,
- '7.9296875'
- ],
- [
- 1495706614.925,
- '7.9296875'
- ],
- [
- 1495706674.925,
- '7.9296875'
- ],
- [
- 1495706734.925,
- '7.9296875'
- ],
- [
- 1495706794.925,
- '7.9296875'
- ],
- [
- 1495706854.925,
- '7.9296875'
- ],
- [
- 1495706914.925,
- '7.9296875'
- ],
- [
- 1495706974.925,
- '7.9296875'
- ],
- [
- 1495707034.925,
- '7.9296875'
- ],
- [
- 1495707094.925,
- '7.9296875'
- ],
- [
- 1495707154.925,
- '7.9296875'
- ],
- [
- 1495707214.925,
- '7.9296875'
- ],
- [
- 1495707274.925,
- '7.9296875'
- ],
- [
- 1495707334.925,
- '7.9296875'
- ],
- [
- 1495707394.925,
- '7.9296875'
- ],
- [
- 1495707454.925,
- '7.9296875'
- ],
- [
- 1495707514.925,
- '7.9296875'
- ],
- [
- 1495707574.925,
- '7.9296875'
- ],
- [
- 1495707634.925,
- '7.9296875'
- ],
- [
- 1495707694.925,
- '7.9296875'
- ],
- [
- 1495707754.925,
- '7.9296875'
- ],
- [
- 1495707814.925,
- '7.9296875'
- ],
- [
- 1495707874.925,
- '7.9296875'
- ],
- [
- 1495707934.925,
- '7.9296875'
- ],
- [
- 1495707994.925,
- '7.9296875'
- ],
- [
- 1495708054.925,
- '7.9296875'
- ],
- [
- 1495708114.925,
- '7.9296875'
- ],
- [
- 1495708174.925,
- '7.9296875'
- ],
- [
- 1495708234.925,
- '7.9296875'
- ],
- [
- 1495708294.925,
- '7.9296875'
- ],
- [
- 1495708354.925,
- '7.9296875'
- ],
- [
- 1495708414.925,
- '7.9296875'
- ],
- [
- 1495708474.925,
- '7.9296875'
- ],
- [
- 1495708534.925,
- '7.9296875'
- ],
- [
- 1495708594.925,
- '7.9296875'
- ],
- [
- 1495708654.925,
- '7.9296875'
- ],
- [
- 1495708714.925,
- '7.9296875'
- ],
- [
- 1495708774.925,
- '7.9296875'
- ],
- [
- 1495708834.925,
- '7.9296875'
- ],
- [
- 1495708894.925,
- '7.9296875'
- ],
- [
- 1495708954.925,
- '7.8984375'
- ],
- [
- 1495709014.925,
- '7.8984375'
- ],
- [
- 1495709074.925,
- '7.8984375'
- ],
- [
- 1495709134.925,
- '7.8984375'
- ],
- [
- 1495709194.925,
- '7.8984375'
- ],
- [
- 1495709254.925,
- '7.89453125'
- ],
- [
- 1495709314.925,
- '7.89453125'
- ],
- [
- 1495709374.925,
- '7.89453125'
- ],
- [
- 1495709434.925,
- '7.89453125'
- ],
- [
- 1495709494.925,
- '7.89453125'
- ],
- [
- 1495709554.925,
- '7.89453125'
- ],
- [
- 1495709614.925,
- '7.89453125'
- ],
- [
- 1495709674.925,
- '7.89453125'
- ],
- [
- 1495709734.925,
- '7.89453125'
- ],
- [
- 1495709794.925,
- '7.89453125'
- ],
- [
- 1495709854.925,
- '7.89453125'
- ],
- [
- 1495709914.925,
- '7.89453125'
- ],
- [
- 1495709974.925,
- '7.89453125'
- ],
- [
- 1495710034.925,
- '7.89453125'
- ],
- [
- 1495710094.925,
- '7.89453125'
- ],
- [
- 1495710154.925,
- '7.89453125'
- ],
- [
- 1495710214.925,
- '7.89453125'
- ],
- [
- 1495710274.925,
- '7.89453125'
- ],
- [
- 1495710334.925,
- '7.89453125'
- ],
- [
- 1495710394.925,
- '7.89453125'
- ],
- [
- 1495710454.925,
- '7.89453125'
- ],
- [
- 1495710514.925,
- '7.89453125'
- ],
- [
- 1495710574.925,
- '7.89453125'
- ],
- [
- 1495710634.925,
- '7.89453125'
- ],
- [
- 1495710694.925,
- '7.89453125'
- ],
- [
- 1495710754.925,
- '7.89453125'
- ],
- [
- 1495710814.925,
- '7.89453125'
- ],
- [
- 1495710874.925,
- '7.89453125'
- ],
- [
- 1495710934.925,
- '7.89453125'
- ],
- [
- 1495710994.925,
- '7.89453125'
- ],
- [
- 1495711054.925,
- '7.89453125'
- ],
- [
- 1495711114.925,
- '7.89453125'
- ],
- [
- 1495711174.925,
- '7.8515625'
- ],
- [
- 1495711234.925,
- '7.8515625'
- ],
- [
- 1495711294.925,
- '7.8515625'
- ],
- [
- 1495711354.925,
- '7.8515625'
- ],
- [
- 1495711414.925,
- '7.8515625'
- ],
- [
- 1495711474.925,
- '7.8515625'
- ],
- [
- 1495711534.925,
- '7.8515625'
- ],
- [
- 1495711594.925,
- '7.8515625'
- ],
- [
- 1495711654.925,
- '7.8515625'
- ],
- [
- 1495711714.925,
- '7.8515625'
- ],
- [
- 1495711774.925,
- '7.8515625'
- ],
- [
- 1495711834.925,
- '7.8515625'
- ],
- [
- 1495711894.925,
- '7.8515625'
- ],
- [
- 1495711954.925,
- '7.8515625'
- ],
- [
- 1495712014.925,
- '7.8515625'
- ],
- [
- 1495712074.925,
- '7.8515625'
- ],
- [
- 1495712134.925,
- '7.8515625'
- ],
- [
- 1495712194.925,
- '7.8515625'
- ],
- [
- 1495712254.925,
- '7.8515625'
- ],
- [
- 1495712314.925,
- '7.8515625'
- ],
- [
- 1495712374.925,
- '7.8515625'
- ],
- [
- 1495712434.925,
- '7.83203125'
- ],
- [
- 1495712494.925,
- '7.83203125'
- ],
- [
- 1495712554.925,
- '7.83203125'
- ],
- [
- 1495712614.925,
- '7.83203125'
- ],
- [
- 1495712674.925,
- '7.83203125'
- ],
- [
- 1495712734.925,
- '7.83203125'
- ],
- [
- 1495712794.925,
- '7.83203125'
- ],
- [
- 1495712854.925,
- '7.83203125'
- ],
- [
- 1495712914.925,
- '7.83203125'
- ],
- [
- 1495712974.925,
- '7.83203125'
- ],
- [
- 1495713034.925,
- '7.83203125'
- ],
- [
- 1495713094.925,
- '7.83203125'
- ],
- [
- 1495713154.925,
- '7.83203125'
- ],
- [
- 1495713214.925,
- '7.83203125'
- ],
- [
- 1495713274.925,
- '7.83203125'
- ],
- [
- 1495713334.925,
- '7.83203125'
- ],
- [
- 1495713394.925,
- '7.8125'
- ],
- [
- 1495713454.925,
- '7.8125'
- ],
- [
- 1495713514.925,
- '7.8125'
- ],
- [
- 1495713574.925,
- '7.8125'
- ],
- [
- 1495713634.925,
- '7.8125'
- ],
- [
- 1495713694.925,
- '7.8125'
- ],
- [
- 1495713754.925,
- '7.8125'
- ],
- [
- 1495713814.925,
- '7.8125'
- ],
- [
- 1495713874.925,
- '7.8125'
- ],
- [
- 1495713934.925,
- '7.8125'
- ],
- [
- 1495713994.925,
- '7.8125'
- ],
- [
- 1495714054.925,
- '7.8125'
- ],
- [
- 1495714114.925,
- '7.8125'
- ],
- [
- 1495714174.925,
- '7.8125'
- ],
- [
- 1495714234.925,
- '7.8125'
- ],
- [
- 1495714294.925,
- '7.8125'
- ],
- [
- 1495714354.925,
- '7.80859375'
- ],
- [
- 1495714414.925,
- '7.80859375'
- ],
- [
- 1495714474.925,
- '7.80859375'
- ],
- [
- 1495714534.925,
- '7.80859375'
- ],
- [
- 1495714594.925,
- '7.80859375'
- ],
- [
- 1495714654.925,
- '7.80859375'
- ],
- [
- 1495714714.925,
- '7.80859375'
- ],
- [
- 1495714774.925,
- '7.80859375'
- ],
- [
- 1495714834.925,
- '7.80859375'
- ],
- [
- 1495714894.925,
- '7.80859375'
- ],
- [
- 1495714954.925,
- '7.80859375'
- ],
- [
- 1495715014.925,
- '7.80859375'
- ],
- [
- 1495715074.925,
- '7.80859375'
- ],
- [
- 1495715134.925,
- '7.80859375'
- ],
- [
- 1495715194.925,
- '7.80859375'
- ],
- [
- 1495715254.925,
- '7.80859375'
- ],
- [
- 1495715314.925,
- '7.80859375'
- ],
- [
- 1495715374.925,
- '7.80859375'
- ],
- [
- 1495715434.925,
- '7.80859375'
- ],
- [
- 1495715494.925,
- '7.80859375'
- ],
- [
- 1495715554.925,
- '7.80859375'
- ],
- [
- 1495715614.925,
- '7.80859375'
- ],
- [
- 1495715674.925,
- '7.80859375'
- ],
- [
- 1495715734.925,
- '7.80859375'
- ],
- [
- 1495715794.925,
- '7.80859375'
- ],
- [
- 1495715854.925,
- '7.80859375'
- ],
- [
- 1495715914.925,
- '7.80078125'
- ],
- [
- 1495715974.925,
- '7.80078125'
- ],
- [
- 1495716034.925,
- '7.80078125'
- ],
- [
- 1495716094.925,
- '7.80078125'
- ],
- [
- 1495716154.925,
- '7.80078125'
- ],
- [
- 1495716214.925,
- '7.796875'
- ],
- [
- 1495716274.925,
- '7.796875'
- ],
- [
- 1495716334.925,
- '7.796875'
- ],
- [
- 1495716394.925,
- '7.796875'
- ],
- [
- 1495716454.925,
- '7.796875'
- ],
- [
- 1495716514.925,
- '7.796875'
- ],
- [
- 1495716574.925,
- '7.796875'
- ],
- [
- 1495716634.925,
- '7.796875'
- ],
- [
- 1495716694.925,
- '7.796875'
- ],
- [
- 1495716754.925,
- '7.796875'
- ],
- [
- 1495716814.925,
- '7.796875'
- ],
- [
- 1495716874.925,
- '7.79296875'
- ],
- [
- 1495716934.925,
- '7.79296875'
- ],
- [
- 1495716994.925,
- '7.79296875'
- ],
- [
- 1495717054.925,
- '7.79296875'
- ],
- [
- 1495717114.925,
- '7.79296875'
- ],
- [
- 1495717174.925,
- '7.7890625'
- ],
- [
- 1495717234.925,
- '7.7890625'
- ],
- [
- 1495717294.925,
- '7.7890625'
- ],
- [
- 1495717354.925,
- '7.7890625'
- ],
- [
- 1495717414.925,
- '7.7890625'
- ],
- [
- 1495717474.925,
- '7.7890625'
- ],
- [
- 1495717534.925,
- '7.7890625'
- ],
- [
- 1495717594.925,
- '7.7890625'
- ],
- [
- 1495717654.925,
- '7.7890625'
- ],
- [
- 1495717714.925,
- '7.7890625'
- ],
- [
- 1495717774.925,
- '7.7890625'
- ],
- [
- 1495717834.925,
- '7.77734375'
- ],
- [
- 1495717894.925,
- '7.77734375'
- ],
- [
- 1495717954.925,
- '7.77734375'
- ],
- [
- 1495718014.925,
- '7.77734375'
- ],
- [
- 1495718074.925,
- '7.77734375'
- ],
- [
- 1495718134.925,
- '7.7421875'
- ],
- [
- 1495718194.925,
- '7.7421875'
- ],
- [
- 1495718254.925,
- '7.7421875'
- ],
- [
- 1495718314.925,
- '7.7421875'
- ]
- ]
- }
- ]
- }
- ]
+ metric: {},
+ values: [
+ [1495700554.925, '8.0390625'],
+ [1495700614.925, '8.0390625'],
+ [1495700674.925, '8.0390625'],
+ [1495700734.925, '8.0390625'],
+ [1495700794.925, '8.0390625'],
+ [1495700854.925, '8.0390625'],
+ [1495700914.925, '8.0390625'],
+ [1495700974.925, '8.0390625'],
+ [1495701034.925, '8.0390625'],
+ [1495701094.925, '8.0390625'],
+ [1495701154.925, '8.0390625'],
+ [1495701214.925, '8.0390625'],
+ [1495701274.925, '8.0390625'],
+ [1495701334.925, '8.0390625'],
+ [1495701394.925, '8.0390625'],
+ [1495701454.925, '8.0390625'],
+ [1495701514.925, '8.0390625'],
+ [1495701574.925, '8.0390625'],
+ [1495701634.925, '8.0390625'],
+ [1495701694.925, '8.0390625'],
+ [1495701754.925, '8.0390625'],
+ [1495701814.925, '8.0390625'],
+ [1495701874.925, '8.0390625'],
+ [1495701934.925, '8.0390625'],
+ [1495701994.925, '8.0390625'],
+ [1495702054.925, '8.0390625'],
+ [1495702114.925, '8.0390625'],
+ [1495702174.925, '8.0390625'],
+ [1495702234.925, '8.0390625'],
+ [1495702294.925, '8.0390625'],
+ [1495702354.925, '8.0390625'],
+ [1495702414.925, '8.0390625'],
+ [1495702474.925, '8.0390625'],
+ [1495702534.925, '8.0390625'],
+ [1495702594.925, '8.0390625'],
+ [1495702654.925, '8.0390625'],
+ [1495702714.925, '8.0390625'],
+ [1495702774.925, '8.0390625'],
+ [1495702834.925, '8.0390625'],
+ [1495702894.925, '8.0390625'],
+ [1495702954.925, '8.0390625'],
+ [1495703014.925, '8.0390625'],
+ [1495703074.925, '8.0390625'],
+ [1495703134.925, '8.0390625'],
+ [1495703194.925, '8.0390625'],
+ [1495703254.925, '8.03515625'],
+ [1495703314.925, '8.03515625'],
+ [1495703374.925, '8.03515625'],
+ [1495703434.925, '8.03515625'],
+ [1495703494.925, '8.03515625'],
+ [1495703554.925, '8.03515625'],
+ [1495703614.925, '8.03515625'],
+ [1495703674.925, '8.03515625'],
+ [1495703734.925, '8.03515625'],
+ [1495703794.925, '8.03515625'],
+ [1495703854.925, '8.03515625'],
+ [1495703914.925, '8.03515625'],
+ [1495703974.925, '8.03515625'],
+ [1495704034.925, '8.03515625'],
+ [1495704094.925, '8.03515625'],
+ [1495704154.925, '8.03515625'],
+ [1495704214.925, '7.9296875'],
+ [1495704274.925, '7.9296875'],
+ [1495704334.925, '7.9296875'],
+ [1495704394.925, '7.9296875'],
+ [1495704454.925, '7.9296875'],
+ [1495704514.925, '7.9296875'],
+ [1495704574.925, '7.9296875'],
+ [1495704634.925, '7.9296875'],
+ [1495704694.925, '7.9296875'],
+ [1495704754.925, '7.9296875'],
+ [1495704814.925, '7.9296875'],
+ [1495704874.925, '7.9296875'],
+ [1495704934.925, '7.9296875'],
+ [1495704994.925, '7.9296875'],
+ [1495705054.925, '7.9296875'],
+ [1495705114.925, '7.9296875'],
+ [1495705174.925, '7.9296875'],
+ [1495705234.925, '7.9296875'],
+ [1495705294.925, '7.9296875'],
+ [1495705354.925, '7.9296875'],
+ [1495705414.925, '7.9296875'],
+ [1495705474.925, '7.9296875'],
+ [1495705534.925, '7.9296875'],
+ [1495705594.925, '7.9296875'],
+ [1495705654.925, '7.9296875'],
+ [1495705714.925, '7.9296875'],
+ [1495705774.925, '7.9296875'],
+ [1495705834.925, '7.9296875'],
+ [1495705894.925, '7.9296875'],
+ [1495705954.925, '7.9296875'],
+ [1495706014.925, '7.9296875'],
+ [1495706074.925, '7.9296875'],
+ [1495706134.925, '7.9296875'],
+ [1495706194.925, '7.9296875'],
+ [1495706254.925, '7.9296875'],
+ [1495706314.925, '7.9296875'],
+ [1495706374.925, '7.9296875'],
+ [1495706434.925, '7.9296875'],
+ [1495706494.925, '7.9296875'],
+ [1495706554.925, '7.9296875'],
+ [1495706614.925, '7.9296875'],
+ [1495706674.925, '7.9296875'],
+ [1495706734.925, '7.9296875'],
+ [1495706794.925, '7.9296875'],
+ [1495706854.925, '7.9296875'],
+ [1495706914.925, '7.9296875'],
+ [1495706974.925, '7.9296875'],
+ [1495707034.925, '7.9296875'],
+ [1495707094.925, '7.9296875'],
+ [1495707154.925, '7.9296875'],
+ [1495707214.925, '7.9296875'],
+ [1495707274.925, '7.9296875'],
+ [1495707334.925, '7.9296875'],
+ [1495707394.925, '7.9296875'],
+ [1495707454.925, '7.9296875'],
+ [1495707514.925, '7.9296875'],
+ [1495707574.925, '7.9296875'],
+ [1495707634.925, '7.9296875'],
+ [1495707694.925, '7.9296875'],
+ [1495707754.925, '7.9296875'],
+ [1495707814.925, '7.9296875'],
+ [1495707874.925, '7.9296875'],
+ [1495707934.925, '7.9296875'],
+ [1495707994.925, '7.9296875'],
+ [1495708054.925, '7.9296875'],
+ [1495708114.925, '7.9296875'],
+ [1495708174.925, '7.9296875'],
+ [1495708234.925, '7.9296875'],
+ [1495708294.925, '7.9296875'],
+ [1495708354.925, '7.9296875'],
+ [1495708414.925, '7.9296875'],
+ [1495708474.925, '7.9296875'],
+ [1495708534.925, '7.9296875'],
+ [1495708594.925, '7.9296875'],
+ [1495708654.925, '7.9296875'],
+ [1495708714.925, '7.9296875'],
+ [1495708774.925, '7.9296875'],
+ [1495708834.925, '7.9296875'],
+ [1495708894.925, '7.9296875'],
+ [1495708954.925, '7.8984375'],
+ [1495709014.925, '7.8984375'],
+ [1495709074.925, '7.8984375'],
+ [1495709134.925, '7.8984375'],
+ [1495709194.925, '7.8984375'],
+ [1495709254.925, '7.89453125'],
+ [1495709314.925, '7.89453125'],
+ [1495709374.925, '7.89453125'],
+ [1495709434.925, '7.89453125'],
+ [1495709494.925, '7.89453125'],
+ [1495709554.925, '7.89453125'],
+ [1495709614.925, '7.89453125'],
+ [1495709674.925, '7.89453125'],
+ [1495709734.925, '7.89453125'],
+ [1495709794.925, '7.89453125'],
+ [1495709854.925, '7.89453125'],
+ [1495709914.925, '7.89453125'],
+ [1495709974.925, '7.89453125'],
+ [1495710034.925, '7.89453125'],
+ [1495710094.925, '7.89453125'],
+ [1495710154.925, '7.89453125'],
+ [1495710214.925, '7.89453125'],
+ [1495710274.925, '7.89453125'],
+ [1495710334.925, '7.89453125'],
+ [1495710394.925, '7.89453125'],
+ [1495710454.925, '7.89453125'],
+ [1495710514.925, '7.89453125'],
+ [1495710574.925, '7.89453125'],
+ [1495710634.925, '7.89453125'],
+ [1495710694.925, '7.89453125'],
+ [1495710754.925, '7.89453125'],
+ [1495710814.925, '7.89453125'],
+ [1495710874.925, '7.89453125'],
+ [1495710934.925, '7.89453125'],
+ [1495710994.925, '7.89453125'],
+ [1495711054.925, '7.89453125'],
+ [1495711114.925, '7.89453125'],
+ [1495711174.925, '7.8515625'],
+ [1495711234.925, '7.8515625'],
+ [1495711294.925, '7.8515625'],
+ [1495711354.925, '7.8515625'],
+ [1495711414.925, '7.8515625'],
+ [1495711474.925, '7.8515625'],
+ [1495711534.925, '7.8515625'],
+ [1495711594.925, '7.8515625'],
+ [1495711654.925, '7.8515625'],
+ [1495711714.925, '7.8515625'],
+ [1495711774.925, '7.8515625'],
+ [1495711834.925, '7.8515625'],
+ [1495711894.925, '7.8515625'],
+ [1495711954.925, '7.8515625'],
+ [1495712014.925, '7.8515625'],
+ [1495712074.925, '7.8515625'],
+ [1495712134.925, '7.8515625'],
+ [1495712194.925, '7.8515625'],
+ [1495712254.925, '7.8515625'],
+ [1495712314.925, '7.8515625'],
+ [1495712374.925, '7.8515625'],
+ [1495712434.925, '7.83203125'],
+ [1495712494.925, '7.83203125'],
+ [1495712554.925, '7.83203125'],
+ [1495712614.925, '7.83203125'],
+ [1495712674.925, '7.83203125'],
+ [1495712734.925, '7.83203125'],
+ [1495712794.925, '7.83203125'],
+ [1495712854.925, '7.83203125'],
+ [1495712914.925, '7.83203125'],
+ [1495712974.925, '7.83203125'],
+ [1495713034.925, '7.83203125'],
+ [1495713094.925, '7.83203125'],
+ [1495713154.925, '7.83203125'],
+ [1495713214.925, '7.83203125'],
+ [1495713274.925, '7.83203125'],
+ [1495713334.925, '7.83203125'],
+ [1495713394.925, '7.8125'],
+ [1495713454.925, '7.8125'],
+ [1495713514.925, '7.8125'],
+ [1495713574.925, '7.8125'],
+ [1495713634.925, '7.8125'],
+ [1495713694.925, '7.8125'],
+ [1495713754.925, '7.8125'],
+ [1495713814.925, '7.8125'],
+ [1495713874.925, '7.8125'],
+ [1495713934.925, '7.8125'],
+ [1495713994.925, '7.8125'],
+ [1495714054.925, '7.8125'],
+ [1495714114.925, '7.8125'],
+ [1495714174.925, '7.8125'],
+ [1495714234.925, '7.8125'],
+ [1495714294.925, '7.8125'],
+ [1495714354.925, '7.80859375'],
+ [1495714414.925, '7.80859375'],
+ [1495714474.925, '7.80859375'],
+ [1495714534.925, '7.80859375'],
+ [1495714594.925, '7.80859375'],
+ [1495714654.925, '7.80859375'],
+ [1495714714.925, '7.80859375'],
+ [1495714774.925, '7.80859375'],
+ [1495714834.925, '7.80859375'],
+ [1495714894.925, '7.80859375'],
+ [1495714954.925, '7.80859375'],
+ [1495715014.925, '7.80859375'],
+ [1495715074.925, '7.80859375'],
+ [1495715134.925, '7.80859375'],
+ [1495715194.925, '7.80859375'],
+ [1495715254.925, '7.80859375'],
+ [1495715314.925, '7.80859375'],
+ [1495715374.925, '7.80859375'],
+ [1495715434.925, '7.80859375'],
+ [1495715494.925, '7.80859375'],
+ [1495715554.925, '7.80859375'],
+ [1495715614.925, '7.80859375'],
+ [1495715674.925, '7.80859375'],
+ [1495715734.925, '7.80859375'],
+ [1495715794.925, '7.80859375'],
+ [1495715854.925, '7.80859375'],
+ [1495715914.925, '7.80078125'],
+ [1495715974.925, '7.80078125'],
+ [1495716034.925, '7.80078125'],
+ [1495716094.925, '7.80078125'],
+ [1495716154.925, '7.80078125'],
+ [1495716214.925, '7.796875'],
+ [1495716274.925, '7.796875'],
+ [1495716334.925, '7.796875'],
+ [1495716394.925, '7.796875'],
+ [1495716454.925, '7.796875'],
+ [1495716514.925, '7.796875'],
+ [1495716574.925, '7.796875'],
+ [1495716634.925, '7.796875'],
+ [1495716694.925, '7.796875'],
+ [1495716754.925, '7.796875'],
+ [1495716814.925, '7.796875'],
+ [1495716874.925, '7.79296875'],
+ [1495716934.925, '7.79296875'],
+ [1495716994.925, '7.79296875'],
+ [1495717054.925, '7.79296875'],
+ [1495717114.925, '7.79296875'],
+ [1495717174.925, '7.7890625'],
+ [1495717234.925, '7.7890625'],
+ [1495717294.925, '7.7890625'],
+ [1495717354.925, '7.7890625'],
+ [1495717414.925, '7.7890625'],
+ [1495717474.925, '7.7890625'],
+ [1495717534.925, '7.7890625'],
+ [1495717594.925, '7.7890625'],
+ [1495717654.925, '7.7890625'],
+ [1495717714.925, '7.7890625'],
+ [1495717774.925, '7.7890625'],
+ [1495717834.925, '7.77734375'],
+ [1495717894.925, '7.77734375'],
+ [1495717954.925, '7.77734375'],
+ [1495718014.925, '7.77734375'],
+ [1495718074.925, '7.77734375'],
+ [1495718134.925, '7.7421875'],
+ [1495718194.925, '7.7421875'],
+ [1495718254.925, '7.7421875'],
+ [1495718314.925, '7.7421875'],
+ ],
+ },
+ ],
+ },
+ ],
},
{
- 'title': 'CPU usage',
- 'weight': 1,
- 'queries': [
+ title: 'CPU usage',
+ weight: 1,
+ queries: [
+ {
+ query_range:
+ 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+ result: [
{
- 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
- 'result': [
- {
- 'metric': {},
- 'values': [
- [
- 1495700554.925,
- '0.0010794445585559514'
- ],
- [
- 1495700614.925,
- '0.003927214935433527'
- ],
- [
- 1495700674.925,
- '0.0053045219047619975'
- ],
- [
- 1495700734.925,
- '0.0048892095238097155'
- ],
- [
- 1495700794.925,
- '0.005827140952381137'
- ],
- [
- 1495700854.925,
- '0.00569846906219937'
- ],
- [
- 1495700914.925,
- '0.004972616802849382'
- ],
- [
- 1495700974.925,
- '0.005117509523809902'
- ],
- [
- 1495701034.925,
- '0.00512389061919564'
- ],
- [
- 1495701094.925,
- '0.005199100501890691'
- ],
- [
- 1495701154.925,
- '0.005415746394885837'
- ],
- [
- 1495701214.925,
- '0.005607682788146286'
- ],
- [
- 1495701274.925,
- '0.005641300000000118'
- ],
- [
- 1495701334.925,
- '0.0071166279368766495'
- ],
- [
- 1495701394.925,
- '0.0063242138095234044'
- ],
- [
- 1495701454.925,
- '0.005793314698235304'
- ],
- [
- 1495701514.925,
- '0.00703934942237556'
- ],
- [
- 1495701574.925,
- '0.006357007076123191'
- ],
- [
- 1495701634.925,
- '0.003753167300126738'
- ],
- [
- 1495701694.925,
- '0.005018469678430698'
- ],
- [
- 1495701754.925,
- '0.0045217153371887'
- ],
- [
- 1495701814.925,
- '0.006140104285714119'
- ],
- [
- 1495701874.925,
- '0.004818684285714102'
- ],
- [
- 1495701934.925,
- '0.005079509718955242'
- ],
- [
- 1495701994.925,
- '0.005059981142498263'
- ],
- [
- 1495702054.925,
- '0.005269098389538773'
- ],
- [
- 1495702114.925,
- '0.005269954285714175'
- ],
- [
- 1495702174.925,
- '0.014199241435795856'
- ],
- [
- 1495702234.925,
- '0.01511936843111017'
- ],
- [
- 1495702294.925,
- '0.0060933692920682875'
- ],
- [
- 1495702354.925,
- '0.004945682380952493'
- ],
- [
- 1495702414.925,
- '0.005641266666666565'
- ],
- [
- 1495702474.925,
- '0.005223752857142996'
- ],
- [
- 1495702534.925,
- '0.005743098505699831'
- ],
- [
- 1495702594.925,
- '0.00538493380952391'
- ],
- [
- 1495702654.925,
- '0.005507793883751339'
- ],
- [
- 1495702714.925,
- '0.005666705714285466'
- ],
- [
- 1495702774.925,
- '0.006231530000000112'
- ],
- [
- 1495702834.925,
- '0.006570768635394899'
- ],
- [
- 1495702894.925,
- '0.005551146666666895'
- ],
- [
- 1495702954.925,
- '0.005602604737098058'
- ],
- [
- 1495703014.925,
- '0.00613993580402159'
- ],
- [
- 1495703074.925,
- '0.004770258764368832'
- ],
- [
- 1495703134.925,
- '0.005512376671364914'
- ],
- [
- 1495703194.925,
- '0.005254436666666674'
- ],
- [
- 1495703254.925,
- '0.0050109839141320505'
- ],
- [
- 1495703314.925,
- '0.0049478019256960016'
- ],
- [
- 1495703374.925,
- '0.0037666860965123463'
- ],
- [
- 1495703434.925,
- '0.004813526061656314'
- ],
- [
- 1495703494.925,
- '0.005047748095238278'
- ],
- [
- 1495703554.925,
- '0.00386494081008772'
- ],
- [
- 1495703614.925,
- '0.004304037408111405'
- ],
- [
- 1495703674.925,
- '0.004999466661587168'
- ],
- [
- 1495703734.925,
- '0.004689140476190834'
- ],
- [
- 1495703794.925,
- '0.004746126153582475'
- ],
- [
- 1495703854.925,
- '0.004482706382572302'
- ],
- [
- 1495703914.925,
- '0.004032808931864524'
- ],
- [
- 1495703974.925,
- '0.005728319047618988'
- ],
- [
- 1495704034.925,
- '0.004436139179627006'
- ],
- [
- 1495704094.925,
- '0.004553455714285617'
- ],
- [
- 1495704154.925,
- '0.003455244285714341'
- ],
- [
- 1495704214.925,
- '0.004742244761904621'
- ],
- [
- 1495704274.925,
- '0.005366978571428422'
- ],
- [
- 1495704334.925,
- '0.004257954837665058'
- ],
- [
- 1495704394.925,
- '0.005431603259831257'
- ],
- [
- 1495704454.925,
- '0.0052009214498621986'
- ],
- [
- 1495704514.925,
- '0.004317201904761618'
- ],
- [
- 1495704574.925,
- '0.004307384285714157'
- ],
- [
- 1495704634.925,
- '0.004789801146644822'
- ],
- [
- 1495704694.925,
- '0.0051429795906706485'
- ],
- [
- 1495704754.925,
- '0.005322495714285479'
- ],
- [
- 1495704814.925,
- '0.004512809333244233'
- ],
- [
- 1495704874.925,
- '0.004953843582568726'
- ],
- [
- 1495704934.925,
- '0.005812690120858119'
- ],
- [
- 1495704994.925,
- '0.004997024285714838'
- ],
- [
- 1495705054.925,
- '0.005246216154439592'
- ],
- [
- 1495705114.925,
- '0.0063494966618726795'
- ],
- [
- 1495705174.925,
- '0.005306004342898225'
- ],
- [
- 1495705234.925,
- '0.005081412857142978'
- ],
- [
- 1495705294.925,
- '0.00511409523809522'
- ],
- [
- 1495705354.925,
- '0.0047861001481192'
- ],
- [
- 1495705414.925,
- '0.005107688228042962'
- ],
- [
- 1495705474.925,
- '0.005271929582294012'
- ],
- [
- 1495705534.925,
- '0.004453254502681249'
- ],
- [
- 1495705594.925,
- '0.005799134293959226'
- ],
- [
- 1495705654.925,
- '0.005340865929502478'
- ],
- [
- 1495705714.925,
- '0.004911654761904942'
- ],
- [
- 1495705774.925,
- '0.005888234873953261'
- ],
- [
- 1495705834.925,
- '0.005565283333332954'
- ],
- [
- 1495705894.925,
- '0.005522869047618869'
- ],
- [
- 1495705954.925,
- '0.005177549737621646'
- ],
- [
- 1495706014.925,
- '0.0053145810232096465'
- ],
- [
- 1495706074.925,
- '0.004751095238095275'
- ],
- [
- 1495706134.925,
- '0.006242077142856976'
- ],
- [
- 1495706194.925,
- '0.00621034406957871'
- ],
- [
- 1495706254.925,
- '0.006887592738978596'
- ],
- [
- 1495706314.925,
- '0.006328128779726213'
- ],
- [
- 1495706374.925,
- '0.007488363809523927'
- ],
- [
- 1495706434.925,
- '0.006193758571428157'
- ],
- [
- 1495706494.925,
- '0.0068798371839706935'
- ],
- [
- 1495706554.925,
- '0.005757034340423128'
- ],
- [
- 1495706614.925,
- '0.004571388497294698'
- ],
- [
- 1495706674.925,
- '0.00620283044923395'
- ],
- [
- 1495706734.925,
- '0.005607562380952455'
- ],
- [
- 1495706794.925,
- '0.005506969933620308'
- ],
- [
- 1495706854.925,
- '0.005621118095238131'
- ],
- [
- 1495706914.925,
- '0.004876606098698849'
- ],
- [
- 1495706974.925,
- '0.0047871205988517206'
- ],
- [
- 1495707034.925,
- '0.00526405939458784'
- ],
- [
- 1495707094.925,
- '0.005716323800605852'
- ],
- [
- 1495707154.925,
- '0.005301459523809575'
- ],
- [
- 1495707214.925,
- '0.0051613042857144905'
- ],
- [
- 1495707274.925,
- '0.005384792857142714'
- ],
- [
- 1495707334.925,
- '0.005259719047619222'
- ],
- [
- 1495707394.925,
- '0.00584101142857182'
- ],
- [
- 1495707454.925,
- '0.0060066121920326326'
- ],
- [
- 1495707514.925,
- '0.006359978571428453'
- ],
- [
- 1495707574.925,
- '0.006315876322151109'
- ],
- [
- 1495707634.925,
- '0.005590012517198831'
- ],
- [
- 1495707694.925,
- '0.005517419877137072'
- ],
- [
- 1495707754.925,
- '0.006089813430348506'
- ],
- [
- 1495707814.925,
- '0.00466754476190479'
- ],
- [
- 1495707874.925,
- '0.006059954380517721'
- ],
- [
- 1495707934.925,
- '0.005085657142856972'
- ],
- [
- 1495707994.925,
- '0.005897665238095296'
- ],
- [
- 1495708054.925,
- '0.0062282023199555885'
- ],
- [
- 1495708114.925,
- '0.00526214553236979'
- ],
- [
- 1495708174.925,
- '0.0044803300000000644'
- ],
- [
- 1495708234.925,
- '0.005421443333333592'
- ],
- [
- 1495708294.925,
- '0.005694326244512144'
- ],
- [
- 1495708354.925,
- '0.005527721904761457'
- ],
- [
- 1495708414.925,
- '0.005988819523809819'
- ],
- [
- 1495708474.925,
- '0.005484704285714448'
- ],
- [
- 1495708534.925,
- '0.005041123649230085'
- ],
- [
- 1495708594.925,
- '0.005717767639612059'
- ],
- [
- 1495708654.925,
- '0.005412954417342863'
- ],
- [
- 1495708714.925,
- '0.005833343333333254'
- ],
- [
- 1495708774.925,
- '0.005448135238094969'
- ],
- [
- 1495708834.925,
- '0.005117341428571432'
- ],
- [
- 1495708894.925,
- '0.005888345825277833'
- ],
- [
- 1495708954.925,
- '0.005398543809524135'
- ],
- [
- 1495709014.925,
- '0.005325611428571416'
- ],
- [
- 1495709074.925,
- '0.005848668571428527'
- ],
- [
- 1495709134.925,
- '0.005135003105145044'
- ],
- [
- 1495709194.925,
- '0.0054551400000003'
- ],
- [
- 1495709254.925,
- '0.005319472937322171'
- ],
- [
- 1495709314.925,
- '0.00585677857142792'
- ],
- [
- 1495709374.925,
- '0.0062146261904759215'
- ],
- [
- 1495709434.925,
- '0.0067105060904182265'
- ],
- [
- 1495709494.925,
- '0.005829691904762108'
- ],
- [
- 1495709554.925,
- '0.005719280952381261'
- ],
- [
- 1495709614.925,
- '0.005682603793416407'
- ],
- [
- 1495709674.925,
- '0.0055272846277326934'
- ],
- [
- 1495709734.925,
- '0.0057123680952386735'
- ],
- [
- 1495709794.925,
- '0.00520597958075818'
- ],
- [
- 1495709854.925,
- '0.005584358957263837'
- ],
- [
- 1495709914.925,
- '0.005601104275197466'
- ],
- [
- 1495709974.925,
- '0.005991657142857066'
- ],
- [
- 1495710034.925,
- '0.00553722238095218'
- ],
- [
- 1495710094.925,
- '0.005127883122696293'
- ],
- [
- 1495710154.925,
- '0.005498111927534584'
- ],
- [
- 1495710214.925,
- '0.005609934069084202'
- ],
- [
- 1495710274.925,
- '0.00459206285714307'
- ],
- [
- 1495710334.925,
- '0.0047910828571428084'
- ],
- [
- 1495710394.925,
- '0.0056014671288845685'
- ],
- [
- 1495710454.925,
- '0.005686936791078528'
- ],
- [
- 1495710514.925,
- '0.00444480476190448'
- ],
- [
- 1495710574.925,
- '0.005780394696738921'
- ],
- [
- 1495710634.925,
- '0.0053107227550210365'
- ],
- [
- 1495710694.925,
- '0.005096031495761817'
- ],
- [
- 1495710754.925,
- '0.005451377979091524'
- ],
- [
- 1495710814.925,
- '0.005328136666667083'
- ],
- [
- 1495710874.925,
- '0.006020612857143043'
- ],
- [
- 1495710934.925,
- '0.0061063585714285365'
- ],
- [
- 1495710994.925,
- '0.006018346015752312'
- ],
- [
- 1495711054.925,
- '0.005069130952381193'
- ],
- [
- 1495711114.925,
- '0.005458406190476052'
- ],
- [
- 1495711174.925,
- '0.00577219190476179'
- ],
- [
- 1495711234.925,
- '0.005760814645658314'
- ],
- [
- 1495711294.925,
- '0.005371875716579101'
- ],
- [
- 1495711354.925,
- '0.0064232666666665834'
- ],
- [
- 1495711414.925,
- '0.009369806836906667'
- ],
- [
- 1495711474.925,
- '0.008956864761904692'
- ],
- [
- 1495711534.925,
- '0.005266849368559271'
- ],
- [
- 1495711594.925,
- '0.005335111364934262'
- ],
- [
- 1495711654.925,
- '0.006461778319586945'
- ],
- [
- 1495711714.925,
- '0.004687939890762393'
- ],
- [
- 1495711774.925,
- '0.004438831245760684'
- ],
- [
- 1495711834.925,
- '0.005142786666666613'
- ],
- [
- 1495711894.925,
- '0.007257734212054963'
- ],
- [
- 1495711954.925,
- '0.005621991904761494'
- ],
- [
- 1495712014.925,
- '0.007868689999999862'
- ],
- [
- 1495712074.925,
- '0.00910970215275738'
- ],
- [
- 1495712134.925,
- '0.006151004285714278'
- ],
- [
- 1495712194.925,
- '0.005447120924961522'
- ],
- [
- 1495712254.925,
- '0.005150705153929503'
- ],
- [
- 1495712314.925,
- '0.006358108714969314'
- ],
- [
- 1495712374.925,
- '0.0057725354795696475'
- ],
- [
- 1495712434.925,
- '0.005232139047619015'
- ],
- [
- 1495712494.925,
- '0.004932809617949037'
- ],
- [
- 1495712554.925,
- '0.004511607508499662'
- ],
- [
- 1495712614.925,
- '0.00440487701522666'
- ],
- [
- 1495712674.925,
- '0.005479113333333174'
- ],
- [
- 1495712734.925,
- '0.004726317619047547'
- ],
- [
- 1495712794.925,
- '0.005582041102958029'
- ],
- [
- 1495712854.925,
- '0.006381481216082099'
- ],
- [
- 1495712914.925,
- '0.005474260014095208'
- ],
- [
- 1495712974.925,
- '0.00567597142857188'
- ],
- [
- 1495713034.925,
- '0.0064741233333332985'
- ],
- [
- 1495713094.925,
- '0.005467475714285271'
- ],
- [
- 1495713154.925,
- '0.004868648393824457'
- ],
- [
- 1495713214.925,
- '0.005254923286444893'
- ],
- [
- 1495713274.925,
- '0.005599217150312865'
- ],
- [
- 1495713334.925,
- '0.005105413720618919'
- ],
- [
- 1495713394.925,
- '0.007246073333333279'
- ],
- [
- 1495713454.925,
- '0.005990312380952272'
- ],
- [
- 1495713514.925,
- '0.005594601853351101'
- ],
- [
- 1495713574.925,
- '0.004739258673727054'
- ],
- [
- 1495713634.925,
- '0.003932121428571783'
- ],
- [
- 1495713694.925,
- '0.005018188268459395'
- ],
- [
- 1495713754.925,
- '0.004538238095237985'
- ],
- [
- 1495713814.925,
- '0.00561816643265435'
- ],
- [
- 1495713874.925,
- '0.0063132584495033586'
- ],
- [
- 1495713934.925,
- '0.00442385238095213'
- ],
- [
- 1495713994.925,
- '0.004181795887658453'
- ],
- [
- 1495714054.925,
- '0.004437759047619037'
- ],
- [
- 1495714114.925,
- '0.006421748157178241'
- ],
- [
- 1495714174.925,
- '0.006525143809523842'
- ],
- [
- 1495714234.925,
- '0.004715904935144247'
- ],
- [
- 1495714294.925,
- '0.005966040152763461'
- ],
- [
- 1495714354.925,
- '0.005614535466921674'
- ],
- [
- 1495714414.925,
- '0.004934375119415906'
- ],
- [
- 1495714474.925,
- '0.0054122933333327385'
- ],
- [
- 1495714534.925,
- '0.004926540699612279'
- ],
- [
- 1495714594.925,
- '0.006124649517134237'
- ],
- [
- 1495714654.925,
- '0.004629427092013995'
- ],
- [
- 1495714714.925,
- '0.005117951257607005'
- ],
- [
- 1495714774.925,
- '0.004868774512685422'
- ],
- [
- 1495714834.925,
- '0.005310093333333399'
- ],
- [
- 1495714894.925,
- '0.0054907752286127345'
- ],
- [
- 1495714954.925,
- '0.004597678117351089'
- ],
- [
- 1495715014.925,
- '0.0059622552380952'
- ],
- [
- 1495715074.925,
- '0.005352457072655368'
- ],
- [
- 1495715134.925,
- '0.005491630952381143'
- ],
- [
- 1495715194.925,
- '0.006391770078379791'
- ],
- [
- 1495715254.925,
- '0.005933472857142518'
- ],
- [
- 1495715314.925,
- '0.005301314285714163'
- ],
- [
- 1495715374.925,
- '0.0058352959724814165'
- ],
- [
- 1495715434.925,
- '0.006154755147867044'
- ],
- [
- 1495715494.925,
- '0.009391935637482038'
- ],
- [
- 1495715554.925,
- '0.007846462857142592'
- ],
- [
- 1495715614.925,
- '0.00477608215316353'
- ],
- [
- 1495715674.925,
- '0.006132865238094998'
- ],
- [
- 1495715734.925,
- '0.006159762457649516'
- ],
- [
- 1495715794.925,
- '0.005957307073265968'
- ],
- [
- 1495715854.925,
- '0.006652319091792501'
- ],
- [
- 1495715914.925,
- '0.005493557402895287'
- ],
- [
- 1495715974.925,
- '0.0058652434829145166'
- ],
- [
- 1495716034.925,
- '0.005627400430468021'
- ],
- [
- 1495716094.925,
- '0.006240656190475609'
- ],
- [
- 1495716154.925,
- '0.006305997676168624'
- ],
- [
- 1495716214.925,
- '0.005388057732783248'
- ],
- [
- 1495716274.925,
- '0.0052814916048421244'
- ],
- [
- 1495716334.925,
- '0.00699498614272497'
- ],
- [
- 1495716394.925,
- '0.00627768693035141'
- ],
- [
- 1495716454.925,
- '0.0042411487048161145'
- ],
- [
- 1495716514.925,
- '0.005348647473627653'
- ],
- [
- 1495716574.925,
- '0.0047176657142853975'
- ],
- [
- 1495716634.925,
- '0.004437898571428686'
- ],
- [
- 1495716694.925,
- '0.004923527366927261'
- ],
- [
- 1495716754.925,
- '0.005131935066048421'
- ],
- [
- 1495716814.925,
- '0.005046949523809611'
- ],
- [
- 1495716874.925,
- '0.00547184095238092'
- ],
- [
- 1495716934.925,
- '0.005224140016380444'
- ],
- [
- 1495716994.925,
- '0.005297991171665292'
- ],
- [
- 1495717054.925,
- '0.005492965995623498'
- ],
- [
- 1495717114.925,
- '0.005754660000000403'
- ],
- [
- 1495717174.925,
- '0.005949557138639285'
- ],
- [
- 1495717234.925,
- '0.006091816112534666'
- ],
- [
- 1495717294.925,
- '0.005554210080192063'
- ],
- [
- 1495717354.925,
- '0.006411504395279871'
- ],
- [
- 1495717414.925,
- '0.006319643996609606'
- ],
- [
- 1495717474.925,
- '0.005539174405717675'
- ],
- [
- 1495717534.925,
- '0.0053157078842772255'
- ],
- [
- 1495717594.925,
- '0.005247480952381066'
- ],
- [
- 1495717654.925,
- '0.004820141620396252'
- ],
- [
- 1495717714.925,
- '0.005906173868322844'
- ],
- [
- 1495717774.925,
- '0.006173117219570961'
- ],
- [
- 1495717834.925,
- '0.005963340952380661'
- ],
- [
- 1495717894.925,
- '0.005698976627681527'
- ],
- [
- 1495717954.925,
- '0.004751279096346378'
- ],
- [
- 1495718014.925,
- '0.005733142379359711'
- ],
- [
- 1495718074.925,
- '0.004831689010348035'
- ],
- [
- 1495718134.925,
- '0.005188370476191092'
- ],
- [
- 1495718194.925,
- '0.004793227554547938'
- ],
- [
- 1495718254.925,
- '0.003997442857142731'
- ],
- [
- 1495718314.925,
- '0.004386040132951264'
- ]
- ]
- }
- ]
- }
- ]
- }
- ]
- }
+ metric: {},
+ values: [
+ [1495700554.925, '0.0010794445585559514'],
+ [1495700614.925, '0.003927214935433527'],
+ [1495700674.925, '0.0053045219047619975'],
+ [1495700734.925, '0.0048892095238097155'],
+ [1495700794.925, '0.005827140952381137'],
+ [1495700854.925, '0.00569846906219937'],
+ [1495700914.925, '0.004972616802849382'],
+ [1495700974.925, '0.005117509523809902'],
+ [1495701034.925, '0.00512389061919564'],
+ [1495701094.925, '0.005199100501890691'],
+ [1495701154.925, '0.005415746394885837'],
+ [1495701214.925, '0.005607682788146286'],
+ [1495701274.925, '0.005641300000000118'],
+ [1495701334.925, '0.0071166279368766495'],
+ [1495701394.925, '0.0063242138095234044'],
+ [1495701454.925, '0.005793314698235304'],
+ [1495701514.925, '0.00703934942237556'],
+ [1495701574.925, '0.006357007076123191'],
+ [1495701634.925, '0.003753167300126738'],
+ [1495701694.925, '0.005018469678430698'],
+ [1495701754.925, '0.0045217153371887'],
+ [1495701814.925, '0.006140104285714119'],
+ [1495701874.925, '0.004818684285714102'],
+ [1495701934.925, '0.005079509718955242'],
+ [1495701994.925, '0.005059981142498263'],
+ [1495702054.925, '0.005269098389538773'],
+ [1495702114.925, '0.005269954285714175'],
+ [1495702174.925, '0.014199241435795856'],
+ [1495702234.925, '0.01511936843111017'],
+ [1495702294.925, '0.0060933692920682875'],
+ [1495702354.925, '0.004945682380952493'],
+ [1495702414.925, '0.005641266666666565'],
+ [1495702474.925, '0.005223752857142996'],
+ [1495702534.925, '0.005743098505699831'],
+ [1495702594.925, '0.00538493380952391'],
+ [1495702654.925, '0.005507793883751339'],
+ [1495702714.925, '0.005666705714285466'],
+ [1495702774.925, '0.006231530000000112'],
+ [1495702834.925, '0.006570768635394899'],
+ [1495702894.925, '0.005551146666666895'],
+ [1495702954.925, '0.005602604737098058'],
+ [1495703014.925, '0.00613993580402159'],
+ [1495703074.925, '0.004770258764368832'],
+ [1495703134.925, '0.005512376671364914'],
+ [1495703194.925, '0.005254436666666674'],
+ [1495703254.925, '0.0050109839141320505'],
+ [1495703314.925, '0.0049478019256960016'],
+ [1495703374.925, '0.0037666860965123463'],
+ [1495703434.925, '0.004813526061656314'],
+ [1495703494.925, '0.005047748095238278'],
+ [1495703554.925, '0.00386494081008772'],
+ [1495703614.925, '0.004304037408111405'],
+ [1495703674.925, '0.004999466661587168'],
+ [1495703734.925, '0.004689140476190834'],
+ [1495703794.925, '0.004746126153582475'],
+ [1495703854.925, '0.004482706382572302'],
+ [1495703914.925, '0.004032808931864524'],
+ [1495703974.925, '0.005728319047618988'],
+ [1495704034.925, '0.004436139179627006'],
+ [1495704094.925, '0.004553455714285617'],
+ [1495704154.925, '0.003455244285714341'],
+ [1495704214.925, '0.004742244761904621'],
+ [1495704274.925, '0.005366978571428422'],
+ [1495704334.925, '0.004257954837665058'],
+ [1495704394.925, '0.005431603259831257'],
+ [1495704454.925, '0.0052009214498621986'],
+ [1495704514.925, '0.004317201904761618'],
+ [1495704574.925, '0.004307384285714157'],
+ [1495704634.925, '0.004789801146644822'],
+ [1495704694.925, '0.0051429795906706485'],
+ [1495704754.925, '0.005322495714285479'],
+ [1495704814.925, '0.004512809333244233'],
+ [1495704874.925, '0.004953843582568726'],
+ [1495704934.925, '0.005812690120858119'],
+ [1495704994.925, '0.004997024285714838'],
+ [1495705054.925, '0.005246216154439592'],
+ [1495705114.925, '0.0063494966618726795'],
+ [1495705174.925, '0.005306004342898225'],
+ [1495705234.925, '0.005081412857142978'],
+ [1495705294.925, '0.00511409523809522'],
+ [1495705354.925, '0.0047861001481192'],
+ [1495705414.925, '0.005107688228042962'],
+ [1495705474.925, '0.005271929582294012'],
+ [1495705534.925, '0.004453254502681249'],
+ [1495705594.925, '0.005799134293959226'],
+ [1495705654.925, '0.005340865929502478'],
+ [1495705714.925, '0.004911654761904942'],
+ [1495705774.925, '0.005888234873953261'],
+ [1495705834.925, '0.005565283333332954'],
+ [1495705894.925, '0.005522869047618869'],
+ [1495705954.925, '0.005177549737621646'],
+ [1495706014.925, '0.0053145810232096465'],
+ [1495706074.925, '0.004751095238095275'],
+ [1495706134.925, '0.006242077142856976'],
+ [1495706194.925, '0.00621034406957871'],
+ [1495706254.925, '0.006887592738978596'],
+ [1495706314.925, '0.006328128779726213'],
+ [1495706374.925, '0.007488363809523927'],
+ [1495706434.925, '0.006193758571428157'],
+ [1495706494.925, '0.0068798371839706935'],
+ [1495706554.925, '0.005757034340423128'],
+ [1495706614.925, '0.004571388497294698'],
+ [1495706674.925, '0.00620283044923395'],
+ [1495706734.925, '0.005607562380952455'],
+ [1495706794.925, '0.005506969933620308'],
+ [1495706854.925, '0.005621118095238131'],
+ [1495706914.925, '0.004876606098698849'],
+ [1495706974.925, '0.0047871205988517206'],
+ [1495707034.925, '0.00526405939458784'],
+ [1495707094.925, '0.005716323800605852'],
+ [1495707154.925, '0.005301459523809575'],
+ [1495707214.925, '0.0051613042857144905'],
+ [1495707274.925, '0.005384792857142714'],
+ [1495707334.925, '0.005259719047619222'],
+ [1495707394.925, '0.00584101142857182'],
+ [1495707454.925, '0.0060066121920326326'],
+ [1495707514.925, '0.006359978571428453'],
+ [1495707574.925, '0.006315876322151109'],
+ [1495707634.925, '0.005590012517198831'],
+ [1495707694.925, '0.005517419877137072'],
+ [1495707754.925, '0.006089813430348506'],
+ [1495707814.925, '0.00466754476190479'],
+ [1495707874.925, '0.006059954380517721'],
+ [1495707934.925, '0.005085657142856972'],
+ [1495707994.925, '0.005897665238095296'],
+ [1495708054.925, '0.0062282023199555885'],
+ [1495708114.925, '0.00526214553236979'],
+ [1495708174.925, '0.0044803300000000644'],
+ [1495708234.925, '0.005421443333333592'],
+ [1495708294.925, '0.005694326244512144'],
+ [1495708354.925, '0.005527721904761457'],
+ [1495708414.925, '0.005988819523809819'],
+ [1495708474.925, '0.005484704285714448'],
+ [1495708534.925, '0.005041123649230085'],
+ [1495708594.925, '0.005717767639612059'],
+ [1495708654.925, '0.005412954417342863'],
+ [1495708714.925, '0.005833343333333254'],
+ [1495708774.925, '0.005448135238094969'],
+ [1495708834.925, '0.005117341428571432'],
+ [1495708894.925, '0.005888345825277833'],
+ [1495708954.925, '0.005398543809524135'],
+ [1495709014.925, '0.005325611428571416'],
+ [1495709074.925, '0.005848668571428527'],
+ [1495709134.925, '0.005135003105145044'],
+ [1495709194.925, '0.0054551400000003'],
+ [1495709254.925, '0.005319472937322171'],
+ [1495709314.925, '0.00585677857142792'],
+ [1495709374.925, '0.0062146261904759215'],
+ [1495709434.925, '0.0067105060904182265'],
+ [1495709494.925, '0.005829691904762108'],
+ [1495709554.925, '0.005719280952381261'],
+ [1495709614.925, '0.005682603793416407'],
+ [1495709674.925, '0.0055272846277326934'],
+ [1495709734.925, '0.0057123680952386735'],
+ [1495709794.925, '0.00520597958075818'],
+ [1495709854.925, '0.005584358957263837'],
+ [1495709914.925, '0.005601104275197466'],
+ [1495709974.925, '0.005991657142857066'],
+ [1495710034.925, '0.00553722238095218'],
+ [1495710094.925, '0.005127883122696293'],
+ [1495710154.925, '0.005498111927534584'],
+ [1495710214.925, '0.005609934069084202'],
+ [1495710274.925, '0.00459206285714307'],
+ [1495710334.925, '0.0047910828571428084'],
+ [1495710394.925, '0.0056014671288845685'],
+ [1495710454.925, '0.005686936791078528'],
+ [1495710514.925, '0.00444480476190448'],
+ [1495710574.925, '0.005780394696738921'],
+ [1495710634.925, '0.0053107227550210365'],
+ [1495710694.925, '0.005096031495761817'],
+ [1495710754.925, '0.005451377979091524'],
+ [1495710814.925, '0.005328136666667083'],
+ [1495710874.925, '0.006020612857143043'],
+ [1495710934.925, '0.0061063585714285365'],
+ [1495710994.925, '0.006018346015752312'],
+ [1495711054.925, '0.005069130952381193'],
+ [1495711114.925, '0.005458406190476052'],
+ [1495711174.925, '0.00577219190476179'],
+ [1495711234.925, '0.005760814645658314'],
+ [1495711294.925, '0.005371875716579101'],
+ [1495711354.925, '0.0064232666666665834'],
+ [1495711414.925, '0.009369806836906667'],
+ [1495711474.925, '0.008956864761904692'],
+ [1495711534.925, '0.005266849368559271'],
+ [1495711594.925, '0.005335111364934262'],
+ [1495711654.925, '0.006461778319586945'],
+ [1495711714.925, '0.004687939890762393'],
+ [1495711774.925, '0.004438831245760684'],
+ [1495711834.925, '0.005142786666666613'],
+ [1495711894.925, '0.007257734212054963'],
+ [1495711954.925, '0.005621991904761494'],
+ [1495712014.925, '0.007868689999999862'],
+ [1495712074.925, '0.00910970215275738'],
+ [1495712134.925, '0.006151004285714278'],
+ [1495712194.925, '0.005447120924961522'],
+ [1495712254.925, '0.005150705153929503'],
+ [1495712314.925, '0.006358108714969314'],
+ [1495712374.925, '0.0057725354795696475'],
+ [1495712434.925, '0.005232139047619015'],
+ [1495712494.925, '0.004932809617949037'],
+ [1495712554.925, '0.004511607508499662'],
+ [1495712614.925, '0.00440487701522666'],
+ [1495712674.925, '0.005479113333333174'],
+ [1495712734.925, '0.004726317619047547'],
+ [1495712794.925, '0.005582041102958029'],
+ [1495712854.925, '0.006381481216082099'],
+ [1495712914.925, '0.005474260014095208'],
+ [1495712974.925, '0.00567597142857188'],
+ [1495713034.925, '0.0064741233333332985'],
+ [1495713094.925, '0.005467475714285271'],
+ [1495713154.925, '0.004868648393824457'],
+ [1495713214.925, '0.005254923286444893'],
+ [1495713274.925, '0.005599217150312865'],
+ [1495713334.925, '0.005105413720618919'],
+ [1495713394.925, '0.007246073333333279'],
+ [1495713454.925, '0.005990312380952272'],
+ [1495713514.925, '0.005594601853351101'],
+ [1495713574.925, '0.004739258673727054'],
+ [1495713634.925, '0.003932121428571783'],
+ [1495713694.925, '0.005018188268459395'],
+ [1495713754.925, '0.004538238095237985'],
+ [1495713814.925, '0.00561816643265435'],
+ [1495713874.925, '0.0063132584495033586'],
+ [1495713934.925, '0.00442385238095213'],
+ [1495713994.925, '0.004181795887658453'],
+ [1495714054.925, '0.004437759047619037'],
+ [1495714114.925, '0.006421748157178241'],
+ [1495714174.925, '0.006525143809523842'],
+ [1495714234.925, '0.004715904935144247'],
+ [1495714294.925, '0.005966040152763461'],
+ [1495714354.925, '0.005614535466921674'],
+ [1495714414.925, '0.004934375119415906'],
+ [1495714474.925, '0.0054122933333327385'],
+ [1495714534.925, '0.004926540699612279'],
+ [1495714594.925, '0.006124649517134237'],
+ [1495714654.925, '0.004629427092013995'],
+ [1495714714.925, '0.005117951257607005'],
+ [1495714774.925, '0.004868774512685422'],
+ [1495714834.925, '0.005310093333333399'],
+ [1495714894.925, '0.0054907752286127345'],
+ [1495714954.925, '0.004597678117351089'],
+ [1495715014.925, '0.0059622552380952'],
+ [1495715074.925, '0.005352457072655368'],
+ [1495715134.925, '0.005491630952381143'],
+ [1495715194.925, '0.006391770078379791'],
+ [1495715254.925, '0.005933472857142518'],
+ [1495715314.925, '0.005301314285714163'],
+ [1495715374.925, '0.0058352959724814165'],
+ [1495715434.925, '0.006154755147867044'],
+ [1495715494.925, '0.009391935637482038'],
+ [1495715554.925, '0.007846462857142592'],
+ [1495715614.925, '0.00477608215316353'],
+ [1495715674.925, '0.006132865238094998'],
+ [1495715734.925, '0.006159762457649516'],
+ [1495715794.925, '0.005957307073265968'],
+ [1495715854.925, '0.006652319091792501'],
+ [1495715914.925, '0.005493557402895287'],
+ [1495715974.925, '0.0058652434829145166'],
+ [1495716034.925, '0.005627400430468021'],
+ [1495716094.925, '0.006240656190475609'],
+ [1495716154.925, '0.006305997676168624'],
+ [1495716214.925, '0.005388057732783248'],
+ [1495716274.925, '0.0052814916048421244'],
+ [1495716334.925, '0.00699498614272497'],
+ [1495716394.925, '0.00627768693035141'],
+ [1495716454.925, '0.0042411487048161145'],
+ [1495716514.925, '0.005348647473627653'],
+ [1495716574.925, '0.0047176657142853975'],
+ [1495716634.925, '0.004437898571428686'],
+ [1495716694.925, '0.004923527366927261'],
+ [1495716754.925, '0.005131935066048421'],
+ [1495716814.925, '0.005046949523809611'],
+ [1495716874.925, '0.00547184095238092'],
+ [1495716934.925, '0.005224140016380444'],
+ [1495716994.925, '0.005297991171665292'],
+ [1495717054.925, '0.005492965995623498'],
+ [1495717114.925, '0.005754660000000403'],
+ [1495717174.925, '0.005949557138639285'],
+ [1495717234.925, '0.006091816112534666'],
+ [1495717294.925, '0.005554210080192063'],
+ [1495717354.925, '0.006411504395279871'],
+ [1495717414.925, '0.006319643996609606'],
+ [1495717474.925, '0.005539174405717675'],
+ [1495717534.925, '0.0053157078842772255'],
+ [1495717594.925, '0.005247480952381066'],
+ [1495717654.925, '0.004820141620396252'],
+ [1495717714.925, '0.005906173868322844'],
+ [1495717774.925, '0.006173117219570961'],
+ [1495717834.925, '0.005963340952380661'],
+ [1495717894.925, '0.005698976627681527'],
+ [1495717954.925, '0.004751279096346378'],
+ [1495718014.925, '0.005733142379359711'],
+ [1495718074.925, '0.004831689010348035'],
+ [1495718134.925, '0.005188370476191092'],
+ [1495718194.925, '0.004793227554547938'],
+ [1495718254.925, '0.003997442857142731'],
+ [1495718314.925, '0.004386040132951264'],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
],
- 'last_update': '2017-05-25T13:18:34.949Z'
+ last_update: '2017-05-25T13:18:34.949Z',
};
export default metricsGroupsAPIResponse;
@@ -2432,41 +651,44 @@ export const deploymentData = [
id: 111,
iid: 3,
sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
- commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ commitUrl:
+ 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
ref: {
- name: 'master'
+ name: 'master',
},
created_at: '2017-05-31T21:23:37.881Z',
tag: false,
tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
- 'last?': true
+ 'last?': true,
},
{
id: 110,
iid: 2,
sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
- commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ commitUrl:
+ 'http://test.host/frontend-fixtures/environments-project/commit/f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
ref: {
- name: 'master'
+ name: 'master',
},
created_at: '2017-05-30T20:08:04.629Z',
tag: false,
- tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
- 'last?': false
+ tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
+ 'last?': false,
},
{
id: 109,
iid: 1,
sha: '6511e58faafaa7ad2228990ec57f19d66f7db7c2',
- commitUrl: 'http://test.host/frontend-fixtures/environments-project/commit/6511e58faafaa7ad2228990ec57f19d66f7db7c2',
+ commitUrl:
+ 'http://test.host/frontend-fixtures/environments-project/commit/6511e58faafaa7ad2228990ec57f19d66f7db7c2',
ref: {
- name: 'update2-readme'
+ name: 'update2-readme',
},
created_at: '2017-05-30T17:42:38.409Z',
tag: false,
tagUrl: 'http://test.host/frontend-fixtures/environments-project/tags/false',
- 'last?': false
- }
+ 'last?': false,
+ },
];
export const statePaths = {
@@ -2476,5844 +698,5844 @@ export const statePaths = {
};
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'
- }
- ]
- },
- ],
- 'when': [
- {
- 'value': 'hundred(s)',
- 'color': 'green',
- },
- ],
- }
- ]
- },
- {
- '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-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'
- }
- ]
- }
- ]
- }
- ]
- }
+ {
+ 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',
+ },
+ ],
+ },
+ ],
+ when: [
+ {
+ value: 'hundred(s)',
+ color: 'green',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ 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-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) => {
+ convertedResult = column.queries[0].result.map(resultObj => {
const convertedMetrics = {};
convertedMetrics.values = resultObj.values.map(val => ({
- time: new Date(val.time),
- value: val.value,
+ time: new Date(val.time),
+ value: val.value,
}));
convertedMetrics.metric = resultObj.metric;
return convertedMetrics;
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 2d88cee61f1..24388fba219 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -52,6 +52,7 @@ export const noteableDataMock = {
updated_at: '2017-08-04T09:53:01.226Z',
updated_by_id: 1,
web_url: '/gitlab-org/gitlab-ce/issues/26',
+ noteableType: 'issue',
};
export const lastFetchedAt = '1501862675';
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index e8fcd4b1a36..581209f215d 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -1,25 +1,30 @@
import Vue from 'vue';
import actionComponent from '~/pipelines/components/graph/action_component.vue';
+import eventHub from '~/pipelines/event_hub';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph action component', () => {
let component;
beforeEach((done) => {
const ActionComponent = Vue.extend(actionComponent);
- component = new ActionComponent({
- propsData: {
- tooltipText: 'bar',
- link: 'foo',
- actionMethod: 'post',
- actionIcon: 'cancel',
- },
- }).$mount();
+ component = mountComponent(ActionComponent, {
+ tooltipText: 'bar',
+ link: 'foo',
+ actionIcon: 'cancel',
+ });
Vue.nextTick(done);
});
- it('should render a link', () => {
- expect(component.$el.getAttribute('href')).toEqual('foo');
+ afterEach(() => {
+ component.$destroy();
+ });
+
+ it('should emit an event with the provided link', () => {
+ eventHub.$on('graphAction', (link) => {
+ expect(link).toEqual('foo');
+ });
});
it('should render the provided title as a bootstrap tooltip', () => {
diff --git a/spec/javascripts/profile/account/components/update_username_spec.js b/spec/javascripts/profile/account/components/update_username_spec.js
new file mode 100644
index 00000000000..bac306edf5a
--- /dev/null
+++ b/spec/javascripts/profile/account/components/update_username_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+
+import updateUsername from '~/profile/account/components/update_username.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('UpdateUsername component', () => {
+ const rootUrl = gl.TEST_HOST;
+ const actionUrl = `${gl.TEST_HOST}/update/username`;
+ const username = 'hasnoname';
+ const newUsername = 'new_username';
+ let Component;
+ let vm;
+ let axiosMock;
+
+ beforeEach(() => {
+ axiosMock = new MockAdapter(axios);
+ Component = Vue.extend(updateUsername);
+ vm = mountComponent(Component, {
+ actionUrl,
+ rootUrl,
+ initialUsername: username,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ axiosMock.restore();
+ });
+
+ const findElements = () => {
+ const modalSelector = `#${vm.$options.modalId}`;
+
+ return {
+ input: vm.$el.querySelector(`#${vm.$options.inputId}`),
+ openModalBtn: vm.$el.querySelector(`[data-target="${modalSelector}"]`),
+ modal: vm.$el.querySelector(modalSelector),
+ modalBody: vm.$el.querySelector(`${modalSelector} .modal-body`),
+ modalHeader: vm.$el.querySelector(`${modalSelector} .modal-title`),
+ confirmModalBtn: vm.$el.querySelector(`${modalSelector} .btn-warning`),
+ };
+ };
+
+ it('has a disabled button if the username was not changed', done => {
+ const { input, openModalBtn } = findElements();
+ input.dispatchEvent(new Event('input'));
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.username).toBe(username);
+ expect(vm.newUsername).toBe(username);
+ expect(openModalBtn).toBeDisabled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('has an enabled button which if the username was changed', done => {
+ const { input, openModalBtn } = findElements();
+ input.value = newUsername;
+ input.dispatchEvent(new Event('input'));
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.username).toBe(username);
+ expect(vm.newUsername).toBe(newUsername);
+ expect(openModalBtn).not.toBeDisabled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('confirmation modal contains proper header and body', done => {
+ const { modalBody, modalHeader } = findElements();
+
+ vm.newUsername = newUsername;
+
+ Vue.nextTick()
+ .then(() => {
+ expect(modalHeader.textContent).toContain('Change username?');
+ expect(modalBody.textContent).toContain(
+ `You are going to change the username ${username} to ${newUsername}`,
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('confirmation modal should escape usernames properly', done => {
+ const { modalBody } = findElements();
+
+ vm.username = vm.newUsername = '<i>Italic</i>';
+
+ Vue.nextTick()
+ .then(() => {
+ expect(modalBody.innerHTML).toContain('&lt;i&gt;Italic&lt;/i&gt;');
+ expect(modalBody.innerHTML).not.toContain(vm.username);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('executes API call on confirmation button click', done => {
+ const { confirmModalBtn } = findElements();
+
+ axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]);
+ spyOn(axios, 'put').and.callThrough();
+
+ vm.newUsername = newUsername;
+
+ Vue.nextTick()
+ .then(() => {
+ confirmModalBtn.click();
+ expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('sets the username after a successful update', done => {
+ const { input, openModalBtn } = findElements();
+
+ axiosMock.onPut(actionUrl).replyOnce(() => {
+ expect(input).toBeDisabled();
+ expect(openModalBtn).toBeDisabled();
+
+ return [200, { message: 'Username changed' }];
+ });
+
+ vm.newUsername = newUsername;
+
+ vm
+ .onConfirm()
+ .then(() => {
+ expect(vm.username).toBe(newUsername);
+ expect(vm.newUsername).toBe(newUsername);
+ expect(input).not.toBeDisabled();
+ expect(input.value).toBe(newUsername);
+ expect(openModalBtn).toBeDisabled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not set the username after a erroneous update', done => {
+ const { input, openModalBtn } = findElements();
+
+ axiosMock.onPut(actionUrl).replyOnce(() => {
+ expect(input).toBeDisabled();
+ expect(openModalBtn).toBeDisabled();
+
+ return [400, { message: 'Invalid username' }];
+ });
+
+ const invalidUsername = 'anything.git';
+ vm.newUsername = invalidUsername;
+
+ vm
+ .onConfirm()
+ .then(() => done.fail('Expected onConfirm to throw!'))
+ .catch(() => {
+ expect(vm.username).toBe(username);
+ expect(vm.newUsername).toBe(invalidUsername);
+ expect(input).not.toBeDisabled();
+ expect(input.value).toBe(invalidUsername);
+ expect(openModalBtn).not.toBeDisabled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 40115792652..1a27955983d 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -6,8 +6,21 @@ import SearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
-(function() {
- var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
+describe('Search autocomplete dropdown', () => {
+ var assertLinks,
+ dashboardIssuesPath,
+ dashboardMRsPath,
+ groupIssuesPath,
+ groupMRsPath,
+ groupName,
+ mockDashboardOptions,
+ mockGroupOptions,
+ mockProjectOptions,
+ projectIssuesPath,
+ projectMRsPath,
+ projectName,
+ userId,
+ widget;
var userName = 'root';
widget = null;
@@ -66,133 +79,126 @@ import * as urlUtils from '~/lib/utils/url_utility';
// Mock `gl` object in window for dashboard specific page. App code will need it.
mockDashboardOptions = function() {
window.gl || (window.gl = {});
- return window.gl.dashboardOptions = {
+ return (window.gl.dashboardOptions = {
issuesPath: dashboardIssuesPath,
- mrPath: dashboardMRsPath
- };
+ mrPath: dashboardMRsPath,
+ });
};
// Mock `gl` object in window for project specific page. App code will need it.
mockProjectOptions = function() {
window.gl || (window.gl = {});
- return window.gl.projectOptions = {
+ return (window.gl.projectOptions = {
'gitlab-ce': {
issuesPath: projectIssuesPath,
mrPath: projectMRsPath,
- projectName: projectName
- }
- };
+ projectName: projectName,
+ },
+ });
};
mockGroupOptions = function() {
window.gl || (window.gl = {});
- return window.gl.groupOptions = {
+ return (window.gl.groupOptions = {
'gitlab-org': {
issuesPath: groupIssuesPath,
mrPath: groupMRsPath,
- projectName: groupName
- }
- };
+ projectName: groupName,
+ },
+ });
};
assertLinks = function(list, issuesPath, mrsPath) {
- var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
if (issuesPath) {
- issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
- issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
- a1 = "a[href='" + issuesAssignedToMeLink + "']";
- a2 = "a[href='" + issuesIHaveCreatedLink + "']";
- expect(list.find(a1).length).toBe(1);
- expect(list.find(a1).text()).toBe('Issues assigned to me');
- expect(list.find(a2).length).toBe(1);
- expect(list.find(a2).text()).toBe("Issues I've created");
+ const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_id=${userId}"]`;
+ const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_id=${userId}"]`;
+ expect(list.find(issuesAssignedToMeLink).length).toBe(1);
+ expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me');
+ expect(list.find(issuesIHaveCreatedLink).length).toBe(1);
+ expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created");
}
- mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName;
- mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName;
- a3 = "a[href='" + mrsAssignedToMeLink + "']";
- a4 = "a[href='" + mrsIHaveCreatedLink + "']";
- expect(list.find(a3).length).toBe(1);
- expect(list.find(a3).text()).toBe('Merge requests assigned to me');
- expect(list.find(a4).length).toBe(1);
- return expect(list.find(a4).text()).toBe("Merge requests I've created");
+ const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_id=${userId}"]`;
+ const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_id=${userId}"]`;
+ expect(list.find(mrsAssignedToMeLink).length).toBe(1);
+ expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me');
+ expect(list.find(mrsIHaveCreatedLink).length).toBe(1);
+ expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
};
- describe('Search autocomplete dropdown', function() {
- preloadFixtures('static/search_autocomplete.html.raw');
- beforeEach(function() {
- loadFixtures('static/search_autocomplete.html.raw');
+ preloadFixtures('static/search_autocomplete.html.raw');
+ beforeEach(function() {
+ loadFixtures('static/search_autocomplete.html.raw');
- // Prevent turbolinks from triggering within gl_dropdown
- spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+ // Prevent turbolinks from triggering within gl_dropdown
+ spyOn(urlUtils, 'visitUrl').and.returnValue(true);
- window.gon = {};
- window.gon.current_user_id = userId;
- window.gon.current_username = userName;
+ window.gon = {};
+ window.gon.current_user_id = userId;
+ window.gon.current_username = userName;
- return widget = new SearchAutocomplete();
- });
+ return (widget = new SearchAutocomplete());
+ });
- afterEach(function() {
- // Undo what we did to the shared <body>
- removeBodyAttributes();
- window.gon = {};
- });
- it('should show Dashboard specific dropdown menu', function() {
- var list;
- addBodyAttributes();
- mockDashboardOptions();
- widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
- });
- it('should show Group specific dropdown menu', function() {
- var list;
- addBodyAttributes('group');
- mockGroupOptions();
- widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, groupIssuesPath, groupMRsPath);
- });
- it('should show Project specific dropdown menu', function() {
- var list;
- addBodyAttributes('project');
- mockProjectOptions();
- widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
- return assertLinks(list, projectIssuesPath, projectMRsPath);
- });
- it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
- addBodyAttributes('project');
- disableProjectIssues();
- mockProjectOptions();
- widget.searchInput.triggerHandler('focus');
- const list = widget.wrap.find('.dropdown-menu').find('ul');
- assertLinks(list, null, projectMRsPath);
- });
- it('should not show category related menu if there is text in the input', function() {
- var link, list;
- addBodyAttributes('project');
- mockProjectOptions();
- widget.searchInput.val('help');
- widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
- link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
- return expect(list.find(link).length).toBe(0);
- });
- return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
- var ENTER = 13;
- var DOWN = 40;
- addBodyAttributes();
- mockDashboardOptions(true);
- var submitSpy = spyOnEvent('form', 'submit');
- widget.searchInput.triggerHandler('focus');
- widget.wrap.trigger($.Event('keydown', { which: DOWN }));
- var enterKeyEvent = $.Event('keydown', { which: ENTER });
- widget.searchInput.trigger(enterKeyEvent);
- // This does not currently catch failing behavior. For security reasons,
- // browsers will not trigger default behavior (form submit, in this
- // example) on JavaScript-created keypresses.
- expect(submitSpy).not.toHaveBeenTriggered();
- });
+ afterEach(function() {
+ // Undo what we did to the shared <body>
+ removeBodyAttributes();
+ window.gon = {};
+ });
+ it('should show Dashboard specific dropdown menu', function() {
+ var list;
+ addBodyAttributes();
+ mockDashboardOptions();
+ widget.searchInput.triggerHandler('focus');
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
+ });
+ it('should show Group specific dropdown menu', function() {
+ var list;
+ addBodyAttributes('group');
+ mockGroupOptions();
+ widget.searchInput.triggerHandler('focus');
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, groupIssuesPath, groupMRsPath);
+ });
+ it('should show Project specific dropdown menu', function() {
+ var list;
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.triggerHandler('focus');
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, projectIssuesPath, projectMRsPath);
+ });
+ it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
+ addBodyAttributes('project');
+ disableProjectIssues();
+ mockProjectOptions();
+ widget.searchInput.triggerHandler('focus');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ assertLinks(list, null, projectMRsPath);
+ });
+ it('should not show category related menu if there is text in the input', function() {
+ var link, list;
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.val('help');
+ widget.searchInput.triggerHandler('focus');
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']";
+ return expect(list.find(link).length).toBe(0);
+ });
+ it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
+ var ENTER = 13;
+ var DOWN = 40;
+ addBodyAttributes();
+ mockDashboardOptions(true);
+ var submitSpy = spyOnEvent('form', 'submit');
+ widget.searchInput.triggerHandler('focus');
+ widget.wrap.trigger($.Event('keydown', { which: DOWN }));
+ var enterKeyEvent = $.Event('keydown', { which: ENTER });
+ widget.searchInput.trigger(enterKeyEvent);
+ // This does not currently catch failing behavior. For security reasons,
+ // browsers will not trigger default behavior (form submit, in this
+ // example) on JavaScript-created keypresses.
+ expect(submitSpy).not.toHaveBeenTriggered();
});
-}).call(window);
+});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 1bcfdfe72b6..d158786e484 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -7,6 +7,9 @@ import Vue from 'vue';
import VueResource from 'vue-resource';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
+import { FIXTURES_PATH, TEST_HOST } from './test_constants';
+
+import customMatchers from './matchers';
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
@@ -27,15 +30,17 @@ Vue.config.errorHandler = function (err) {
Vue.use(VueResource);
// enable test fixtures
-jasmine.getFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
-jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
+jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
+jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
+
+beforeAll(() => jasmine.addMatchers(customMatchers));
// globalize common libraries
window.$ = window.jQuery = $;
// stub expected globals
window.gl = window.gl || {};
-window.gl.TEST_HOST = 'http://test.host';
+window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
new file mode 100644
index 00000000000..df59195e9f6
--- /dev/null
+++ b/spec/javascripts/test_constants.js
@@ -0,0 +1,4 @@
+export const FIXTURES_PATH = '/base/spec/javascripts/fixtures';
+export const TEST_HOST = 'http://test.host';
+
+export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`;
diff --git a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
index c7c454a0b45..383f0cd29ea 100644
--- a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
@@ -38,4 +38,33 @@ describe('ContentViewer', () => {
done();
});
});
+
+ it('renders image preview', done => {
+ createComponent({
+ path: 'test.jpg',
+ fileSize: 1024,
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.image_file img').getAttribute('src')).toBe('test.jpg');
+
+ done();
+ });
+ });
+
+ it('renders fallback download control', done => {
+ createComponent({
+ path: 'test.abc',
+ fileSize: 1024,
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain(
+ 'test.abc (1.00 KiB)',
+ );
+ expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toContain('Download');
+
+ done();
+ });
+ });
});
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 35f8792ff35..b18af806118 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -207,4 +207,35 @@ describe Banzai::Filter::CommitReferenceFilter do
expect(reference_filter(act).to_html).to match(%r{<a.+>#{Regexp.escape(invalidate_reference(reference))}</a>})
end
end
+
+ context 'URL reference for a commit patch' do
+ let(:namespace) { create(:namespace) }
+ let(:project2) { create(:project, :public, :repository, namespace: namespace) }
+ let(:commit) { project2.commit }
+ let(:link) { urls.project_commit_url(project2, commit.id) }
+ let(:extension) { '.patch' }
+ let(:reference) { link + extension }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href'))
+ .to eq reference
+ end
+
+ it 'has valid text' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.text).to eq("See #{commit.reference_link_text(project)} (patch)")
+ end
+
+ it 'does not link to patch when extension match is after the path' do
+ invalidate_commit_reference = reference_filter("#{link}/builds.patch")
+
+ doc = reference_filter("See (#{invalidate_commit_reference})")
+
+ expect(doc.css('a').first.attr('href')).to eq "#{link}/builds"
+ expect(doc.text).to eq("See (#{commit.reference_link_text(project)} (builds).patch)")
+ end
+ end
end
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
new file mode 100644
index 00000000000..cf40c467c72
--- /dev/null
+++ b/spec/lib/forever_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Forever do
+ describe '.date' do
+ subject { described_class.date }
+
+ context 'when using PostgreSQL' do
+ it 'should return Postgresql future date' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ expect(subject).to eq(described_class::POSTGRESQL_DATE)
+ end
+ end
+
+ context 'when using MySQL' do
+ it 'should return MySQL future date' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ expect(subject).to eq(described_class::MYSQL_DATE)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 18cef8ec996..9ccd0b206cc 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Auth do
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
- expect(subject::API_SCOPES).to eq %i[api read_user sudo]
+ expect(subject::API_SCOPES).to eq %i[api read_user sudo read_repository]
end
it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
@@ -19,7 +19,7 @@ describe Gitlab::Auth do
it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true)
- expect(subject.optional_scopes).to eq %i[read_user sudo read_registry openid]
+ expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid]
end
context 'registry_scopes' do
@@ -231,7 +231,7 @@ describe Gitlab::Auth do
.to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
- it 'falls through oauth authentication when the username is oauth2' do
+ it 'fails through oauth authentication when the username is oauth2' do
user = create(
:user,
username: 'oauth2',
@@ -255,6 +255,122 @@ describe Gitlab::Auth do
expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
end
+
+ context 'while using deploy tokens' do
+ let(:project) { create(:project) }
+ let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
+
+ context 'when the deploy token has read_repository as scope' do
+ let(:deploy_token) { create(:deploy_token, read_registry: false, projects: [project]) }
+ let(:login) { deploy_token.username }
+
+ it 'succeeds when login and token are valid' do
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login)
+ expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails when login is not valid' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
+ expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails when token is not valid' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token is nil' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, nil, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token is not related to project' do
+ another_deploy_token = create(:deploy_token)
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, another_deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token has been revoked' do
+ deploy_token.revoke!
+
+ expect(deploy_token.revoked?).to be_truthy
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
+ expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+
+ context 'when the deploy token has read_registry as a scope' do
+ let(:deploy_token) { create(:deploy_token, read_repository: false, projects: [project]) }
+ let(:login) { deploy_token.username }
+
+ context 'when registry enabled' do
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ it 'succeeds when login and token are valid' do
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:read_container_image])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login)
+ expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails when login is not valid' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
+ expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails when token is not valid' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token is nil' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token is not related to project' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+
+ it 'fails if token has been revoked' do
+ deploy_token.revoke!
+
+ expect(deploy_token.revoked?).to be_truthy
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
+ expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+
+ context 'when registry disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it 'fails when login and token are valid' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
+ expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+ end
+ end
end
describe 'find_with_user_password' do
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 3ef0b6817e9..78d6fa65b5a 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -90,6 +90,10 @@ describe Gitlab::Ci::Status::Build::Cancelable do
describe '#action_title' do
it { expect(subject.action_title).to eq 'Cancel' }
end
+
+ describe '#action_button_title' do
+ it { expect(subject.action_button_title).to eq 'Cancel this job' }
+ end
end
describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
new file mode 100644
index 00000000000..c6b5cc68770
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Canceled do
+ let(:user) { create(:user) }
+
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title) }
+ end
+
+ describe '.matches?' do
+ subject {described_class.matches?(build, user) }
+
+ context 'when build is canceled' do
+ let(:build) { create(:ci_build, :canceled) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not canceled' do
+ let(:build) { create(:ci_build) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
new file mode 100644
index 00000000000..8bdfe6ef7a2
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Created do
+ let(:user) { create(:user) }
+
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject {described_class.matches?(build, user) }
+
+ context 'when build is created' do
+ let(:build) { create(:ci_build, :created) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not created' do
+ let(:build) { create(:ci_build) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
new file mode 100644
index 00000000000..0acd271e375
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Erased do
+ let(:user) { create(:user) }
+
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title) }
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, user) }
+
+ context 'when build is erased' do
+ let(:build) { create(:ci_build, :success, :erased) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not erased' do
+ let(:build) { create(:ci_build, :success, :trace_artifact) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index bbfa60169a1..6d5b73bb01b 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when build is successful' do
- let(:build) { create(:ci_build, :success) }
+ let(:build) { create(:ci_build, :success, :trace_artifact) }
it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
@@ -38,6 +38,33 @@ describe Gitlab::Ci::Status::Build::Factory do
end
end
+ context 'when build is erased' do
+ let(:build) { create(:ci_build, :success, :erased) }
+
+ it 'matches correct core status' do
+ expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+ end
+
+ it 'matches correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Build::Erased,
+ Gitlab::Ci::Status::Build::Retryable]
+ end
+
+ it 'fabricates a retryable build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+ end
+
+ it 'fabricates status with correct details' do
+ expect(status.text).to eq 'passed'
+ expect(status.icon).to eq 'status_success'
+ expect(status.favicon).to eq 'favicon_status_success'
+ expect(status.label).to eq 'passed'
+ expect(status).to have_details
+ expect(status).to have_action
+ end
+ end
+
context 'when build is failed' do
context 'when build is not allowed to fail' do
let(:build) { create(:ci_build, :failed) }
@@ -106,7 +133,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Retryable]
+ .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable]
end
it 'fabricates a retryable build status' do
@@ -117,6 +144,7 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.text).to eq 'canceled'
expect(status.icon).to eq 'status_canceled'
expect(status.favicon).to eq 'favicon_status_canceled'
+ expect(status.illustration).to include(:image, :size, :title)
expect(status.label).to eq 'canceled'
expect(status).to have_details
expect(status).to have_action
@@ -158,7 +186,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Cancelable]
+ .to eq [Gitlab::Ci::Status::Build::Pending, Gitlab::Ci::Status::Build::Cancelable]
end
it 'fabricates a cancelable build status' do
@@ -169,6 +197,7 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.text).to eq 'pending'
expect(status.icon).to eq 'status_pending'
expect(status.favicon).to eq 'favicon_status_pending'
+ expect(status.illustration).to include(:image, :size, :title, :content)
expect(status.label).to eq 'pending'
expect(status).to have_details
expect(status).to have_action
@@ -182,18 +211,19 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
end
- it 'does not match extended statuses' do
- expect(factory.extended_statuses).to be_empty
+ it 'matches correct extended statuses' do
+ expect(factory.extended_statuses).to eq [Gitlab::Ci::Status::Build::Skipped]
end
- it 'fabricates a core skipped status' do
- expect(status).to be_a Gitlab::Ci::Status::Skipped
+ it 'fabricates a skipped build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::Skipped
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'skipped'
expect(status.icon).to eq 'status_skipped'
expect(status.favicon).to eq 'favicon_status_skipped'
+ expect(status.illustration).to include(:image, :size, :title)
expect(status.label).to eq 'skipped'
expect(status).to have_details
expect(status).not_to have_action
@@ -210,7 +240,8 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Play,
+ .to eq [Gitlab::Ci::Status::Build::Manual,
+ Gitlab::Ci::Status::Build::Play,
Gitlab::Ci::Status::Build::Action]
end
@@ -223,6 +254,7 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
+ expect(status.illustration).to include(:image, :size, :title, :content)
expect(status.label).to include 'manual play action'
expect(status).to have_details
expect(status.action_path).to include 'play'
@@ -257,7 +289,8 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Stop,
+ .to eq [Gitlab::Ci::Status::Build::Manual,
+ Gitlab::Ci::Status::Build::Stop,
Gitlab::Ci::Status::Build::Action]
end
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
new file mode 100644
index 00000000000..6386296f992
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Manual do
+ let(:user) { create(:user) }
+
+ subject do
+ build = create(:ci_build, :manual)
+ described_class.new(Gitlab::Ci::Status::Core.new(build, user))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject {described_class.matches?(build, user) }
+
+ context 'when build is manual' do
+ let(:build) { create(:ci_build, :manual) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not manual' do
+ let(:build) { create(:ci_build) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
new file mode 100644
index 00000000000..4cf70828e53
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Pending do
+ let(:user) { create(:user) }
+
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject {described_class.matches?(build, user) }
+
+ context 'when build is pending' do
+ let(:build) { create(:ci_build, :pending) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not pending' do
+ let(:build) { create(:ci_build, :success) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index 35e47cd2526..f128c1d4ca4 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -69,6 +69,10 @@ describe Gitlab::Ci::Status::Build::Play do
it { expect(subject.action_title).to eq 'Play' }
end
+ describe '#action_button_title' do
+ it { expect(subject.action_button_title).to eq 'Trigger this manual action' }
+ end
+
describe '.matches?' do
subject { described_class.matches?(build, user) }
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 0c5099b7da5..84d98588f2d 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -90,6 +90,10 @@ describe Gitlab::Ci::Status::Build::Retryable do
describe '#action_title' do
it { expect(subject.action_title).to eq 'Retry' }
end
+
+ describe '#action_button_title' do
+ it { expect(subject.action_button_title).to eq 'Retry this job' }
+ end
end
describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
new file mode 100644
index 00000000000..46f6933025a
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Skipped do
+ let(:user) { create(:user) }
+
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title) }
+ end
+
+ describe '.matches?' do
+ subject {described_class.matches?(build, user) }
+
+ context 'when build is skipped' do
+ let(:build) { create(:ci_build, :skipped) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not skipped' do
+ let(:build) { create(:ci_build) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index f16fc5c9205..5b7534c96c1 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -44,6 +44,10 @@ describe Gitlab::Ci::Status::Build::Stop do
describe '#action_title' do
it { expect(subject.action_title).to eq 'Stop' }
end
+
+ describe '#action_button_title' do
+ it { expect(subject.action_button_title).to eq 'Stop this environment' }
+ end
end
describe '.matches?' do
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 62c1d37ea1c..778bfa2cc47 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -19,15 +19,15 @@ describe Gitlab::Database::ShaAttribute do
let(:attribute) { described_class.new }
- describe '#type_cast_from_database' do
+ describe '#deserialize' do
it 'converts the binary SHA to a String' do
- expect(attribute.type_cast_from_database(binary_from_db)).to eq(sha)
+ expect(attribute.deserialize(binary_from_db)).to eq(sha)
end
end
- describe '#type_cast_for_database' do
+ describe '#serialize' do
it 'converts a SHA String to binary data' do
- expect(attribute.type_cast_for_database(sha).to_s).to eq(binary_sha)
+ expect(attribute.serialize(sha).to_s).to eq(binary_sha)
end
end
end
diff --git a/spec/lib/gitlab/git/checksum_spec.rb b/spec/lib/gitlab/git/checksum_spec.rb
deleted file mode 100644
index 8ff310905bf..00000000000
--- a/spec/lib/gitlab/git/checksum_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Checksum, seed_helper: true do
- let(:storage) { 'default' }
-
- it 'raises Gitlab::Git::Repository::NoRepository when there is no repo' do
- checksum = described_class.new(storage, 'nonexistent-repo')
-
- expect { checksum.calculate }.to raise_error Gitlab::Git::Repository::NoRepository
- end
-
- it 'pretends that checksum is 000000... when the repo is empty' do
- FileUtils.rm_rf(File.join(SEED_STORAGE_PATH, 'empty-repo.git'))
-
- system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
-
- checksum = described_class.new(storage, 'empty-repo')
-
- expect(checksum.calculate).to eq '0000000000000000000000000000000000000000'
- end
-
- it 'raises Gitlab::Git::Repository::Failure when shelling out to git return non-zero status' do
- checksum = described_class.new(storage, 'gitlab-git-test')
-
- allow(checksum).to receive(:popen).and_return(['output', nil])
-
- expect { checksum.calculate }.to raise_error Gitlab::Git::Checksum::Failure
- end
-
- it 'calculates the checksum when there is a repo' do
- checksum = described_class.new(storage, 'gitlab-git-test')
-
- expect(checksum.calculate).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4'
- end
-end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 5cbe2808d0b..d3ab61746f4 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -247,38 +247,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it 'returns parameterised string for a ref containing slashes' do
- prefix = repository.archive_prefix('test/branch', 'SHA')
+ prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil)
expect(prefix).to eq("#{project_name}-test-branch-SHA")
end
it 'returns correct string for a ref containing dots' do
- prefix = repository.archive_prefix('test.branch', 'SHA')
+ prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil)
expect(prefix).to eq("#{project_name}-test.branch-SHA")
end
+
+ it 'returns string with sha when append_sha is false' do
+ prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false)
+
+ expect(prefix).to eq("#{project_name}-test.branch")
+ end
end
describe '#archive' do
- let(:metadata) { repository.archive_metadata('master', '/tmp') }
+ let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) }
it_should_behave_like 'archive check', '.tar.gz'
end
describe '#archive_zip' do
- let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) }
it_should_behave_like 'archive check', '.zip'
end
describe '#archive_bz2' do
- let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) }
it_should_behave_like 'archive check', '.tar.bz2'
end
describe '#archive_fallback' do
- let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) }
it_should_behave_like 'archive check', '.tar.gz'
end
@@ -2178,6 +2184,55 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#checksum' do
+ shared_examples 'calculating checksum' do
+ it 'calculates the checksum for non-empty repo' do
+ expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4'
+ end
+
+ it 'returns 0000000000000000000000000000000000000000 for an empty repo' do
+ FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git'))
+
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git),
+ chdir: storage_path,
+ out: '/dev/null',
+ err: '/dev/null')
+
+ empty_repo = described_class.new('default', 'empty-repo.git', '')
+
+ expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000'
+ end
+
+ it 'raises a no repository exception when there is no repo' do
+ broken_repo = described_class.new('default', 'a/path.git', '')
+
+ expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+ end
+
+ context 'when calculate_checksum Gitaly feature is enabled' do
+ it_behaves_like 'calculating checksum'
+ end
+
+ context 'when calculate_checksum Gitaly feature is disabled', :disable_gitaly do
+ it_behaves_like 'calculating checksum'
+
+ describe 'when storage is broken', :broken_storage do
+ it 'raises a storage exception when storage is not available' do
+ broken_repo = described_class.new('broken', 'a/path.git', '')
+
+ expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible)
+ end
+ end
+
+ it "raises a Gitlab::Git::Repository::Failure error if the `popen` call to git returns a non-zero exit code" do
+ allow(repository).to receive(:popen).and_return(['output', nil])
+
+ expect { repository.checksum }.to raise_error Gitlab::Git::Repository::ChecksumError
+ end
+ end
+ end
+
context 'gitlab_projects commands' do
let(:gitlab_projects) { repository.gitlab_projects }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
@@ -2251,6 +2306,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#clean_stale_repository_files' do
+ let(:worktree_path) { File.join(repository.path, 'worktrees', 'delete-me') }
+
+ it 'cleans up the files' do
+ repository.with_worktree(worktree_path, 'master', env: ENV) do
+ FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
+ # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
+ # but the HEAD must be 40 characters long or git will ignore it.
+ File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
+
+ # git 2.16 fails with "fatal: bad object HEAD"
+ expect { repository.rev_list(including: :all) }.to raise_error(Gitlab::Git::Repository::GitError)
+
+ repository.clean_stale_repository_files
+
+ expect { repository.rev_list(including: :all) }.not_to raise_error
+ expect(File.exist?(worktree_path)).to be_falsey
+ end
+ end
+
+ it 'increments a counter upon an error' do
+ expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
+
+ counter = double(:counter)
+
+ expect(counter).to receive(:increment)
+ expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
+ 'Number of failed repository cleanup events').and_return(counter)
+
+ repository.clean_stale_repository_files
+ end
+ end
+
describe '#delete_remote_branches' do
subject do
repository.delete_remote_branches('downstream-remote', ['master'])
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index b845abab5ef..6c625596605 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -145,6 +145,33 @@ describe Gitlab::GitAccess do
expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
end
end
+
+ context 'when actor is DeployToken' do
+ let(:actor) { create(:deploy_token, projects: [project]) }
+
+ context 'when DeployToken is active and belongs to project' do
+ it 'allows pull access' do
+ expect { pull_access_check }.not_to raise_error
+ end
+
+ it 'blocks the push' do
+ expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload])
+ end
+ end
+
+ context 'when DeployToken does not belong to project' do
+ let(:another_project) { create(:project) }
+ let(:actor) { create(:deploy_token, projects: [another_project]) }
+
+ it 'blocks pull access' do
+ expect { pull_access_check }.to raise_not_found
+ end
+
+ it 'blocks the push' do
+ expect { push_access_check }.to raise_not_found
+ end
+ end
+ end
end
context 'when actor is nil' do
@@ -594,6 +621,41 @@ describe Gitlab::GitAccess do
end
end
+ describe 'deploy token permissions' do
+ let(:deploy_token) { create(:deploy_token) }
+ let(:actor) { deploy_token }
+
+ context 'pull code' do
+ context 'when project is authorized' do
+ before do
+ deploy_token.projects << project
+ end
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when unauthorized' do
+ context 'from public project' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'from internal project' do
+ let(:project) { create(:project, :internal, :repository) }
+
+ it { expect { pull_access_check }.to raise_not_found }
+ end
+
+ context 'from private project' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it { expect { pull_access_check }.to raise_not_found }
+ end
+ end
+ end
+ end
+
describe 'build authentication_abilities permissions' do
let(:authentication_abilities) { build_authentication_abilities }
@@ -855,6 +917,20 @@ describe Gitlab::GitAccess do
admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
end
end
+
+ context 'when pushing to a project' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow" }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'cleans up the files' do
+ expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
+ expect { push_access_check }.not_to raise_error
+ end
+ end
end
describe 'build authentication abilities' do
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 1c41dbcb9ef..21592688bf0 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -17,6 +17,16 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#cleanup' do
+ it 'sends a cleanup message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:cleanup)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+
+ client.cleanup
+ end
+ end
+
describe '#garbage_collect' do
it 'sends a garbage_collect message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -124,4 +134,15 @@ describe Gitlab::GitalyClient::RepositoryService do
client.squash_in_progress?(squash_id)
end
end
+
+ describe '#calculate_checksum' do
+ it 'sends a calculate_checksum message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:calculate_checksum)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(checksum: 0))
+
+ client.calculate_checksum
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index b675d5dc031..897a5984782 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -145,6 +145,9 @@ pipeline_schedule:
- pipelines
pipeline_schedule_variables:
- pipeline_schedule
+deploy_tokens:
+- project_deploy_tokens
+- projects
deploy_keys:
- user
- deploy_keys_projects
@@ -281,6 +284,8 @@ project:
- project_badges
- source_of_merge_requests
- internal_ids
+- project_deploy_tokens
+- deploy_tokens
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index d75416f2a62..991e354f499 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Gitlab::ImportExport::Importer do
+ let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
let(:project) { create(:project, import_source: File.join(test_path, 'exported-project.gz')) }
@@ -11,6 +12,7 @@ describe Gitlab::ImportExport::Importer do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
FileUtils.mkdir_p(shared.export_path)
FileUtils.cp(Rails.root.join('spec', 'fixtures', 'exported-project.gz'), test_path)
+ allow(subject).to receive(:remove_import_file)
end
after do
@@ -42,7 +44,8 @@ describe Gitlab::ImportExport::Importer do
Gitlab::ImportExport::RepoRestorer,
Gitlab::ImportExport::WikiRestorer,
Gitlab::ImportExport::UploadsRestorer,
- Gitlab::ImportExport::LfsRestorer
+ Gitlab::ImportExport::LfsRestorer,
+ Gitlab::ImportExport::StatisticsRestorer
].each do |restorer|
it "calls the #{restorer}" do
fake_restorer = double(restorer.to_s)
@@ -60,5 +63,42 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
end
+
+ context 'when project successfully restored' do
+ let!(:existing_project) { create(:project, namespace: user.namespace) }
+ let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') }
+
+ before do
+ restorers = double
+
+ allow(subject).to receive(:import_file).and_return(true)
+ allow(subject).to receive(:check_version!).and_return(true)
+ allow(subject).to receive(:restorers).and_return(restorers)
+ allow(restorers).to receive(:all?).and_return(true)
+ allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
+ end
+
+ context 'when import_data' do
+ context 'has original_path' do
+ it 'overwrites existing project' do
+ expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project)
+
+ subject.execute
+ end
+ end
+
+ context 'has not original_path' do
+ before do
+ allow(project).to receive(:import_data).and_return(double(data: {}))
+ end
+
+ it 'does not call the overwrite service' do
+ expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project)
+
+ subject.execute
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 2b3ffb2d7c0..d64ea72e346 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::Workhorse do
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
- let(:base_params) { repository.archive_metadata(ref, storage_path, format) }
+ let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
let(:gitaly_params) do
base_params.merge(
'GitalyServer' => {
@@ -29,7 +29,7 @@ describe Gitlab::Workhorse do
let(:cache_disabled) { false }
subject do
- described_class.send_git_archive(repository, ref: ref, format: format)
+ described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
end
before do
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index 4a22bd6f342..bf2fa5c0f56 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -4,8 +4,8 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod
describe AddForeignKeysToTodos, :migration do
let(:todos) { table(:todos) }
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
context 'add foreign key on user_id' do
let!(:todo_with_user) { create_todo(user_id: user.id) }
@@ -34,7 +34,7 @@ describe AddForeignKeysToTodos, :migration do
end
context 'add foreign key on note_id' do
- let(:note) { create(:note) }
+ let(:note) { create(:note) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let!(:todo_with_note) { create_todo(note_id: note.id) }
let!(:todo_with_invalid_note) { create_todo(note_id: 4711) }
let!(:todo_without_note) { create_todo(note_id: nil) }
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
index 63defcb39bf..d8dd7a2fb83 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -6,18 +6,18 @@ describe AddHeadPipelineForEachMergeRequest, :delete do
let(:migration) { described_class.new }
- let!(:project) { create(:project) }
+ let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let!(:other_project) { fork_project(project) }
- let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") }
- let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
- let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
- let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") }
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") }
- let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") }
- let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") }
- let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") }
+ let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
context "#up" do
context "when source_project and source_branch of pipeline are the same of merge request" do
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index f3a46025376..19f06810e54 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev
describe CalculateConvDevIndexPercentages, :delete do
let(:migration) { described_class.new }
let!(:conv_dev_index) do
- create(:conversational_development_index_metric,
+ create(:conversational_development_index_metric, # rubocop:disable RSpec/FactoriesInMigrationSpecs
leader_notes: 0,
instance_milestones: 0,
percentage_issues: 0,
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index 033d0e7584d..b5980cb9ddb 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -10,9 +10,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
describe '#up' do
it 'only cleans up pending delete projects' do
- create(:project)
- create(:project, pending_delete: true)
- project = build(:project, pending_delete: true, namespace_id: nil)
+ create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs
project.save(validate: false)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -21,8 +21,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project)
- create(:project, pending_delete: true)
+ create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
index 7879105a334..8f40ac3e38b 100644
--- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
@@ -9,11 +9,11 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
end
describe '#up' do
- set(:some_project) { create(:project) }
+ set(:some_project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
it 'only cleans up when namespace does not exist' do
- create(:project, pending_delete: true)
- project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ)
+ create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ) # rubocop:disable RSpec/FactoriesInMigrationSpecs
project.save(validate: false)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -22,7 +22,7 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project, pending_delete: true, namespace: create(:namespace))
+ create(:project, pending_delete: true, namespace: create(:namespace)) # rubocop:disable RSpec/FactoriesInMigrationSpecs
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
index d2eef81f396..dd2b08099f2 100644
--- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
+++ b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
@@ -5,9 +5,9 @@ require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_fore
# only_mirror_protected_branches column in the projects table to create a
# project via FactoryBot.
describe IssuesMovedToIdForeignKey, :migration, schema: 20171114150259 do
- let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) }
- let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) }
- let!(:issue_third) { create(:issue) }
+ let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:issue_third) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
subject { described_class.new }
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 638b2853374..4187ab149a5 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -16,18 +16,18 @@ describe MigrateOldArtifacts do
end
context 'with migratable data' do
- set(:project1) { create(:project, ci_id: 2) }
- set(:project2) { create(:project, ci_id: 3) }
- set(:project3) { create(:project) }
-
- set(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
- set(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
- set(:pipeline3) { create(:ci_empty_pipeline, project: project3) }
-
- let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) }
- let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) }
- let!(:build2) { create(:ci_build, pipeline: pipeline2) }
- let!(:build3) { create(:ci_build, pipeline: pipeline3) }
+ set(:project1) { create(:project, ci_id: 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ set(:project2) { create(:project, ci_id: 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ set(:project3) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+
+ set(:pipeline1) { create(:ci_empty_pipeline, project: project1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ set(:pipeline2) { create(:ci_empty_pipeline, project: project2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ set(:pipeline3) { create(:ci_empty_pipeline, project: project3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+
+ let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:build2) { create(:ci_build, pipeline: pipeline2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:build3) { create(:ci_build, pipeline: pipeline3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
setup_builds(build2, build3)
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 657113812bd..4ee1d255fbd 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb')
describe MigrateProcessCommitWorkerJobs do
- let(:project) { create(:project, :legacy_storage, :repository) }
- let(:user) { create(:user) }
+ let(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:commit) { project.commit.raw.rugged_commit }
describe 'Project' do
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index a17c9c72bde..99173708190 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activ
describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
let(:migration) { described_class.new }
- let!(:user_active_1) { create(:user) }
- let!(:user_active_2) { create(:user) }
+ let!(:user_active_1) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:user_active_2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
def record_activity(user, time)
Gitlab::Redis::SharedState.with do |redis|
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 31d16e17d7b..80468b9d01e 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -5,7 +5,7 @@ require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_proje
describe MigrateUserProjectView, :delete do
let(:migration) { described_class.new }
- let!(:user) { create(:user, project_view: 'readme') }
+ let!(:user) { create(:user, project_view: 'readme') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
describe '#up' do
it 'updates project view setting with new value' do
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
index 1a319eccc0d..1f39ad98fb8 100644
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -16,14 +16,14 @@ describe MovePersonalSnippetsFiles do
describe "#up" do
let(:snippet) do
- snippet = create(:personal_snippet)
+ snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
create_upload('picture.jpg', snippet)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:snippet)
+ snippet = create(:snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
create_upload('picture.jpg', snippet, create_file: false)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
@@ -62,7 +62,7 @@ describe MovePersonalSnippetsFiles do
secret = "secret#{snippet.id}"
file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
markdown = markdown_linking_file('picture.jpg', snippet)
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+ note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
migration.up
@@ -73,14 +73,14 @@ describe MovePersonalSnippetsFiles do
describe "#down" do
let(:snippet) do
- snippet = create(:personal_snippet)
+ snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
create_upload('picture.jpg', snippet, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:personal_snippet)
+ snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
@@ -119,7 +119,7 @@ describe MovePersonalSnippetsFiles do
markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
secret = "secret#{snippet.id}"
file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+ note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
migration.down
@@ -135,7 +135,7 @@ describe MovePersonalSnippetsFiles do
secret = '123456789'
filename = 'hello.jpg'
- snippet = create(:personal_snippet)
+ snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
@@ -161,7 +161,7 @@ describe MovePersonalSnippetsFiles do
FileUtils.touch(absolute_path)
end
- create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader)
+ create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) # rubocop:disable RSpec/FactoriesInMigrationSpecs
end
def markdown_linking_file(filename, snippet, in_new_path: false)
diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb
index 3a88a66a476..f11880a83e9 100644
--- a/spec/migrations/remove_dot_git_from_usernames_spec.rb
+++ b/spec/migrations/remove_dot_git_from_usernames_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
describe RemoveDotGitFromUsernames do
- let(:user) { create(:user) }
+ let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:migration) { described_class.new }
describe '#up' do
@@ -23,7 +23,7 @@ describe RemoveDotGitFromUsernames do
context 'when new path exists already' do
describe '#up' do
- let(:user2) { create(:user) }
+ let(:user2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
update_namespace(user, 'test.git')
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
index e51872239ad..2509ac6afd6 100644
--- a/spec/migrations/remove_duplicate_mr_events_spec.rb
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -5,17 +5,17 @@ describe RemoveDuplicateMrEvents, :delete do
let(:migration) { described_class.new }
describe '#up' do
- let(:user) { create(:user) }
- let(:merge_requests) { create_list(:merge_request, 2) }
- let(:issue) { create(:issue) }
+ let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:merge_requests) { create_list(:merge_request, 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:issue) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let!(:events) do
[
- create(:event, :created, author: user, target: merge_requests.first),
- create(:event, :created, author: user, target: merge_requests.first),
- create(:event, :updated, author: user, target: merge_requests.first),
- create(:event, :created, author: user, target: merge_requests.second),
- create(:event, :created, author: user, target: issue),
- create(:event, :created, author: user, target: issue)
+ create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:event, :updated, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:event, :created, author: user, target: merge_requests.second), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:event, :created, author: user, target: issue), # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:event, :created, author: user, target: issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs
]
end
diff --git a/spec/migrations/remove_project_labels_group_id_spec.rb b/spec/migrations/remove_project_labels_group_id_spec.rb
index d80d61af20b..01b09e71d83 100644
--- a/spec/migrations/remove_project_labels_group_id_spec.rb
+++ b/spec/migrations/remove_project_labels_group_id_spec.rb
@@ -5,9 +5,9 @@ require Rails.root.join('db', 'post_migrate', '20180202111106_remove_project_lab
describe RemoveProjectLabelsGroupId, :delete do
let(:migration) { described_class.new }
- let(:group) { create(:group) }
- let!(:project_label) { create(:label, group_id: group.id) }
- let!(:group_label) { create(:group_label) }
+ let(:group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:project_label) { create(:label, group_id: group.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:group_label) { create(:group_label) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
describe '#up' do
it 'updates the project labels group ID' do
diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb
index ec089f9106d..fb70c284f5e 100644
--- a/spec/migrations/remove_soft_removed_objects_spec.rb
+++ b/spec/migrations/remove_soft_removed_objects_spec.rb
@@ -8,7 +8,7 @@ describe RemoveSoftRemovedObjects, :migration do
create_with_deleted_at(:issue)
end
- regular_issue = create(:issue)
+ regular_issue = create(:issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs
run_migration
@@ -28,7 +28,7 @@ describe RemoveSoftRemovedObjects, :migration do
it 'removes routes of soft removed personal namespaces' do
namespace = create_with_deleted_at(:namespace)
- group = create(:group)
+ group = create(:group) # rubocop:disable RSpec/FactoriesInMigrationSpecs
expect(Route.where(source: namespace).exists?).to eq(true)
expect(Route.where(source: group).exists?).to eq(true)
@@ -41,7 +41,7 @@ describe RemoveSoftRemovedObjects, :migration do
it 'schedules the removal of soft removed groups' do
group = create_with_deleted_at(:group)
- admin = create(:user, admin: true)
+ admin = create(:user, admin: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
expect_any_instance_of(GroupDestroyWorker)
.to receive(:perform)
@@ -67,7 +67,7 @@ describe RemoveSoftRemovedObjects, :migration do
end
def create_with_deleted_at(*args)
- row = create(*args)
+ row = create(*args) # rubocop:disable RSpec/FactoriesInMigrationSpecs
# We set "deleted_at" this way so we don't run into any column cache issues.
row.class.where(id: row.id).update_all(deleted_at: 1.year.ago)
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index 75310075cc5..034e8a6a4e5 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -8,7 +8,7 @@ require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserv
# around this we use the DELETE cleaning strategy.
describe RenameMoreReservedProjectNames, :delete do
let(:migration) { described_class.new }
- let!(:project) { create(:project) }
+ let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
project.path = 'artifacts'
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 34336d705b1..592ac2b5fb9 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -12,7 +12,7 @@ require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_pr
# Ideally, the test should not use factories and rely on the `table` helper instead.
describe RenameReservedProjectNames, :migration, schema: :latest do
let(:migration) { described_class.new }
- let!(:project) { create(:project) }
+ let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
project.path = 'projects'
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
index e2994103ed7..b8a4dc2b2c0 100644
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -3,12 +3,12 @@ require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_
describe RenameUsersWithRenamedNamespace, :delete do
it 'renames a user that had their namespace renamed to the namespace path' do
- other_user = create(:user, username: 'kodingu')
- other_user1 = create(:user, username: 'api0')
+ other_user = create(:user, username: 'kodingu') # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ other_user1 = create(:user, username: 'api0') # rubocop:disable RSpec/FactoriesInMigrationSpecs
- user = create(:user, username: "Users0")
+ user = create(:user, username: "Users0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
user.update_column(:username, 'Users')
- user1 = create(:user, username: "import0")
+ user1 = create(:user, username: "import0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
user1.update_column(:username, 'import')
described_class.new.up
diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
index 65ec07da31c..ed306fb3d62 100644
--- a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
+++ b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -3,8 +3,8 @@ require Rails.root.join('db', 'post_migrate', '20171005130944_schedule_create_gp
describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do
before do
- create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key)
- create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key)
+ create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs
# Delete all subkeys so they can be recreated
GpgKeySubkey.destroy_all
end
diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
index 7494624066a..578440cba20 100644
--- a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
@@ -8,7 +8,7 @@ describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq
.to receive(:commits_count=).and_return(nil)
end
- let!(:mrs) { create_list(:merge_request, 3) }
+ let!(:mrs) { create_list(:merge_request, 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
it 'correctly schedules background migrations' do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
index 528dc54781d..560409f08de 100644
--- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
+++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
@@ -2,10 +2,10 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb')
describe TurnNestedGroupsIntoRegularGroupsForMysql do
- let!(:parent_group) { create(:group) }
- let!(:child_group) { create(:group, parent: parent_group) }
- let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) }
- let!(:member) { create(:user) }
+ let!(:parent_group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:child_group) { create(:group, parent: parent_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:member) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:migration) { described_class.new }
before do
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
index ccb77766b84..637dcbb8e01 100644
--- a/spec/migrations/update_retried_for_ci_build_spec.rb
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
describe UpdateRetriedForCiBuild, :delete do
- let(:pipeline) { create(:ci_pipeline) }
- let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') }
- let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') }
+ let(:pipeline) { create(:ci_pipeline) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
described_class.new.up
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
new file mode 100644
index 00000000000..5a15c23def4
--- /dev/null
+++ b/spec/models/deploy_token_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe DeployToken do
+ subject(:deploy_token) { create(:deploy_token) }
+
+ it { is_expected.to have_many :project_deploy_tokens }
+ it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
+
+ describe '#ensure_token' do
+ it 'should ensure a token' do
+ deploy_token.token = nil
+ deploy_token.save
+
+ expect(deploy_token.token).not_to be_empty
+ end
+ end
+
+ describe '#ensure_at_least_one_scope' do
+ context 'with at least one scope' do
+ it 'should be valid' do
+ is_expected.to be_valid
+ end
+ end
+
+ context 'with no scopes' do
+ it 'should be invalid' do
+ deploy_token = build(:deploy_token, read_repository: false, read_registry: false)
+
+ expect(deploy_token).not_to be_valid
+ expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank")
+ end
+ end
+ end
+
+ describe '#scopes' do
+ context 'with all the scopes' do
+ it 'should return scopes assigned to DeployToken' do
+ expect(deploy_token.scopes).to eq([:read_repository, :read_registry])
+ end
+ end
+
+ context 'with only one scope' do
+ it 'should return scopes assigned to DeployToken' do
+ deploy_token = create(:deploy_token, read_registry: false)
+ expect(deploy_token.scopes).to eq([:read_repository])
+ end
+ end
+ end
+
+ describe '#revoke!' do
+ it 'should update revoke attribute' do
+ deploy_token.revoke!
+ expect(deploy_token.revoked?).to be_truthy
+ end
+ end
+
+ describe "#active?" do
+ context "when it has been revoked" do
+ it 'should return false' do
+ deploy_token.revoke!
+ expect(deploy_token.active?).to be_falsy
+ end
+ end
+
+ context "when it hasn't been revoked" do
+ it 'should return true' do
+ expect(deploy_token.active?).to be_truthy
+ end
+ end
+ end
+
+ describe '#username' do
+ it 'returns a harcoded username' do
+ expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
+ end
+ end
+
+ describe '#has_access_to?' do
+ let(:project) { create(:project) }
+
+ subject(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+ context 'when the deploy token has access to the project' do
+ it 'should return true' do
+ expect(deploy_token.has_access_to?(project)).to be_truthy
+ end
+ end
+
+ context 'when the deploy token does not have access to the project' do
+ it 'should return false' do
+ another_project = create(:project)
+ expect(deploy_token.has_access_to?(another_project)).to be_falsy
+ end
+ end
+ end
+
+ describe '#expires_at' do
+ context 'when using Forever.date' do
+ let(:deploy_token) { create(:deploy_token, expires_at: nil) }
+
+ it 'should return nil' do
+ expect(deploy_token.expires_at).to be_nil
+ end
+ end
+
+ context 'when using a personalized date' do
+ let(:expires_at) { Date.today + 5.months }
+ let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
+
+ it 'should return the personalized date' do
+ expect(deploy_token.expires_at).to eq(expires_at)
+ end
+ end
+ end
+
+ describe '#expires_at=' do
+ context 'when passing nil' do
+ let(:deploy_token) { create(:deploy_token, expires_at: nil) }
+
+ it 'should assign Forever.date' do
+ expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date)
+ end
+ end
+
+ context 'when passign a value' do
+ let(:expires_at) { Date.today + 5.months }
+ let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
+
+ it 'should respect the value' do
+ expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at)
+ end
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 412eca4a56b..56161bfcc28 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -368,6 +368,32 @@ describe Environment do
end
end
+ describe '#deployment_platform' do
+ context 'when there is a deployment platform for environment' do
+ let!(:cluster) do
+ create(:cluster, :provided_by_gcp,
+ environment_scope: '*', projects: [project])
+ end
+
+ it 'finds a deployment platform' do
+ expect(environment.deployment_platform).to eq cluster.platform
+ end
+ end
+
+ context 'when there is no deployment platform for environment' do
+ it 'returns nil' do
+ expect(environment.deployment_platform).to be_nil
+ end
+ end
+
+ it 'checks deployment platforms associated with a project' do
+ expect(project).to receive(:deployment_platform)
+ .with(environment: environment.name)
+
+ environment.deployment_platform
+ end
+ end
+
describe '#terminals' do
subject { environment.terminals }
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 7709cf43200..8c01a7ac18f 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -36,7 +36,7 @@ describe MergeRequestDiffCommit do
"committer_email": "dmitriy.zaporozhets@gmail.com",
"merge_request_diff_id": merge_request_diff_id,
"relative_order": 0,
- "sha": sha_attribute.type_cast_for_database('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ "sha": sha_attribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e")
},
{
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
@@ -48,7 +48,7 @@ describe MergeRequestDiffCommit do
"committer_email": "dmitriy.zaporozhets@gmail.com",
"merge_request_diff_id": merge_request_diff_id,
"relative_order": 1,
- "sha": sha_attribute.type_cast_for_database('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ "sha": sha_attribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
}
]
end
@@ -79,7 +79,7 @@ describe MergeRequestDiffCommit do
"committer_email": "alejorro70@gmail.com",
"merge_request_diff_id": merge_request_diff_id,
"relative_order": 0,
- "sha": sha_attribute.type_cast_for_database('ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69')
+ "sha": sha_attribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69")
}]
end
diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb
new file mode 100644
index 00000000000..9e2e40c2e8f
--- /dev/null
+++ b/spec/models/project_deploy_token_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+RSpec.describe ProjectDeployToken, type: :model do
+ let(:project) { create(:project) }
+ let(:deploy_token) { create(:deploy_token) }
+ subject(:project_deploy_token) { create(:project_deploy_token, project: project, deploy_token: deploy_token) }
+
+ it { is_expected.to belong_to :project }
+ it { is_expected.to belong_to :deploy_token }
+
+ it { is_expected.to validate_presence_of :deploy_token }
+ it { is_expected.to validate_presence_of :project }
+ it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:project_id) }
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 7007f78e702..2675c2f52c1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -84,6 +84,8 @@ describe Project do
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
it { is_expected.to have_many(:lfs_file_locks) }
+ it { is_expected.to have_many(:project_deploy_tokens) }
+ it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
context 'after initialized' do
it "has a project_feature" do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a600987d0bf..73266c0085f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2234,6 +2234,20 @@ describe User do
end
end
+ context '#invalidate_personal_projects_count' do
+ let(:user) { build_stubbed(:user) }
+
+ it 'invalidates cache for personal projects counter' do
+ cache_mock = double
+
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'personal_projects_count'])
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ user.invalidate_personal_projects_count
+ end
+ end
+
describe '#allow_password_authentication_for_web?' do
context 'regular user' do
let(:user) { build(:user) }
@@ -2283,11 +2297,9 @@ describe User do
user = build(:user)
projects = double(:projects, count: 1)
- expect(user).to receive(:personal_projects).once.and_return(projects)
+ expect(user).to receive(:personal_projects).and_return(projects)
- 2.times do
- expect(user.personal_projects_count).to eq(1)
- end
+ expect(user.personal_projects_count).to eq(1)
end
end
diff --git a/spec/policies/deploy_token_policy_spec.rb b/spec/policies/deploy_token_policy_spec.rb
new file mode 100644
index 00000000000..eea287d895e
--- /dev/null
+++ b/spec/policies/deploy_token_policy_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe DeployTokenPolicy do
+ let(:current_user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+ subject { described_class.new(current_user, deploy_token) }
+
+ describe 'creating a deploy key' do
+ context 'when user is master' do
+ before do
+ project.add_master(current_user)
+ end
+
+ it { is_expected.to be_allowed(:create_deploy_token) }
+ end
+
+ context 'when user is not master' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it { is_expected.to be_disallowed(:create_deploy_token) }
+ end
+ end
+
+ describe 'updating a deploy key' do
+ context 'when user is master' do
+ before do
+ project.add_master(current_user)
+ end
+
+ it { is_expected.to be_allowed(:update_deploy_token) }
+ end
+
+ context 'when user is not master' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it { is_expected.to be_disallowed(:update_deploy_token) }
+ end
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 6614e8cea43..90f9c4ad214 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -384,6 +384,30 @@ describe API::Issues do
end
let(:base_url) { "/groups/#{group.id}/issues" }
+ context 'when group has subgroups', :nested_groups do
+ let(:subgroup_1) { create(:group, parent: group) }
+ let(:subgroup_2) { create(:group, parent: subgroup_1) }
+
+ let(:subgroup_1_project) { create(:project, namespace: subgroup_1) }
+ let(:subgroup_2_project) { create(:project, namespace: subgroup_2) }
+
+ let!(:issue_1) { create(:issue, project: subgroup_1_project) }
+ let!(:issue_2) { create(:issue, project: subgroup_2_project) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'also returns subgroups projects issues' do
+ get api(base_url, user)
+
+ issue_ids = json_response.map { |issue| issue['id'] }
+
+ expect_paginated_array_response(size: 5)
+ expect(issue_ids).to include(issue_1.id, issue_2.id)
+ end
+ end
+
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 5d13e6de741..f68057a92a1 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -114,6 +114,29 @@ describe API::ProjectImport do
expect(import_project.description).to eq('Hello world')
end
+ context 'when target path already exists in namespace' do
+ let(:existing_project) { create(:project, namespace: user.namespace) }
+
+ it 'does not schedule an import' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+ post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Name has already been taken')
+ end
+
+ context 'when param overwrite is true' do
+ it 'schedules an import' do
+ stub_import(user.namespace)
+
+ post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file), overwrite: true
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+ end
+
def stub_import(namespace)
expect_any_instance_of(Project).to receive(:import_schedule)
expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index fb1281a6b42..e1b4e618092 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -164,20 +164,36 @@ describe 'project routing' do
# archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive
# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit
describe Projects::RepositoriesController, 'routing' do
- it 'to #archive' do
- expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master')
- end
-
it 'to #archive format:zip' do
- expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master')
+ expect(get('/gitlab/gitlabhq/-/archive/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master/archive')
end
it 'to #archive format:tar.bz2' do
- expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master')
+ expect(get('/gitlab/gitlabhq/-/archive/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master/archive')
end
it 'to #archive with "/" in route' do
- expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome')
+ expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome')
+ end
+
+ it 'to #archive_alternative' do
+ expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', append_sha: true)
+ end
+
+ it 'to #archive_deprecated' do
+ expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', append_sha: true)
+ end
+
+ it 'to #archive_deprecated format:zip' do
+ expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master', append_sha: true)
+ end
+
+ it 'to #archive_deprecated format:tar.bz2' do
+ expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master', append_sha: true)
+ end
+
+ it 'to #archive_deprecated with "/" in route' do
+ expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'improve/awesome', append_sha: true)
end
end
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
new file mode 100644
index 00000000000..2763f2bda21
--- /dev/null
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
+
+describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
+ include CopHelper
+
+ let(:source_file) { 'spec/migrations/foo_spec.rb' }
+
+ subject(:cop) { described_class.new }
+
+ shared_examples 'an offensive factory call' do |namespace|
+ %i[build build_list create create_list].each do |forbidden_method|
+ namespaced_forbidden_method = "#{namespace}#{forbidden_method}(:user)"
+
+ it "registers an offense for #{namespaced_forbidden_method}" do
+ expect_offense(<<-RUBY)
+ describe 'foo' do
+ let(:user) { #{namespaced_forbidden_method} }
+ #{'^' * namespaced_forbidden_method.size} Don't use FactoryBot.#{forbidden_method} in migration specs, use `table` instead.
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'in a migration spec file' do
+ before do
+ allow(cop).to receive(:in_migration_spec?).and_return(true)
+ end
+
+ it_behaves_like 'an offensive factory call', ''
+ it_behaves_like 'an offensive factory call', 'FactoryBot.'
+ end
+
+ context 'outside of a migration spec file' do
+ it "does not register an offense" do
+ expect_no_offenses(<<-RUBY)
+ describe 'foo' do
+ let(:user) { create(:user) }
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index b4efa3e44b6..27a7bf0e605 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -48,10 +48,8 @@ describe Boards::Issues::ListService do
context 'when parent is a group' do
let(:user) { create(:user) }
- let(:group) { create(:group) }
let(:project) { create(:project, :empty_repo, namespace: group) }
let(:project1) { create(:project, :empty_repo, namespace: group) }
- let(:board) { create(:board, group: group) }
let(:m1) { create(:milestone, group: group) }
let(:m2) { create(:milestone, group: group) }
@@ -92,13 +90,30 @@ describe Boards::Issues::ListService do
let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) }
let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) }
- let(:parent) { group }
-
before do
group.add_developer(user)
end
- it_behaves_like 'issues list service'
+ context 'and group has no parent' do
+ let(:parent) { group }
+ let(:group) { create(:group) }
+ let(:board) { create(:board, group: group) }
+
+ it_behaves_like 'issues list service'
+ end
+
+ context 'and group is an ancestor', :nested_groups do
+ let(:parent) { create(:group) }
+ let(:group) { create(:group, parent: parent) }
+ let!(:backlog) { create(:backlog_list, board: board) }
+ let(:board) { create(:board, group: parent) }
+
+ before do
+ parent.add_developer(user)
+ end
+
+ it_behaves_like 'issues list service'
+ end
end
end
end
diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb
new file mode 100644
index 00000000000..3a2bbf1ecd1
--- /dev/null
+++ b/spec/services/deploy_tokens/create_service_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe DeployTokens::CreateService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:deploy_token_params) { attributes_for(:deploy_token) }
+
+ describe '#execute' do
+ subject { described_class.new(project, user, deploy_token_params).execute }
+
+ context 'when the deploy token is valid' do
+ it 'should create a new DeployToken' do
+ expect { subject }.to change { DeployToken.count }.by(1)
+ end
+
+ it 'should create a new ProjectDeployToken' do
+ expect { subject }.to change { ProjectDeployToken.count }.by(1)
+ end
+
+ it 'returns a DeployToken' do
+ expect(subject).to be_an_instance_of DeployToken
+ end
+ end
+
+ context 'when expires at date is not passed' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, expires_at: '') }
+
+ it 'should set Forever.date' do
+ expect(subject.read_attribute(:expires_at)).to eq(Forever.date)
+ end
+ end
+
+ context 'when the deploy token is invalid' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) }
+
+ it 'should not create a new DeployToken' do
+ expect { subject }.not_to change { DeployToken.count }
+ end
+
+ it 'should not create a new ProjectDeployToken' do
+ expect { subject }.not_to change { ProjectDeployToken.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index 0a3647a814f..8ccbba7fa58 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -8,7 +8,7 @@ describe Issuable::DestroyService do
describe '#execute' do
context 'when issuable is an issue' do
- let!(:issue) { create(:issue, project: project, author: user) }
+ let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'destroys the issue' do
expect { service.execute(issue) }.to change { project.issues.count }.by(-1)
@@ -26,10 +26,15 @@ describe Issuable::DestroyService do
expect { service.execute(issue) }
.to change { user.todos_pending_count }.from(1).to(0)
end
+
+ it 'invalidates the issues count cache for the assignees' do
+ expect_any_instance_of(User).to receive(:invalidate_cache_counts).once
+ service.execute(issue)
+ end
end
context 'when issuable is a merge request' do
- let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user) }
+ let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) }
it 'destroys the merge request' do
expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1)
@@ -41,6 +46,11 @@ describe Issuable::DestroyService do
service.execute(merge_request)
end
+ it 'invalidates the merge request caches for the MR assignee' do
+ expect_any_instance_of(User).to receive(:invalidate_cache_counts).once
+ service.execute(merge_request)
+ end
+
it 'updates the todo caches for users with todos on the merge request' do
create(:todo, target: merge_request, user: user, author: user, project: project)
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f95474208f3..23b1134b5a3 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -97,11 +97,13 @@ describe Issues::UpdateService, :mailer do
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
- context 'when moving issue between issues from different projects' do
+ context 'when moving issue between issues from different projects', :nested_groups do
let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+
let(:project_1) { create(:project, namespace: group) }
let(:project_2) { create(:project, namespace: group) }
- let(:project_3) { create(:project, namespace: group) }
+ let(:project_3) { create(:project, namespace: subgroup) }
let(:issue_1) { create(:issue, project: project_1) }
let(:issue_2) { create(:issue, project: project_2) }
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index 6cadcd438c3..837b8a56d12 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -77,6 +77,14 @@ describe MergeRequests::Conflicts::ListService do
expect(service.can_be_resolved_in_ui?).to be_falsey
end
+ it 'returns a falsey value when the MR has a missing revision after a force push' do
+ merge_request = create_merge_request('conflict-resolvable')
+ service = conflicts_service(merge_request)
+ allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA)
+
+ expect(service.can_be_resolved_in_ui?).to be_falsey
+ end
+
context 'with gitaly disabled', :skip_gitaly_mock do
it 'returns a falsey value when the MR has a missing ref after a force push' do
merge_request = create_merge_request('conflict-resolvable')
@@ -85,6 +93,14 @@ describe MergeRequests::Conflicts::ListService do
expect(service.can_be_resolved_in_ui?).to be_falsey
end
+
+ it 'returns a falsey value when the MR has a missing revision after a force push' do
+ merge_request = create_merge_request('conflict-resolvable')
+ service = conflicts_service(merge_request)
+ allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA)
+
+ expect(service.can_be_resolved_in_ui?).to be_falsey
+ end
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 2cacb97a293..e35f0f6337a 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -28,6 +28,14 @@ describe Projects::CreateService, '#execute' do
end
end
+ describe 'after create actions' do
+ it 'invalidate personal_projects_count caches' do
+ expect(user).to receive(:invalidate_personal_projects_count)
+
+ create_project(user, opts)
+ end
+ end
+
context "admin creates project with other user's namespace_id" do
it 'sets the correct permissions' do
admin = create(:admin)
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 0bec2054f50..a66e3c5e995 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -66,6 +66,12 @@ describe Projects::DestroyService do
end
it_behaves_like 'deleting the project'
+
+ it 'invalidates personal_project_count cache' do
+ expect(user).to receive(:invalidate_personal_projects_count)
+
+ destroy_project(project, user)
+ end
end
context 'Sidekiq fake' do
@@ -242,6 +248,28 @@ describe Projects::DestroyService do
end
end
+ context '#attempt_restore_repositories' do
+ let(:path) { project.disk_path + '.git' }
+
+ before do
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
+
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_falsey
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy
+ end
+
+ it 'restores the repositories' do
+ Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback }
+
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+ end
+ end
+
def destroy_project(project, user, params = {})
if async
Projects::DestroyService.new(project, user, params).async_execute
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index 880b2aae66a..ee1a886f5d6 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -4,7 +4,8 @@ describe Projects::GitlabProjectsImportService do
set(:namespace) { create(:namespace) }
let(:path) { 'test-path' }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
- let(:import_params) { { namespace_id: namespace.id, path: path, file: file } }
+ let(:overwrite) { false }
+ let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }
subject { described_class.new(namespace.owner, import_params) }
describe '#execute' do
@@ -37,5 +38,28 @@ describe Projects::GitlabProjectsImportService do
expect(project.import_data.data['override_params']['description']).to eq('Hello')
end
end
+
+ context 'when there is a project with the same path' do
+ let(:existing_project) { create(:project, namespace: namespace) }
+ let(:path) { existing_project.path}
+
+ it 'does not create the project' do
+ project = subject.execute
+
+ expect(project).to be_invalid
+ expect(project).not_to be_persisted
+ end
+
+ context 'when overwrite param is set' do
+ let(:overwrite) { true }
+
+ it 'creates a project in a temporary full_path' do
+ project = subject.execute
+
+ expect(project).to be_valid
+ expect(project).to be_persisted
+ end
+ end
+ end
end
end
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
new file mode 100644
index 00000000000..a820ebd91f4
--- /dev/null
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+describe Projects::MoveAccessService do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_with_access) { create(:project, namespace: user.namespace) }
+ let(:master_user) { create(:user) }
+ let(:reporter_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+ let(:master_group) { create(:group) }
+ let(:reporter_group) { create(:group) }
+ let(:developer_group) { create(:group) }
+
+ before do
+ project_with_access.add_master(master_user)
+ project_with_access.add_developer(developer_user)
+ project_with_access.add_reporter(reporter_user)
+ project_with_access.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+ project_with_access.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+ project_with_access.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+ end
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ shared_examples 'move the accesses' do
+ it do
+ expect(project_with_access.project_members.count).to eq 4
+ expect(project_with_access.project_group_links.count).to eq 3
+ expect(project_with_access.authorized_users.count).to eq 4
+
+ subject.execute(project_with_access)
+
+ expect(project_with_access.project_members.count).to eq 0
+ expect(project_with_access.project_group_links.count).to eq 0
+ expect(project_with_access.authorized_users.count).to eq 1
+ expect(target_project.project_members.count).to eq 4
+ expect(target_project.project_group_links.count).to eq 3
+ expect(target_project.authorized_users.count).to eq 4
+ end
+
+ it 'rollbacks if an exception is raised' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
+
+ expect(project_with_access.project_members.count).to eq 4
+ expect(project_with_access.project_group_links.count).to eq 3
+ expect(project_with_access.authorized_users.count).to eq 4
+ end
+ end
+
+ context 'when both projects are in the same namespace' do
+ let(:target_project) { create(:project, namespace: user.namespace) }
+
+ it 'does not refresh project owner authorized projects' do
+ allow(project_with_access).to receive(:namespace).and_return(user.namespace)
+ expect(project_with_access.namespace).not_to receive(:refresh_project_authorizations)
+ expect(target_project.namespace).not_to receive(:refresh_project_authorizations)
+
+ subject.execute(project_with_access)
+ end
+
+ it_behaves_like 'move the accesses'
+ end
+
+ context 'when projects are in different namespaces' do
+ let(:target_project) { create(:project, namespace: group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it 'refreshes both project owner authorized projects' do
+ allow(project_with_access).to receive(:namespace).and_return(user.namespace)
+ expect(user.namespace).to receive(:refresh_project_authorizations).once
+ expect(group).to receive(:refresh_project_authorizations).once
+
+ subject.execute(project_with_access)
+ end
+
+ it_behaves_like 'move the accesses'
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:target_project) { create(:project, namespace: user.namespace) }
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining memberships' do
+ target_project.add_master(master_user)
+
+ subject.execute(project_with_access, options)
+
+ expect(project_with_access.project_members.count).not_to eq 0
+ end
+
+ it 'does not remove remaining group links' do
+ target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+
+ subject.execute(project_with_access, options)
+
+ expect(project_with_access.project_group_links.count).not_to eq 0
+ end
+
+ it 'does not remove remaining authorizations' do
+ target_project.add_developer(developer_user)
+
+ subject.execute(project_with_access, options)
+
+ expect(project_with_access.project_authorizations.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
new file mode 100644
index 00000000000..c548edf39a8
--- /dev/null
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::MoveDeployKeysProjectsService do
+ let!(:user) { create(:user) }
+ let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) }
+ let!(:target_project) { create(:project, namespace: user.namespace) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ before do
+ create_list(:deploy_keys_project, 2, project: project_with_deploy_keys)
+ end
+
+ it 'moves the user\'s deploy keys from one project to another' do
+ expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+ expect(target_project.deploy_keys_projects.count).to eq 0
+
+ subject.execute(project_with_deploy_keys)
+
+ expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
+ expect(target_project.deploy_keys_projects.count).to eq 2
+ end
+
+ it 'does not link existent deploy_keys in the current project' do
+ target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
+
+ expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+ expect(target_project.deploy_keys_projects.count).to eq 1
+
+ subject.execute(project_with_deploy_keys)
+
+ expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
+ expect(target_project.deploy_keys_projects.count).to eq 2
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_deploy_keys) }.to raise_error(StandardError)
+
+ expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
+ expect(target_project.deploy_keys_projects.count).to eq 0
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining deploy keys projects' do
+ target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
+
+ subject.execute(project_with_deploy_keys, options)
+
+ expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb
new file mode 100644
index 00000000000..f4a5a7f9fc2
--- /dev/null
+++ b/spec/services/projects/move_forks_service_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Projects::MoveForksService do
+ include ProjectForksHelper
+
+ let!(:user) { create(:user) }
+ let!(:project_with_forks) { create(:project, namespace: user.namespace) }
+ let!(:target_project) { create(:project, namespace: user.namespace) }
+ let!(:lvl1_forked_project_1) { fork_project(project_with_forks, user) }
+ let!(:lvl1_forked_project_2) { fork_project(project_with_forks, user) }
+ let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
+ let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ context 'when moving a root forked project' do
+ it 'moves the descendant forks' do
+ expect(project_with_forks.forks.count).to eq 2
+ expect(target_project.forks.count).to eq 0
+
+ subject.execute(project_with_forks)
+
+ expect(project_with_forks.forks.count).to eq 0
+ expect(target_project.forks.count).to eq 2
+ expect(lvl1_forked_project_1.forked_from_project).to eq target_project
+ expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq target_project
+ expect(lvl1_forked_project_2.forked_from_project).to eq target_project
+ expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq target_project
+ end
+
+ it 'updates the fork network' do
+ expect(project_with_forks.fork_network.root_project).to eq project_with_forks
+ expect(project_with_forks.fork_network.fork_network_members.map(&:project)).to include project_with_forks
+
+ subject.execute(project_with_forks)
+
+ expect(target_project.reload.fork_network.root_project).to eq target_project
+ expect(target_project.fork_network.fork_network_members.map(&:project)).not_to include project_with_forks
+ end
+ end
+
+ context 'when moving a intermediate forked project' do
+ it 'moves the descendant forks' do
+ expect(lvl1_forked_project_1.forks.count).to eq 2
+ expect(target_project.forks.count).to eq 0
+
+ subject.execute(lvl1_forked_project_1)
+
+ expect(lvl1_forked_project_1.forks.count).to eq 0
+ expect(target_project.forks.count).to eq 2
+ expect(lvl2_forked_project_1_1.forked_from_project).to eq target_project
+ expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq target_project
+ expect(lvl2_forked_project_1_2.forked_from_project).to eq target_project
+ expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq target_project
+ end
+
+ it 'moves the ascendant fork' do
+ subject.execute(lvl1_forked_project_1)
+
+ expect(target_project.forked_from_project).to eq project_with_forks
+ expect(target_project.fork_network_member.forked_from_project).to eq project_with_forks
+ end
+
+ it 'does not update fork network' do
+ subject.execute(lvl1_forked_project_1)
+
+ expect(target_project.reload.fork_network.root_project).to eq project_with_forks
+ end
+ end
+
+ context 'when moving a leaf forked project' do
+ it 'moves the ascendant fork' do
+ subject.execute(lvl2_forked_project_1_1)
+
+ expect(target_project.forked_from_project).to eq lvl1_forked_project_1
+ expect(target_project.fork_network_member.forked_from_project).to eq lvl1_forked_project_1
+ end
+
+ it 'does not update fork network' do
+ subject.execute(lvl2_forked_project_1_1)
+
+ expect(target_project.reload.fork_network.root_project).to eq project_with_forks
+ end
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_forks) }.to raise_error(StandardError)
+
+ expect(project_with_forks.forks.count).to eq 2
+ expect(target_project.forks.count).to eq 0
+ end
+ end
+end
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
new file mode 100644
index 00000000000..517a24a982a
--- /dev/null
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Projects::MoveLfsObjectsProjectsService do
+ let!(:user) { create(:user) }
+ let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) }
+ let!(:target_project) { create(:project, namespace: user.namespace) }
+
+ subject { described_class.new(target_project, user) }
+
+ before do
+ create_list(:lfs_objects_project, 3, project: project_with_lfs_objects)
+ end
+
+ describe '#execute' do
+ it 'links the lfs objects from existent in source project' do
+ expect(target_project.lfs_objects.count).to eq 0
+
+ subject.execute(project_with_lfs_objects)
+
+ expect(project_with_lfs_objects.reload.lfs_objects.count).to eq 0
+ expect(target_project.reload.lfs_objects.count).to eq 3
+ end
+
+ it 'does not link existent lfs_object in the current project' do
+ target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
+
+ expect(target_project.lfs_objects.count).to eq 2
+
+ subject.execute(project_with_lfs_objects)
+
+ expect(target_project.lfs_objects.count).to eq 3
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_lfs_objects) }.to raise_error(StandardError)
+
+ expect(project_with_lfs_objects.lfs_objects.count).to eq 3
+ expect(target_project.lfs_objects.count).to eq 0
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining lfs objects' do
+ target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
+
+ subject.execute(project_with_lfs_objects, options)
+
+ expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
new file mode 100644
index 00000000000..24d69eef86a
--- /dev/null
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Projects::MoveNotificationSettingsService do
+ let(:user) { create(:user) }
+ let(:project_with_notifications) { create(:project, namespace: user.namespace) }
+ let(:target_project) { create(:project, namespace: user.namespace) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ context 'with notification settings' do
+ before do
+ create_list(:notification_setting, 2, source: project_with_notifications)
+ end
+
+ it 'moves the user\'s notification settings from one project to another' do
+ expect(project_with_notifications.notification_settings.count).to eq 3
+ expect(target_project.notification_settings.count).to eq 1
+
+ subject.execute(project_with_notifications)
+
+ expect(project_with_notifications.notification_settings.count).to eq 0
+ expect(target_project.notification_settings.count).to eq 3
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_notifications) }.to raise_error(StandardError)
+
+ expect(project_with_notifications.notification_settings.count).to eq 3
+ expect(target_project.notification_settings.count).to eq 1
+ end
+ end
+
+ it 'does not move existent notification settings in the current project' do
+ expect(project_with_notifications.notification_settings.count).to eq 1
+ expect(target_project.notification_settings.count).to eq 1
+ expect(user.notification_settings.count).to eq 2
+
+ subject.execute(project_with_notifications)
+
+ expect(user.notification_settings.count).to eq 1
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining notification settings' do
+ subject.execute(project_with_notifications, options)
+
+ expect(project_with_notifications.notification_settings.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
new file mode 100644
index 00000000000..f7262b9b887
--- /dev/null
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectAuthorizationsService do
+ let!(:user) { create(:user) }
+ let(:project_with_users) { create(:project, namespace: user.namespace) }
+ let(:target_project) { create(:project, namespace: user.namespace) }
+ let(:master_user) { create(:user) }
+ let(:reporter_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ before do
+ project_with_users.add_master(master_user)
+ project_with_users.add_developer(developer_user)
+ project_with_users.add_reporter(reporter_user)
+ end
+
+ it 'moves the authorizations from one project to another' do
+ expect(project_with_users.authorized_users.count).to eq 4
+ expect(target_project.authorized_users.count).to eq 1
+
+ subject.execute(project_with_users)
+
+ expect(project_with_users.authorized_users.count).to eq 0
+ expect(target_project.authorized_users.count).to eq 4
+ end
+
+ it 'does not move existent authorizations to the current project' do
+ target_project.add_master(developer_user)
+ target_project.add_developer(reporter_user)
+
+ expect(project_with_users.authorized_users.count).to eq 4
+ expect(target_project.authorized_users.count).to eq 3
+
+ subject.execute(project_with_users)
+
+ expect(project_with_users.authorized_users.count).to eq 0
+ expect(target_project.authorized_users.count).to eq 4
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining project authorizations' do
+ target_project.add_master(developer_user)
+ target_project.add_developer(reporter_user)
+
+ subject.execute(project_with_users, options)
+
+ expect(project_with_users.project_authorizations.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
new file mode 100644
index 00000000000..e3d06e6d3d7
--- /dev/null
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectGroupLinksService do
+ let!(:user) { create(:user) }
+ let(:project_with_groups) { create(:project, namespace: user.namespace) }
+ let(:target_project) { create(:project, namespace: user.namespace) }
+ let(:master_group) { create(:group) }
+ let(:reporter_group) { create(:group) }
+ let(:developer_group) { create(:group) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ before do
+ project_with_groups.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+ project_with_groups.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+ project_with_groups.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+ end
+
+ it 'moves the group links from one project to another' do
+ expect(project_with_groups.project_group_links.count).to eq 3
+ expect(target_project.project_group_links.count).to eq 0
+
+ subject.execute(project_with_groups)
+
+ expect(project_with_groups.project_group_links.count).to eq 0
+ expect(target_project.project_group_links.count).to eq 3
+ end
+
+ it 'does not move existent group links in the current project' do
+ target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+ target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+
+ expect(project_with_groups.project_group_links.count).to eq 3
+ expect(target_project.project_group_links.count).to eq 2
+
+ subject.execute(project_with_groups)
+
+ expect(project_with_groups.project_group_links.count).to eq 0
+ expect(target_project.project_group_links.count).to eq 3
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
+
+ expect(project_with_groups.project_group_links.count).to eq 3
+ expect(target_project.project_group_links.count).to eq 0
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining project group links' do
+ target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+ target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+
+ subject.execute(project_with_groups, options)
+
+ expect(project_with_groups.project_group_links.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
new file mode 100644
index 00000000000..9c9a2d2fde1
--- /dev/null
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::MoveProjectMembersService do
+ let!(:user) { create(:user) }
+ let(:project_with_users) { create(:project, namespace: user.namespace) }
+ let(:target_project) { create(:project, namespace: user.namespace) }
+ let(:master_user) { create(:user) }
+ let(:reporter_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ before do
+ project_with_users.add_master(master_user)
+ project_with_users.add_developer(developer_user)
+ project_with_users.add_reporter(reporter_user)
+ end
+
+ it 'moves the members from one project to another' do
+ expect(project_with_users.project_members.count).to eq 4
+ expect(target_project.project_members.count).to eq 1
+
+ subject.execute(project_with_users)
+
+ expect(project_with_users.project_members.count).to eq 0
+ expect(target_project.project_members.count).to eq 4
+ end
+
+ it 'does not move existent members to the current project' do
+ target_project.add_master(developer_user)
+ target_project.add_developer(reporter_user)
+
+ expect(project_with_users.project_members.count).to eq 4
+ expect(target_project.project_members.count).to eq 3
+
+ subject.execute(project_with_users)
+
+ expect(project_with_users.project_members.count).to eq 0
+ expect(target_project.project_members.count).to eq 4
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_users) }.to raise_error(StandardError)
+
+ expect(project_with_users.project_members.count).to eq 4
+ expect(target_project.project_members.count).to eq 1
+ end
+
+ context 'when remove_remaining_elements is false' do
+ let(:options) { { remove_remaining_elements: false } }
+
+ it 'does not remove remaining project members' do
+ target_project.add_master(developer_user)
+ target_project.add_developer(reporter_user)
+
+ subject.execute(project_with_users, options)
+
+ expect(project_with_users.project_members.count).not_to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb
new file mode 100644
index 00000000000..e0545c5a21b
--- /dev/null
+++ b/spec/services/projects/move_users_star_projects_service_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Projects::MoveUsersStarProjectsService do
+ let!(:user) { create(:user) }
+ let!(:project_with_stars) { create(:project, namespace: user.namespace) }
+ let!(:target_project) { create(:project, namespace: user.namespace) }
+
+ subject { described_class.new(target_project, user) }
+
+ describe '#execute' do
+ before do
+ create_list(:users_star_project, 2, project: project_with_stars)
+ end
+
+ it 'moves the user\'s stars from one project to another' do
+ expect(project_with_stars.users_star_projects.count).to eq 2
+ expect(project_with_stars.star_count).to eq 2
+ expect(target_project.users_star_projects.count).to eq 0
+ expect(target_project.star_count).to eq 0
+
+ subject.execute(project_with_stars)
+ project_with_stars.reload
+ target_project.reload
+
+ expect(project_with_stars.users_star_projects.count).to eq 0
+ expect(project_with_stars.star_count).to eq 0
+ expect(target_project.users_star_projects.count).to eq 2
+ expect(target_project.star_count).to eq 2
+ end
+
+ it 'rollbacks changes if transaction fails' do
+ allow(subject).to receive(:success).and_raise(StandardError)
+
+ expect { subject.execute(project_with_stars) }.to raise_error(StandardError)
+
+ expect(project_with_stars.users_star_projects.count).to eq 2
+ expect(project_with_stars.star_count).to eq 2
+ expect(target_project.users_star_projects.count).to eq 0
+ expect(target_project.star_count).to eq 0
+ end
+ end
+end
diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb
new file mode 100644
index 00000000000..252c61f4224
--- /dev/null
+++ b/spec/services/projects/overwrite_project_service_spec.rb
@@ -0,0 +1,198 @@
+require 'spec_helper'
+
+describe Projects::OverwriteProjectService do
+ include ProjectForksHelper
+
+ let(:user) { create(:user) }
+ let(:project_from) { create(:project, namespace: user.namespace) }
+ let(:project_to) { create(:project, namespace: user.namespace) }
+ let!(:lvl1_forked_project_1) { fork_project(project_from, user) }
+ let!(:lvl1_forked_project_2) { fork_project(project_from, user) }
+ let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
+ let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
+
+ subject { described_class.new(project_to, user) }
+
+ before do
+ allow(project_to).to receive(:import_data).and_return(double(data: { 'original_path' => project_from.path }))
+ end
+
+ describe '#execute' do
+ shared_examples 'overwrite actions' do
+ it 'moves deploy keys' do
+ deploy_keys_count = project_from.deploy_keys_projects.count
+
+ subject.execute(project_from)
+
+ expect(project_to.deploy_keys_projects.count).to eq deploy_keys_count
+ end
+
+ it 'moves notification settings' do
+ notification_count = project_from.notification_settings.count
+
+ subject.execute(project_from)
+
+ expect(project_to.notification_settings.count).to eq notification_count
+ end
+
+ it 'moves users stars' do
+ stars_count = project_from.users_star_projects.count
+
+ subject.execute(project_from)
+ project_to.reload
+
+ expect(project_to.users_star_projects.count).to eq stars_count
+ expect(project_to.star_count).to eq stars_count
+ end
+
+ it 'moves project group links' do
+ group_links_count = project_from.project_group_links.count
+
+ subject.execute(project_from)
+
+ expect(project_to.project_group_links.count).to eq group_links_count
+ end
+
+ it 'moves memberships and authorizations' do
+ members_count = project_from.project_members.count
+ project_authorizations = project_from.project_authorizations.count
+
+ subject.execute(project_from)
+
+ expect(project_to.project_members.count).to eq members_count
+ expect(project_to.project_authorizations.count).to eq project_authorizations
+ end
+
+ context 'moves lfs objects relationships' do
+ before do
+ create_list(:lfs_objects_project, 3, project: project_from)
+ end
+
+ it do
+ lfs_objects_count = project_from.lfs_objects.count
+
+ subject.execute(project_from)
+
+ expect(project_to.lfs_objects.count).to eq lfs_objects_count
+ end
+ end
+
+ it 'removes the original project' do
+ subject.execute(project_from)
+
+ expect { Project.find(project_from.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'renames the project' do
+ subject.execute(project_from)
+
+ expect(project_to.full_path).to eq project_from.full_path
+ end
+ end
+
+ context 'when project does not have any relation' do
+ it_behaves_like 'overwrite actions'
+ end
+
+ context 'when project with elements' do
+ it_behaves_like 'overwrite actions' do
+ let(:master_user) { create(:user) }
+ let(:reporter_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+ let(:master_group) { create(:group) }
+ let(:reporter_group) { create(:group) }
+ let(:developer_group) { create(:group) }
+
+ before do
+ create_list(:deploy_keys_project, 2, project: project_from)
+ create_list(:notification_setting, 2, source: project_from)
+ create_list(:users_star_project, 2, project: project_from)
+ project_from.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
+ project_from.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
+ project_from.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
+ project_from.add_master(master_user)
+ project_from.add_developer(developer_user)
+ project_from.add_reporter(reporter_user)
+ end
+ end
+ end
+
+ context 'forks' do
+ context 'when moving a root forked project' do
+ it 'moves the descendant forks' do
+ expect(project_from.forks.count).to eq 2
+ expect(project_to.forks.count).to eq 0
+
+ subject.execute(project_from)
+
+ expect(project_from.forks.count).to eq 0
+ expect(project_to.forks.count).to eq 2
+ expect(lvl1_forked_project_1.forked_from_project).to eq project_to
+ expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq project_to
+ expect(lvl1_forked_project_2.forked_from_project).to eq project_to
+ expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq project_to
+ end
+
+ it 'updates the fork network' do
+ expect(project_from.fork_network.root_project).to eq project_from
+ expect(project_from.fork_network.fork_network_members.map(&:project)).to include project_from
+
+ subject.execute(project_from)
+
+ expect(project_to.reload.fork_network.root_project).to eq project_to
+ expect(project_to.fork_network.fork_network_members.map(&:project)).not_to include project_from
+ end
+ end
+ context 'when moving a intermediate forked project' do
+ let(:project_to) { create(:project, namespace: lvl1_forked_project_1.namespace) }
+
+ it 'moves the descendant forks' do
+ expect(lvl1_forked_project_1.forks.count).to eq 2
+ expect(project_to.forks.count).to eq 0
+
+ subject.execute(lvl1_forked_project_1)
+
+ expect(lvl1_forked_project_1.forks.count).to eq 0
+ expect(project_to.forks.count).to eq 2
+ expect(lvl2_forked_project_1_1.forked_from_project).to eq project_to
+ expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq project_to
+ expect(lvl2_forked_project_1_2.forked_from_project).to eq project_to
+ expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq project_to
+ end
+
+ it 'moves the ascendant fork' do
+ subject.execute(lvl1_forked_project_1)
+
+ expect(project_to.reload.forked_from_project).to eq project_from
+ expect(project_to.fork_network_member.forked_from_project).to eq project_from
+ end
+
+ it 'does not update fork network' do
+ subject.execute(lvl1_forked_project_1)
+
+ expect(project_to.reload.fork_network.root_project).to eq project_from
+ end
+ end
+ end
+
+ context 'if an exception is raised' do
+ it 'rollbacks changes' do
+ updated_at = project_from.updated_at
+
+ allow(subject).to receive(:rename_project).and_raise(StandardError)
+
+ expect { subject.execute(project_from) }.to raise_error(StandardError)
+ expect(Project.find(project_from.id)).not_to be_nil
+ expect(project_from.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
+ end
+
+ it 'tries to restore the original project repositories' do
+ allow(subject).to receive(:rename_project).and_raise(StandardError)
+
+ expect(subject).to receive(:attempt_restore_repositories).with(project_from)
+
+ expect { subject.execute(project_from) }.to raise_error(StandardError)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 95a6771c59d..ff9b2372a35 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -37,6 +37,12 @@ describe Projects::TransferService do
transfer_project(project, user, group)
end
+ it 'invalidates the user\'s personal_project_count cache' do
+ expect(user).to receive(:invalidate_personal_projects_count)
+
+ transfer_project(project, user, group)
+ end
+
it 'executes system hooks' do
transfer_project(project, user, group) do |service|
expect(service).to receive(:execute_system_hooks)
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index dd31a677dfe..1b6caeab15d 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -21,76 +21,72 @@ describe Projects::UpdatePagesService do
end
context 'legacy artifacts' do
- %w(tar.gz zip).each do |format|
- let(:extension) { format }
+ let(:extension) { 'zip' }
- context "for valid #{format}" do
+ before do
+ build.update_attributes(legacy_artifacts_file: file)
+ build.update_attributes(legacy_artifacts_metadata: metadata)
+ end
+
+ describe 'pages artifacts' do
+ context 'with expiry date' do
before do
- build.update_attributes(legacy_artifacts_file: file)
- build.update_attributes(legacy_artifacts_metadata: metadata)
+ build.artifacts_expire_in = "2 days"
+ build.save!
end
- describe 'pages artifacts' do
- context 'with expiry date' do
- before do
- build.artifacts_expire_in = "2 days"
- build.save!
- end
-
- it "doesn't delete artifacts" do
- expect(execute).to eq(:success)
-
- expect(build.reload.artifacts?).to eq(true)
- end
- end
-
- context 'without expiry date' do
- it "does delete artifacts" do
- expect(execute).to eq(:success)
+ it "doesn't delete artifacts" do
+ expect(execute).to eq(:success)
- expect(build.reload.artifacts?).to eq(false)
- end
- end
+ expect(build.reload.artifacts?).to eq(true)
end
+ end
- it 'succeeds' do
- expect(project.pages_deployed?).to be_falsey
+ context 'without expiry date' do
+ it "does delete artifacts" do
expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
- # Check that all expected files are extracted
- %w[index.html zero .hidden/file].each do |filename|
- expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
- end
+ expect(build.reload.artifacts?).to eq(false)
end
+ end
+ end
- it 'limits pages size' do
- stub_application_setting(max_pages_size: 1)
- expect(execute).not_to eq(:success)
- end
+ it 'succeeds' do
+ expect(project.pages_deployed?).to be_falsey
+ expect(execute).to eq(:success)
+ expect(project.pages_deployed?).to be_truthy
- it 'removes pages after destroy' do
- expect(PagesWorker).to receive(:perform_in)
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
- project.destroy
- expect(project.pages_deployed?).to be_falsey
- end
+ # Check that all expected files are extracted
+ %w[index.html zero .hidden/file].each do |filename|
+ expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
+ end
+ end
- it 'fails if sha on branch is not latest' do
- build.update_attributes(ref: 'feature')
+ it 'limits pages size' do
+ stub_application_setting(max_pages_size: 1)
+ expect(execute).not_to eq(:success)
+ end
- expect(execute).not_to eq(:success)
- end
+ it 'removes pages after destroy' do
+ expect(PagesWorker).to receive(:perform_in)
+ expect(project.pages_deployed?).to be_falsey
+ expect(execute).to eq(:success)
+ expect(project.pages_deployed?).to be_truthy
+ project.destroy
+ expect(project.pages_deployed?).to be_falsey
+ end
- it 'fails for empty file fails' do
- build.update_attributes(legacy_artifacts_file: empty_file)
+ it 'fails if sha on branch is not latest' do
+ build.update_attributes(ref: 'feature')
- expect { execute }
- .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
- end
- end
+ expect(execute).not_to eq(:success)
+ end
+
+ it 'fails for empty file fails' do
+ build.update_attributes(legacy_artifacts_file: empty_file)
+
+ expect { execute }
+ .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index beabba99cf5..83664bae046 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -66,6 +66,7 @@ RSpec.configure do |config|
config.include MigrationsHelpers, :migration
config.include StubFeatureFlags
config.include StubENV
+ config.include ExpectOffense
config.infer_spec_type_from_file_location!
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
new file mode 100644
index 00000000000..35718ba90c5
--- /dev/null
+++ b/spec/support/helpers/expect_offense.rb
@@ -0,0 +1,20 @@
+require 'rubocop/rspec/support'
+
+# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
+# rubocop-rspec gem extension of RuboCop's ExpectOffense module.
+#
+# This mixin is the same as rubocop's ExpectOffense except the default
+# filename ends with `_spec.rb`
+module ExpectOffense
+ include RuboCop::RSpec::ExpectOffense
+
+ DEFAULT_FILENAME = 'example_spec.rb'.freeze
+
+ def expect_offense(source, filename = DEFAULT_FILENAME)
+ super
+ end
+
+ def expect_no_offenses(source, filename = DEFAULT_FILENAME)
+ super
+ end
+end
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index 75982432ab4..e61983c60b4 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -5,9 +5,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
%w[fix improve/awesome].each do |source_branch|
issuable =
if issuable_type == :issue
- create(issuable_type, project: project)
+ create(issuable_type, project: project, author: project.creator)
else
- create(issuable_type, source_project: project, source_branch: source_branch)
+ create(issuable_type, source_project: project, source_branch: source_branch, author: project.creator)
end
@issuable_ids << issuable.id
@@ -16,7 +16,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
it "creates indexed meta-data object for issuable notes and votes count" do
if action
- get action
+ get action, author_id: project.creator.id
else
get :index, namespace_id: project.namespace, project_id: project
end
@@ -35,7 +35,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
it "doesn't execute any queries with false conditions" do
get_action =
if action
- proc { get action }
+ proc { get action, author_id: project.creator.id }
else
proc { get :index, namespace_id: project2.namespace, project_id: project2 }
end
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
new file mode 100644
index 00000000000..439ef5ed92e
--- /dev/null
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -0,0 +1,15 @@
+shared_examples 'issuables requiring filter' do |action|
+ it "doesn't load any issuables if no filter is set" do
+ expect_any_instance_of(described_class).not_to receive(:issuables_collection)
+
+ get action
+
+ expect(response).to render_template(action)
+ end
+
+ it "loads issuables if at least one filter is set" do
+ expect_any_instance_of(described_class).to receive(:issuables_collection).and_call_original
+
+ get action, author_id: user.id
+ end
+end
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 9e692159bd0..c93152b88e3 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'projects/jobs/show' do
describe 'environment info in job view' do
context 'job with latest deployment' do
let(:build) do
- create(:ci_build, :success, environment: 'staging')
+ create(:ci_build, :success, :trace_artifact, environment: 'staging')
end
before do
@@ -40,11 +40,11 @@ describe 'projects/jobs/show' do
context 'job with outdated deployment' do
let(:build) do
- create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let(:second_build) do
- create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let(:environment) do
@@ -70,7 +70,7 @@ describe 'projects/jobs/show' do
context 'job failed to deploy' do
let(:build) do
- create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
@@ -88,7 +88,7 @@ describe 'projects/jobs/show' do
context 'job will deploy' do
let(:build) do
- create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
end
context 'when environment exists' do
@@ -136,7 +136,7 @@ describe 'projects/jobs/show' do
context 'job that failed to deploy and environment has not been created' do
let(:build) do
- create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
@@ -154,7 +154,7 @@ describe 'projects/jobs/show' do
context 'job that will deploy and environment has not been created' do
let(:build) do
- create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
+ create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
@@ -174,8 +174,9 @@ describe 'projects/jobs/show' do
end
context 'when job is running' do
+ let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) }
+
before do
- build.run!
render
end
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 3ca67114558..b1c6565c08a 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -28,6 +28,6 @@ describe 'projects/merge_requests/_commits.html.haml' do
commit = merge_request.commits.first # HEAD
href = diffs_project_merge_request_path(target_project, merge_request, commit_id: commit)
- expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
+ expect(rendered).to have_link(href: href)
end
end