summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.scss-lint.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock6
-rw-r--r--Gemfile.rails5.lock11
-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/blob/file_template_mediator.js2
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js4
-rw-r--r--app/assets/javascripts/boards/components/board.js4
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue (renamed from app/assets/javascripts/boards/components/board_blank_state.js)71
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.js6
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js6
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js6
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js3
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js3
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.js3
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.js6
-rw-r--r--app/assets/javascripts/boards/index.js7
-rw-r--r--app/assets/javascripts/boards/mixins/modal_mixins.js4
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js5
-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/labels_select.js3
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js20
-rw-r--r--app/assets/javascripts/milestone_select.js3
-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/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/search_autocomplete.js8
-rw-r--r--app/assets/javascripts/users_select.js3
-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/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/concerns/authenticates_with_two_factor.rb2
-rw-r--r--app/controllers/dashboard_controller.rb19
-rw-r--r--app/controllers/groups/settings/badges_controller.rb13
-rw-r--r--app/controllers/jwt_controller.rb3
-rw-r--r--app/controllers/projects/deploy_tokens_controller.rb10
-rw-r--r--app/controllers/projects/repositories_controller.rb22
-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/namespace.rb4
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/project_deploy_token.rb8
-rw-r--r--app/models/service.rb6
-rw-r--r--app/models/user.rb26
-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/deploy_tokens/create_service.rb7
-rw-r--r--app/services/notification_recipient_service.rb3
-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/notify/push_to_merge_request_email.html.haml2
-rw-r--r--app/views/notify/push_to_merge_request_email.text.haml2
-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/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/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/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/32617-fix-template-selector-menu-visibility.yml6
-rw-r--r--changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.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/44224-remove-gl.yml5
-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/jivl-summary-statistics-prometheus-dashboard.yml5
-rw-r--r--changelogs/unreleased/jramsay-38830-tarball.yml5
-rw-r--r--changelogs/unreleased/move-board-blank-state-vue-component.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/winh-41174-projects-groups-badges-ui.yml5
-rw-r--r--changelogs/unreleased/zj-find-license-opt-out.yml5
-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/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/development/emails.md18
-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/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/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/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/email/handler/create_issue_handler.rb2
-rw-r--r--lib/gitlab/git/checksum.rb82
-rw-r--r--lib/gitlab/git/conflict/resolver.rb2
-rw-r--r--lib/gitlab/git/repository.rb124
-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--package.json2
-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/projects/repositories_controller_spec.rb19
-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/merge_request/user_sees_mini_pipeline_graph_spec.rb9
-rw-r--r--spec/features/projects/files/template_selector_menu_spec.rb66
-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/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/board_blank_state_spec.js4
-rw-r--r--spec/javascripts/boards/modal_store_spec.js3
-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/pipelines/graph/action_component_spec.js25
-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/email/handler_spec.rb24
-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/mailers/notify_spec.rb16
-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/project_deploy_token_spec.rb14
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/user_spec.rb35
-rw-r--r--spec/policies/deploy_token_policy_spec.rb45
-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/deploy_tokens/create_service_spec.rb45
-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
-rw-r--r--yarn.lock6
352 files changed, 15318 insertions, 9812 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/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 21c8c7b46b8..a8a18875682 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-7.1.1
+7.1.2
diff --git a/Gemfile b/Gemfile
index 5eac6d73269..7f5b1f1d3ef 100644
--- a/Gemfile
+++ b/Gemfile
@@ -384,6 +384,7 @@ group :test do
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2'
+ gem 'rails-controller-testing' if rails5? # Rails5 only gem.
gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.0.5'
@@ -421,7 +422,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..03fe5f2ed26 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)
@@ -678,6 +678,10 @@ GEM
bundler (>= 1.3.0)
railties (= 5.0.6)
sprockets-rails (>= 2.0.0)
+ rails-controller-testing (1.0.2)
+ actionpack (~> 5.x, >= 5.0.1)
+ actionview (~> 5.x, >= 5.0.1)
+ activesupport (~> 5.x)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (2.0.3)
@@ -1062,7 +1066,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)
@@ -1145,6 +1149,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 5.0.6)
+ rails-controller-testing
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1)
rainbow (~> 2.2)
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/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index 030ca1907e5..ff1cbcad145 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -94,7 +94,7 @@ export default class FileTemplateMediator {
const hash = urlPieces[1];
if (hash === 'preview') {
this.hideTemplateSelectorMenu();
- } else if (hash === 'editor') {
+ } else if (hash === 'editor' && !this.typeSelector.isHidden()) {
this.showTemplateSelectorMenu();
}
});
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index e52cf249f3a..02228434a29 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -32,6 +32,10 @@ export default class FileTemplateSelector {
}
}
+ isHidden() {
+ return this.$wrapper.hasClass('hidden');
+ }
+
getToggleText() {
return this.$dropdownToggleText.text();
}
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 3cffd91716a..bea818010a4 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -5,7 +5,7 @@ import Sortable from 'vendor/Sortable';
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list.vue';
-import boardBlankState from './board_blank_state';
+import BoardBlankState from './board_blank_state.vue';
import './board_delete';
const Store = gl.issueBoards.BoardsStore;
@@ -18,7 +18,7 @@ gl.issueBoards.Board = Vue.extend({
components: {
boardList,
'board-delete': gl.issueBoards.BoardDelete,
- boardBlankState,
+ BoardBlankState,
},
props: {
list: Object,
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.vue
index 72db626d3c7..2049eeb9c30 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,42 +1,11 @@
+<script>
/* global ListLabel */
-
import _ from 'underscore';
import Cookies from 'js-cookie';
const Store = gl.issueBoards.BoardsStore;
export default {
- template: `
- <div class="board-blank-state">
- <p>
- Add the following default lists to your Issue Board with one click:
- </p>
- <ul class="board-blank-state-list">
- <li v-for="label in predefinedLabels">
- <span
- class="label-color"
- :style="{ backgroundColor: label.color }">
- </span>
- {{ label.title }}
- </li>
- </ul>
- <p>
- Starting out with the default set of lists will get you right on the way to making the most of your board.
- </p>
- <button
- class="btn btn-create btn-inverted btn-block"
- type="button"
- @click.stop="addDefaultLists">
- Add default lists
- </button>
- <button
- class="btn btn-default btn-block"
- type="button"
- @click.stop="clearBlankState">
- Nevermind, I'll use my own
- </button>
- </div>
- `,
data() {
return {
predefinedLabels: [
@@ -89,3 +58,41 @@ export default {
clearBlankState: Store.removeBlankState.bind(Store),
},
};
+
+</script>
+
+<template>
+ <div class="board-blank-state">
+ <p>
+ Add the following default lists to your Issue Board with one click:
+ </p>
+ <ul class="board-blank-state-list">
+ <li
+ v-for="(label, index) in predefinedLabels"
+ :key="index"
+ >
+ <span
+ class="label-color"
+ :style="{ backgroundColor: label.color }">
+ </span>
+ {{ label.title }}
+ </li>
+ </ul>
+ <p>
+ Starting out with the default set of lists will get you
+ right on the way to making the most of your board.
+ </p>
+ <button
+ class="btn btn-create btn-inverted btn-block"
+ type="button"
+ @click.stop="addDefaultLists">
+ Add default lists
+ </button>
+ <button
+ class="btn btn-default btn-block"
+ type="button"
+ @click.stop="clearBlankState">
+ Nevermind, I'll use my own
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js
index e571b11a83d..9e37f95cdd6 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalEmptyState = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return ModalStore.store;
},
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 03cd7ef65cb..9735e0ddacc 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -3,11 +3,11 @@ import Flash from '../../../flash';
import { __ } from '../../../locale';
import './lists_dropdown';
import { pluralize } from '../../../lib/utils/text_utility';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalFooter = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 31f59d295bf..67c29ebca72 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import modalFilters from './filters';
import './tabs';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalHeader = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
props: {
projectId: {
type: Number,
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index d825ff38587..3083b3e4405 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -7,8 +7,7 @@ import './header';
import './list';
import './footer';
import './empty_state';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.IssuesModal = Vue.extend({
props: {
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index 7c62134b3a3..6b04a6c7a6c 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -2,8 +2,7 @@
import Vue from 'vue';
import bp from '../../../breakpoints';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.ModalList = Vue.extend({
props: {
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 4684ea76647..e644de2d4fc 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
@@ -1,6 +1,5 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
data() {
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
index 3e5d08e3d75..b6465a88e5e 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalTabs = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return ModalStore.store;
},
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 8b1c14c04ff..a6f8681cfac 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -17,9 +17,9 @@ import './models/milestone';
import './models/project';
import './models/assignee';
import './stores/boards_store';
-import './stores/modal_store';
+import ModalStore from './stores/modal_store';
import BoardService from './services/board_service';
-import './mixins/modal_mixins';
+import modalMixin from './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
import './components/board';
@@ -31,7 +31,6 @@ import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/fi
export default () => {
const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore;
- const ModalStore = gl.issueBoards.ModalStore;
window.gl = window.gl || {};
@@ -176,7 +175,7 @@ export default () => {
gl.IssueBoardsModalAddBtn = new Vue({
el: document.getElementById('js-add-issues-btn'),
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
index 2b0a1aaa89f..6c97e1629bf 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
@@ -1,6 +1,6 @@
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../stores/modal_store';
-gl.issueBoards.ModalMixins = {
+export default {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 4fdc925c825..a4220cd840d 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -1,6 +1,3 @@
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
class ModalStore {
constructor() {
this.store = {
@@ -95,4 +92,4 @@ class ModalStore {
}
}
-gl.issueBoards.ModalStore = new ModalStore();
+export default new ModalStore();
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/labels_select.js b/app/assets/javascripts/labels_select.js
index 824d3f7ca09..d0050abb8e9 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -10,6 +10,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
import flash from './flash';
+import ModalStore from './boards/stores/modal_store';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -350,7 +351,7 @@ export default class LabelsSelect {
}
if ($dropdown.closest('.add-issues-modal').length) {
- boardsModel = gl.issueBoards.ModalStore.store.filter;
+ boardsModel = ModalStore.store.filter;
}
if (boardsModel) {
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/milestone_select.js b/app/assets/javascripts/milestone_select.js
index c749042a14a..d0a2b27b0e6 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -6,6 +6,7 @@ import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
+import ModalStore from './boards/stores/modal_store';
export default class MilestoneSelect {
constructor(currentProject, els, options = {}) {
@@ -164,7 +165,7 @@ export default class MilestoneSelect {
}
if ($dropdown.closest('.add-issues-modal').length) {
- boardsStore = gl.issueBoards.ModalStore.store.filter;
+ boardsStore = ModalStore.store.filter;
}
if (boardsStore) {
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/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/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/users_select.js b/app/assets/javascripts/users_select.js
index f3b961eb109..520a0b3f424 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -5,6 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
+import ModalStore from './boards/stores/modal_store';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
@@ -441,7 +442,7 @@ function UsersSelect(currentUser, els, options = {}) {
return;
}
if ($el.closest('.add-issues-modal').length) {
- gl.issueBoards.ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
+ ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
} else if (handleClick) {
e.preventDefault();
handleClick(user, isMarking);
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/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/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/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/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..937b0e39cbd 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,26 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- send_git_archive @repository, ref: params[:ref], format: params[:format]
+ append_sha = params[:append_sha]
+
+ if @ref
+ shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
+ append_sha = false if @filename == shortname
+ end
+
+ 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]
+
+ return unless @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/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/service.rb b/app/models/service.rb
index e9b6f005aec..f7e3f7590ad 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -206,7 +206,11 @@ class Service < ActiveRecord::Base
args.each do |arg|
class_eval %{
def #{arg}?
- ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+ if Gitlab.rails5?
+ !ActiveModel::Type::Boolean::FALSE_VALUES.include?(#{arg})
+ else
+ ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+ end
end
}
end
diff --git a/app/models/user.rb b/app/models/user.rb
index ce56b39b1c8..42bb27d4753 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)
@@ -997,7 +993,7 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject
- .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ .where(project: authorized_projects(Gitlab::Access::MASTER))
.select(:runner_id)
Ci::Runner.specific.where(id: runner_ids)
end
@@ -1046,6 +1042,12 @@ class User < ActiveRecord::Base
end
end
+ 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
todos_done_count(force: true)
todos_pending_count(force: true)
@@ -1056,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
@@ -1074,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).
#
@@ -1197,15 +1204,6 @@ class User < ActiveRecord::Base
], remove_duplicates: false)
end
- def ci_projects_union
- scope = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
- groups = groups_projects.where(members: scope)
- other = projects.where(members: scope)
-
- Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
- other.select(:id)])
- end
-
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications)
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/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/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index e4be953e810..b82d9c64296 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -54,8 +54,7 @@ module NotificationRecipientService
users = users.includes(:notification_settings)
end
- users = Array(users)
- users.compact!
+ users = Array(users).compact
recipients.concat(users.map { |u| make_recipient(u, type, reason) })
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/notify/push_to_merge_request_email.html.haml b/app/views/notify/push_to_merge_request_email.html.haml
index 4c507c08ed7..67744ec1cee 100644
--- a/app/views/notify/push_to_merge_request_email.html.haml
+++ b/app/views/notify/push_to_merge_request_email.html.haml
@@ -7,7 +7,7 @@
- count = @existing_commits.size
%ul
%li
- - if count.one?
+ - if count == 1
- commit_id = @existing_commits.first[:short_id]
= link_to(commit_id, project_commit_url(@merge_request.target_project, commit_id))
- else
diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml
index 553f771f1a6..95759d127e2 100644
--- a/app/views/notify/push_to_merge_request_email.text.haml
+++ b/app/views/notify/push_to_merge_request_email.text.haml
@@ -4,7 +4,7 @@
\
- if @existing_commits.any?
- count = @existing_commits.size
- - commits_id = count.one? ? @existing_commits.first[:short_id] : "#{@existing_commits.first[:short_id]}...#{@existing_commits.last[:short_id]}"
+ - commits_id = count == 1 ? @existing_commits.first[:short_id] : "#{@existing_commits.first[:short_id]}...#{@existing_commits.last[:short_id]}"
- commits_text = "#{count} commit".pluralize(count)
* #{commits_id} - #{commits_text} from branch `#{@merge_request.target_branch}`
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/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/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/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/32617-fix-template-selector-menu-visibility.yml b/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml
new file mode 100644
index 00000000000..c73be5a901e
--- /dev/null
+++ b/changelogs/unreleased/32617-fix-template-selector-menu-visibility.yml
@@ -0,0 +1,6 @@
+---
+title: Fix template selector menu visibility when toggling preview mode in file edit
+ view
+merge_request: 18118
+author: Fabian Schneider
+type: fixed
diff --git a/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml b/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml
new file mode 100644
index 00000000000..30481e7af84
--- /dev/null
+++ b/changelogs/unreleased/41981-allow-group-owner-to-enable-runners-from-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: 'Allow group owner to enable runners from subgroups (#41981)'
+merge_request: 18009
+author:
+type: fixed
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/44224-remove-gl.yml b/changelogs/unreleased/44224-remove-gl.yml
new file mode 100644
index 00000000000..1c792883f09
--- /dev/null
+++ b/changelogs/unreleased/44224-remove-gl.yml
@@ -0,0 +1,5 @@
+---
+title: Removes modal boards store and mixins from global scope
+merge_request:
+author:
+type: other
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/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/move-board-blank-state-vue-component.yml b/changelogs/unreleased/move-board-blank-state-vue-component.yml
new file mode 100644
index 00000000000..0a278a8c009
--- /dev/null
+++ b/changelogs/unreleased/move-board-blank-state-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move BoardBlankState vue component
+merge_request: 17666
+author: George Tsiolis
+type: performance
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/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/changelogs/unreleased/zj-find-license-opt-out.yml b/changelogs/unreleased/zj-find-license-opt-out.yml
new file mode 100644
index 00000000000..be2656601a9
--- /dev/null
+++ b/changelogs/unreleased/zj-find-license-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detect repository license on Gitaly by default
+merge_request:
+author:
+type: performance
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/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/development/emails.md b/doc/development/emails.md
index 4dbf064fd75..73cac82caf0 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -74,6 +74,24 @@ See the [Rails guides] for more info.
1. Reply by email should now be working.
+## Email namespace
+
+If you need to implement a new feature which requires a new email handler, follow these rules:
+
+ - You must choose a namespace. The namespace cannot contain `/` or `+`, and cannot match `\h{16}`.
+ - If your feature is related to a project, you will append the namespace **after** the project path, separated by a `+`
+ - If you have different actions in the namespace, you add the actions **after** the namespace separated by a `+`. The action name cannot contain `/` or `+`, , and cannot match `\h{16}`.
+ - You will register your handlers in `lib/gitlab/email/handler.rb`
+
+Therefore, these are the only valid formats for an email handler:
+
+ - `path/to/project+namespace`
+ - `path/to/project+namespace+action`
+ - `namespace`
+ - `namespace+action`
+
+Please note that `path/to/project` is used in GitLab Premium as handler for the Service Desk feature.
+
---
[Return to Development documentation](README.md)
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/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/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/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/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index a616a80e8f5..05a60deb7d3 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -14,7 +14,7 @@ module Gitlab
end
def can_handle?
- !incoming_email_token.nil?
+ !incoming_email_token.nil? && !incoming_email_token.include?("+") && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)
end
def execute
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..f1b575bd872 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,
@@ -1036,7 +1045,8 @@ module Gitlab
end
def license_short_name
- gitaly_migrate(:license_short_name) do |is_enabled|
+ gitaly_migrate(:license_short_name,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_repository_client.license_short_name
else
@@ -1362,6 +1372,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 +1478,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 +1601,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 +2433,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/package.json b/package.json
index 56fd2575e91..27f612aca38 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
- "@gitlab-org/gitlab-svgs": "^1.16.0",
+ "@gitlab-org/gitlab-svgs": "^1.17.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.0",
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/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 04d16e98913..c3b71458e38 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,7 +22,20 @@ 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
+
+ it 'handles legacy queries with no ref' do
+ get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
@@ -33,7 +46,7 @@ describe Projects::RepositoriesController do
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/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/projects/files/template_selector_menu_spec.rb b/spec/features/projects/files/template_selector_menu_spec.rb
new file mode 100644
index 00000000000..b549a69ddf3
--- /dev/null
+++ b/spec/features/projects/files/template_selector_menu_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+feature 'Template selector menu', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in user
+ end
+
+ context 'editing a non-matching file' do
+ before do
+ create_and_edit_file('README.md')
+ end
+
+ scenario 'is not displayed' do
+ check_template_selector_menu_display(false)
+ end
+
+ context 'user toggles preview' do
+ before do
+ click_link 'Preview'
+ end
+
+ scenario 'template selector menu is not displayed' do
+ check_template_selector_menu_display(false)
+ click_link 'Write'
+ check_template_selector_menu_display(false)
+ end
+ end
+ end
+
+ context 'editing a matching file' do
+ before do
+ visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
+ end
+
+ scenario 'is displayed' do
+ check_template_selector_menu_display(true)
+ end
+
+ context 'user toggles preview' do
+ before do
+ click_link 'Preview'
+ end
+
+ scenario 'template selector menu is hidden and shown correctly' do
+ check_template_selector_menu_display(false)
+ click_link 'Write'
+ check_template_selector_menu_display(true)
+ end
+ end
+ end
+end
+
+def check_template_selector_menu_display(is_visible)
+ count = is_visible ? 1 : 0
+ expect(page).to have_css('.template-selectors-menu', count: count)
+end
+
+def create_and_edit_file(file_name)
+ visit project_new_blob_path(project, 'master', file_name: file_name)
+ click_button "Commit changes"
+ visit project_edit_blob_path(project, File.join(project.default_branch, file_name))
+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/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/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index f757dadfada..664ea202e93 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,7 +1,7 @@
/* global BoardService */
import Vue from 'vue';
import '~/boards/stores/boards_store';
-import boardBlankState from '~/boards/components/board_blank_state';
+import BoardBlankState from '~/boards/components/board_blank_state.vue';
import { mockBoardService } from './mock_data';
describe('Boards blank state', () => {
@@ -9,7 +9,7 @@ describe('Boards blank state', () => {
let fail = false;
beforeEach((done) => {
- const Comp = Vue.extend(boardBlankState);
+ const Comp = Vue.extend(BoardBlankState);
gl.issueBoards.BoardsStore.create();
gl.boardService = mockBoardService();
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
index e9d77f035e3..797693a21aa 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/javascripts/boards/modal_store_spec.js
@@ -4,12 +4,11 @@ import '~/vue_shared/models/label';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/models/assignee';
-import '~/boards/stores/modal_store';
+import Store from '~/boards/stores/modal_store';
describe('Modal store', () => {
let issue;
let issue2;
- const Store = gl.issueBoards.ModalStore;
beforeEach(() => {
// Setup default state
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/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/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/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 650b01c4df4..386d73e6115 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -14,4 +14,28 @@ describe Gitlab::Email::Handler do
expect(described_class.for('email', '')).to be_nil
end
end
+
+ describe 'regexps are set properly' do
+ let(:addresses) do
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+ end
+
+ it 'picks each handler at least once' do
+ matched_handlers = addresses.map do |address|
+ described_class.for('email', address).class
+ end
+
+ expect(matched_handlers.uniq).to match_array(Gitlab::Email::Handler::HANDLERS)
+ end
+
+ it 'can pick exactly one handler for each address' do
+ addresses.each do |address|
+ matched_handlers = Gitlab::Email::Handler::HANDLERS.select do |handler|
+ handler.new('email', address).can_handle?
+ end
+
+ expect(matched_handlers.count).to eq(1), "#{address} matches #{matched_handlers.count} handlers: #{matched_handlers}"
+ end
+ 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/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 971a88e9ee9..43e419cd7de 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -390,11 +390,11 @@ describe Notify do
end
end
- describe 'that have new commits' do
+ shared_examples 'a push to an existing merge request' do
let(:push_user) { create(:user) }
subject do
- described_class.push_to_merge_request_email(recipient.id, merge_request.id, push_user.id, new_commits: merge_request.commits)
+ described_class.push_to_merge_request_email(recipient.id, merge_request.id, push_user.id, new_commits: merge_request.commits, existing_commits: existing_commits)
end
it_behaves_like 'a multiple recipients email'
@@ -419,6 +419,18 @@ describe Notify do
end
end
end
+
+ describe 'that have new commits' do
+ let(:existing_commits) { [] }
+
+ it_behaves_like 'a push to an existing merge request'
+ end
+
+ describe 'that have new commits on top of an existing one' do
+ let(:existing_commits) { [merge_request.commits.first] }
+
+ it_behaves_like 'a push to an existing merge request'
+ end
end
context 'for issue notes' 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/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..35db7616efb 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1850,6 +1850,21 @@ describe User do
it_behaves_like :member
end
+
+ context 'with subgroup with different owner for project runner', :nested_groups do
+ let(:group) { create(:group) }
+ let(:another_user) { create(:user) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project) { create(:project, group: subgroup) }
+
+ def add_user(access)
+ group.add_user(user, access)
+ group.add_user(another_user, :owner)
+ subgroup.add_user(another_user, :owner)
+ end
+
+ it_behaves_like :member
+ end
end
describe '#projects_with_reporter_access_limited_to' do
@@ -2234,6 +2249,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 +2312,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/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/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/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
diff --git a/yarn.lock b/yarn.lock
index af7bda5d562..243b83f4471 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.16.0":
- version "1.16.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.16.0.tgz#6c88a1bd9f5b3d3e5bf6a6d89d61724022185667"
+"@gitlab-org/gitlab-svgs@^1.17.0":
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.17.0.tgz#d0c74d9e44c127ccfad16941f352088b86f86c89"
"@types/jquery@^2.0.40":
version "2.0.48"