summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2017-09-06 21:03:07 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2017-09-06 21:03:07 +0200
commit64db7077740385eabc77178c8fd386330b57183b (patch)
tree55c2b05f1fa3d20c9702ad5bda7c2f2fe21f4173 /spec
parent051623d215d6a00983b5c247a7c83f474387622d (diff)
parentcd8ea329f0d64c04e5dee00fb8916268dae7a6f7 (diff)
downloadgitlab-ce-64db7077740385eabc77178c8fd386330b57183b.tar.gz
Merge branch 'zj/gitlab-ce-zj-auto-devops-table' into 37158-autodevops-banner
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb28
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb82
-rw-r--r--spec/controllers/profiles_controller_spec.rb25
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb8
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb209
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb22
-rw-r--r--spec/factories/ci/builds.rb7
-rw-r--r--spec/factories/ci/pipeline_variables.rb (renamed from spec/factories/ci/pipeline_variable_variables.rb)0
-rw-r--r--spec/factories/ci/pipelines.rb5
-rw-r--r--spec/factories/ci/runners.rb5
-rw-r--r--spec/factories/ci/trigger_requests.rb9
-rw-r--r--spec/factories/gpg_signature.rb2
-rw-r--r--spec/factories/project_auto_devops.rb1
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb8
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb4
-rw-r--r--spec/features/boards/boards_spec.rb18
-rw-r--r--spec/features/commits_spec.rb101
-rw-r--r--spec/features/issues/form_spec.rb6
-rw-r--r--spec/features/issues/issue_detail_spec.rb14
-rw-r--r--spec/features/issues/move_spec.rb32
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb4
-rw-r--r--spec/features/merge_requests/form_spec.rb7
-rw-r--r--spec/features/merge_requests/resolve_outdated_diff_discussions.rb78
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb10
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb4
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb14
-rw-r--r--spec/features/projects/jobs_spec.rb40
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb3
-rw-r--r--spec/features/projects_spec.rb43
-rw-r--r--spec/features/runners_spec.rb15
-rw-r--r--spec/features/signed_commits_spec.rb179
-rw-r--r--spec/features/task_lists_spec.rb4
-rw-r--r--spec/finders/issues_finder_spec.rb18
-rw-r--r--spec/finders/merge_requests_finder_spec.rb14
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json4
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule_variable.json8
-rw-r--r--spec/helpers/blame_helper_spec.rb27
-rw-r--r--spec/helpers/groups_helper_spec.rb3
-rw-r--r--spec/helpers/issues_helper_spec.rb10
-rw-r--r--spec/helpers/markup_helper_spec.rb91
-rw-r--r--spec/helpers/notes_helper_spec.rb32
-rw-r--r--spec/helpers/profiles_helper_spec.rb31
-rw-r--r--spec/helpers/search_helper_spec.rb20
-rw-r--r--spec/javascripts/api_spec.js6
-rw-r--r--spec/javascripts/feature_highlight/feature_highlight_helper_spec.js219
-rw-r--r--spec/javascripts/feature_highlight/feature_highlight_options_spec.js45
-rw-r--r--spec/javascripts/feature_highlight/feature_highlight_spec.js122
-rw-r--r--spec/javascripts/gl_dropdown_spec.js323
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js21
-rw-r--r--spec/javascripts/issue_show/components/fields/project_move_spec.js38
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js2
-rw-r--r--spec/javascripts/monitoring/graph/flag_spec.js4
-rw-r--r--spec/javascripts/monitoring/graph/legend_spec.js122
-rw-r--r--spec/javascripts/monitoring/graph_row_spec.js62
-rw-r--r--spec/javascripts/monitoring/graph_spec.js34
-rw-r--r--spec/javascripts/monitoring/mock_data.js7580
-rw-r--r--spec/javascripts/monitoring/monitoring_paths_spec.js34
-rw-r--r--spec/javascripts/monitoring/monitoring_store_spec.js4
-rw-r--r--spec/javascripts/monitoring/utils/multiple_time_series_spec.js21
-rw-r--r--spec/javascripts/project_title_spec.js59
-rw-r--r--spec/javascripts/projects_dropdown/components/app_spec.js348
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js72
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_item_spec.js65
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_search_spec.js84
-rw-r--r--spec/javascripts/projects_dropdown/components/search_spec.js101
-rw-r--r--spec/javascripts/projects_dropdown/mock_data.js96
-rw-r--r--spec/javascripts/projects_dropdown/service/projects_service_spec.js179
-rw-r--r--spec/javascripts/projects_dropdown/store/projects_store_spec.js41
-rw-r--r--spec/javascripts/sidebar/mock_data.js41
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js40
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js142
-rw-r--r--spec/javascripts/sidebar/sidebar_service_spec.js28
-rw-r--r--spec/javascripts/sidebar/sidebar_store_spec.js14
-rw-r--r--spec/javascripts/vue_shared/components/identicon_spec.js28
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb19
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb36
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb90
-rw-r--r--spec/lib/banzai/renderer_spec.rb36
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb39
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/stage/seed_spec.rb20
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb19
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb123
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb232
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb43
-rw-r--r--spec/lib/gitlab/gpg_spec.rb15
-rw-r--r--spec/lib/gitlab/i18n/po_linter_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb19
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml6
-rw-r--r--spec/lib/gitlab/issuables_count_for_state_spec.rb37
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb19
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb190
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb120
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb18
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb179
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb79
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb24
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb51
-rw-r--r--spec/models/ci/build_spec.rb78
-rw-r--r--spec/models/ci/pipeline_spec.rb141
-rw-r--r--spec/models/ci/runner_spec.rb76
-rw-r--r--spec/models/ci/trigger_request_spec.rb17
-rw-r--r--spec/models/commit_status_spec.rb21
-rw-r--r--spec/models/concerns/issuable_spec.rb109
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb16
-rw-r--r--spec/models/gpg_key_spec.rb38
-rw-r--r--spec/models/group_spec.rb19
-rw-r--r--spec/models/member_spec.rb9
-rw-r--r--spec/models/merge_request_spec.rb38
-rw-r--r--spec/models/project_auto_devops_spec.rb17
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb13
-rw-r--r--spec/models/project_spec.rb148
-rw-r--r--spec/models/repository_spec.rb55
-rw-r--r--spec/models/user_spec.rb80
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb34
-rw-r--r--spec/requests/api/branches_spec.rb16
-rw-r--r--spec/requests/api/commit_statuses_spec.rb7
-rw-r--r--spec/requests/api/commits_spec.rb8
-rw-r--r--spec/requests/api/files_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb31
-rw-r--r--spec/requests/api/issues_spec.rb10
-rw-r--r--spec/requests/api/jobs_spec.rb94
-rw-r--r--spec/requests/api/merge_requests_spec.rb12
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb160
-rw-r--r--spec/requests/api/projects_spec.rb58
-rw-r--r--spec/requests/api/runner_spec.rb56
-rw-r--r--spec/requests/api/runners_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb326
-rw-r--r--spec/requests/api/v3/commits_spec.rb4
-rw-r--r--spec/requests/api/v3/files_spec.rb4
-rw-r--r--spec/requests/api/v3/projects_spec.rb1
-rw-r--r--spec/requests/api/v3/triggers_spec.rb5
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb16
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb52
-rw-r--r--spec/services/ci/register_job_service_spec.rb92
-rw-r--r--spec/services/ci/retry_build_service_spec.rb15
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb62
-rw-r--r--spec/services/projects/update_pages_service_spec.rb1
-rw-r--r--spec/support/cycle_analytics_helpers.rb6
-rw-r--r--spec/support/group_members_shared_example.rb27
-rw-r--r--spec/support/seed_helper.rb2
-rw-r--r--spec/support/test_env.rb20
-rw-r--r--spec/views/ci/lints/show.html.haml_spec.rb4
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb (renamed from spec/views/layouts/nav/_project.html.haml_spec.rb)6
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb16
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb9
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb6
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb22
152 files changed, 11674 insertions, 3145 deletions
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 572b567cddf..be27bbb4283 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -241,13 +241,10 @@ describe AutocompleteController do
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq(2)
-
- expect(json_response.first['id']).to eq(0)
- expect(json_response.first['name_with_namespace']).to eq 'No project'
+ expect(json_response.size).to eq(1)
- expect(json_response.last['id']).to eq authorized_project.id
- expect(json_response.last['name_with_namespace']).to eq authorized_project.name_with_namespace
+ expect(json_response.first['id']).to eq authorized_project.id
+ expect(json_response.first['name_with_namespace']).to eq authorized_project.name_with_namespace
end
end
end
@@ -265,10 +262,10 @@ describe AutocompleteController do
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq(2)
+ expect(json_response.size).to eq(1)
- expect(json_response.last['id']).to eq authorized_search_project.id
- expect(json_response.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
+ expect(json_response.first['id']).to eq authorized_search_project.id
+ expect(json_response.first['name_with_namespace']).to eq authorized_search_project.name_with_namespace
end
end
end
@@ -292,7 +289,7 @@ describe AutocompleteController do
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq 3 # Of a total of 4
+ expect(json_response.size).to eq 2 # Of a total of 3
end
end
end
@@ -312,9 +309,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
- it 'returns "No project"' do
- expect(json_response.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
- expect(json_response.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq 2 # Of a total of 3
end
end
end
@@ -331,10 +328,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- it 'returns a single "No project"' do
+ it 'returns no projects' do
expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq(1) # 'No project'
- expect(json_response.first['id']).to eq 0
+ expect(json_response.size).to eq(0)
end
end
end
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
new file mode 100644
index 00000000000..c9687af4dd2
--- /dev/null
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe IssuableCollections do
+ let(:user) { create(:user) }
+
+ let(:controller) do
+ klass = Class.new do
+ def self.helper_method(name); end
+
+ include IssuableCollections
+ end
+
+ controller = klass.new
+
+ allow(controller).to receive(:params).and_return(state: 'opened')
+
+ controller
+ end
+
+ describe '#redirect_out_of_range' do
+ before do
+ allow(controller).to receive(:url_for)
+ end
+
+ it 'returns true and redirects if the offset is out of range' do
+ relation = double(:relation, current_page: 10)
+
+ expect(controller).to receive(:redirect_to)
+ expect(controller.send(:redirect_out_of_range, relation, 2)).to eq(true)
+ end
+
+ it 'returns false if the offset is not out of range' do
+ relation = double(:relation, current_page: 1)
+
+ expect(controller).not_to receive(:redirect_to)
+ expect(controller.send(:redirect_out_of_range, relation, 2)).to eq(false)
+ end
+ end
+
+ describe '#issues_page_count' do
+ it 'returns the number of issue pages' do
+ project = create(:project, :public)
+
+ create(:issue, project: project)
+
+ finder = IssuesFinder.new(user)
+ issues = finder.execute
+
+ allow(controller).to receive(:issues_finder)
+ .and_return(finder)
+
+ expect(controller.send(:issues_page_count, issues)).to eq(1)
+ end
+ end
+
+ describe '#merge_requests_page_count' do
+ it 'returns the number of merge request pages' do
+ project = create(:project, :public)
+
+ create(:merge_request, source_project: project, target_project: project)
+
+ finder = MergeRequestsFinder.new(user)
+ merge_requests = finder.execute
+
+ allow(controller).to receive(:merge_requests_finder)
+ .and_return(finder)
+
+ pages = controller.send(:merge_requests_page_count, merge_requests)
+
+ expect(pages).to eq(1)
+ end
+ end
+
+ describe '#page_count_for_relation' do
+ it 'returns the number of pages' do
+ relation = double(:relation, limit_value: 20)
+ pages = controller.send(:page_count_for_relation, relation, 28)
+
+ expect(pages).to eq(2)
+ end
+ end
+end
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 9d60dab12d1..b52b63e05a4 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -16,7 +16,11 @@ describe ProfilesController do
end
it "ignores an email update from a user with an external email address" do
- ldap_user = create(:omniauth_user, external_email: true)
+ stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+
+ ldap_user = create(:omniauth_user)
+ ldap_user.create_user_synced_attributes_metadata(provider: 'ldap', name_synced: true, email_synced: true)
sign_in(ldap_user)
put :update,
@@ -27,5 +31,24 @@ describe ProfilesController do
expect(response.status).to eq(302)
expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com')
end
+
+ it "ignores an email and name update but allows a location update from a user with external email and name, but not external location" do
+ stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+
+ ldap_user = create(:omniauth_user, name: 'Alex')
+ ldap_user.create_user_synced_attributes_metadata(provider: 'ldap', name_synced: true, email_synced: true, location_synced: false)
+ sign_in(ldap_user)
+
+ put :update,
+ user: { email: "john@gmail.com", name: "John", location: "City, Country" }
+
+ ldap_user.reload
+
+ expect(response.status).to eq(302)
+ expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com')
+ expect(ldap_user.name).not_to eq('John')
+ expect(ldap_user.location).to eq('City, Country')
+ end
end
end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index d2c613a2423..caa63e7bd22 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -81,14 +81,6 @@ describe Projects::ArtifactsController do
expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt'))
end
end
-
- context 'when the file does not exist' do
- it 'responds Not Found' do
- get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
-
- expect(response).to be_not_found
- end
- end
end
describe 'GET latest_succeeded' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 65f4d09cfce..5d9403c23ac 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -233,144 +233,119 @@ describe Projects::IssuesController do
end
end
- context 'when moving issue to another private project' do
- let(:another_project) { create(:project, :private) }
-
- context 'when user has access to move issue' do
- before do
- another_project.team << [user, :reporter]
- end
-
- it 'moves issue to another project' do
- move_issue
+ context 'Akismet is enabled' do
+ let(:project) { create(:project_empty_repo, :public) }
- expect(response).to have_http_status :found
- expect(another_project.issues).not_to be_empty
- end
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
- context 'when user does not have access to move issue' do
- it 'responds with 404' do
- move_issue
+ context 'when an issue is not identified as spam' do
+ before do
+ allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
+ allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
+ end
- expect(response).to have_http_status :not_found
+ it 'normally updates the issue' do
+ expect { update_issue(title: 'Foo') }.to change { issue.reload.title }.to('Foo')
end
end
- context 'Akismet is enabled' do
- let(:project) { create(:project_empty_repo, :public) }
-
+ context 'when an issue is identified as spam' do
before do
- stub_application_setting(recaptcha_enabled: true)
- allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
end
- context 'when an issue is not identified as spam' do
- before do
- allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
- allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
+ context 'when captcha is not verified' do
+ def update_spam_issue
+ update_issue(title: 'Spam Title', description: 'Spam lives here')
end
- it 'normally updates the issue' do
- expect { update_issue(title: 'Foo') }.to change { issue.reload.title }.to('Foo')
+ before do
+ allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
end
- end
- context 'when an issue is identified as spam' do
- before do
- allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
+ it 'rejects an issue recognized as a spam' do
+ expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true)
+ expect { update_spam_issue }.not_to change { issue.reload.title }
end
- context 'when captcha is not verified' do
- def update_spam_issue
- update_issue(title: 'Spam Title', description: 'Spam lives here')
- end
+ it 'rejects an issue recognized as a spam when recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
- before do
- allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
- end
+ expect { update_spam_issue }.not_to change { issue.reload.title }
+ end
- it 'rejects an issue recognized as a spam' do
- expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true)
- expect { update_spam_issue }.not_to change { issue.reload.title }
- end
+ it 'creates a spam log' do
+ update_spam_issue
- it 'rejects an issue recognized as a spam when recaptcha disabled' do
- stub_application_setting(recaptcha_enabled: false)
+ spam_logs = SpamLog.all
- expect { update_spam_issue }.not_to change { issue.reload.title }
- end
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs.first.title).to eq('Spam Title')
+ expect(spam_logs.first.recaptcha_verified).to be_falsey
+ end
- it 'creates a spam log' do
+ context 'as HTML' do
+ it 'renders verify template' do
update_spam_issue
- spam_logs = SpamLog.all
-
- expect(spam_logs.count).to eq(1)
- expect(spam_logs.first.title).to eq('Spam Title')
- expect(spam_logs.first.recaptcha_verified).to be_falsey
+ expect(response).to render_template(:verify)
end
+ end
- context 'as HTML' do
- it 'renders verify template' do
- update_spam_issue
-
- expect(response).to render_template(:verify)
- end
+ context 'as JSON' do
+ before do
+ update_issue({ title: 'Spam Title', description: 'Spam lives here' }, format: :json)
end
- context 'as JSON' do
- before do
- update_issue({ title: 'Spam Title', description: 'Spam lives here' }, format: :json)
- end
-
- it 'renders json errors' do
- expect(json_response)
- .to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."])
- end
+ it 'renders json errors' do
+ expect(json_response)
+ .to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."])
+ end
- it 'returns 422 status' do
- expect(response).to have_http_status(422)
- end
+ it 'returns 422 status' do
+ expect(response).to have_http_status(422)
end
end
+ end
- context 'when captcha is verified' do
- let(:spammy_title) { 'Whatever' }
- let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
+ context 'when captcha is verified' do
+ let(:spammy_title) { 'Whatever' }
+ let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
- def update_verified_issue
- update_issue({ title: spammy_title },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
- end
+ def update_verified_issue
+ update_issue({ title: spammy_title },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+ end
- before do
- allow_any_instance_of(described_class).to receive(:verify_recaptcha)
- .and_return(true)
- end
+ before do
+ allow_any_instance_of(described_class).to receive(:verify_recaptcha)
+ .and_return(true)
+ end
- it 'redirect to issue page' do
- update_verified_issue
+ it 'redirect to issue page' do
+ update_verified_issue
- expect(response)
- .to redirect_to(project_issue_path(project, issue))
- end
+ expect(response)
+ .to redirect_to(project_issue_path(project, issue))
+ end
- it 'accepts an issue after recaptcha is verified' do
- expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title)
- end
+ it 'accepts an issue after recaptcha is verified' do
+ expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title)
+ end
- it 'marks spam log as recaptcha_verified' do
- expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
- end
+ it 'marks spam log as recaptcha_verified' do
+ expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
+ end
- it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
- spam_log = create(:spam_log)
+ it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
+ spam_log = create(:spam_log)
- expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }
- .not_to change { SpamLog.last.recaptcha_verified }
- end
+ expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }
+ .not_to change { SpamLog.last.recaptcha_verified }
end
end
end
@@ -385,13 +360,45 @@ describe Projects::IssuesController do
put :update, params
end
+ end
+ end
+
+ describe 'POST #move' do
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ end
+
+ context 'when moving issue to another private project' do
+ let(:another_project) { create(:project, :private) }
+
+ context 'when user has access to move issue' do
+ before do
+ another_project.add_reporter(user)
+ end
+
+ it 'moves issue to another project' do
+ move_issue
+
+ expect(response).to have_http_status :ok
+ expect(another_project.issues).not_to be_empty
+ end
+ end
+
+ context 'when user does not have access to move issue' do
+ it 'responds with 404' do
+ move_issue
+
+ expect(response).to have_http_status :not_found
+ end
+ end
def move_issue
- put :update,
+ post :move,
+ format: :json,
namespace_id: project.namespace.to_param,
project_id: project,
id: issue.iid,
- issue: { title: 'New title' },
move_to_project_id: another_project.id
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index bb67db268fa..6775012bab5 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -56,6 +56,28 @@ describe Projects::MergeRequestsController do
expect(response).to be_success
end
+
+ context "loads notes" do
+ let(:first_contributor) { create(:user) }
+ let(:contributor) { create(:user) }
+ let(:merge_request) { create(:merge_request, author: first_contributor, target_project: project, source_project: project) }
+ let(:contributor_merge_request) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) }
+ # the order here is important
+ # as the controller reloads these from DB, references doesn't correspond after
+ let!(:first_contributor_note) { create(:note, author: first_contributor, noteable: merge_request, project: project) }
+ let!(:contributor_note) { create(:note, author: contributor, noteable: merge_request, project: project) }
+ let!(:owner_note) { create(:note, author: user, noteable: merge_request, project: project) }
+
+ it "with special_role FIRST_TIME_CONTRIBUTOR" do
+ go(format: :html)
+
+ notes = assigns(:notes)
+ expect(notes).to match(a_collection_containing_exactly(an_object_having_attributes(special_role: Note::SpecialRole::FIRST_TIME_CONTRIBUTOR),
+ an_object_having_attributes(special_role: nil),
+ an_object_having_attributes(special_role: nil)
+ ))
+ end
+ end
end
describe 'as json' do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5bba1dec7db..c2b59239af9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -12,6 +12,7 @@ FactoryGirl.define do
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
+ protected false
options do
{
@@ -106,7 +107,7 @@ FactoryGirl.define do
end
trait :triggered do
- trigger_request factory: :ci_trigger_request_with_variables
+ trigger_request factory: :ci_trigger_request
end
after(:build) do |build, evaluator|
@@ -226,5 +227,9 @@ FactoryGirl.define do
status 'created'
self.when 'manual'
end
+
+ trait :protected do
+ protected true
+ end
end
end
diff --git a/spec/factories/ci/pipeline_variable_variables.rb b/spec/factories/ci/pipeline_variables.rb
index 7c1a7faec08..7c1a7faec08 100644
--- a/spec/factories/ci/pipeline_variable_variables.rb
+++ b/spec/factories/ci/pipeline_variables.rb
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index e83a0e599a8..e5ea6b41ea3 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -4,6 +4,7 @@ FactoryGirl.define do
ref 'master'
sha '97de212e80737a608d939f648d959671fb0a0142'
status 'pending'
+ protected false
project
@@ -59,6 +60,10 @@ FactoryGirl.define do
trait :failed do
status :failed
end
+
+ trait :protected do
+ protected true
+ end
end
end
end
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 05abf60d5ce..88bb755d068 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -5,6 +5,7 @@ FactoryGirl.define do
platform "darwin"
is_shared false
active true
+ access_level :not_protected
trait :online do
contacted_at Time.now
@@ -21,5 +22,9 @@ FactoryGirl.define do
trait :inactive do
active false
end
+
+ trait :ref_protected do
+ access_level :ref_protected
+ end
end
end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 10e0ab4fd3c..40b8848920e 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,14 +1,5 @@
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
trigger factory: :ci_trigger
-
- factory :ci_trigger_request_with_variables do
- variables do
- {
- TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
- TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
- }
- end
- end
end
end
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
index a5aeffbe12d..c0beecf0bea 100644
--- a/spec/factories/gpg_signature.rb
+++ b/spec/factories/gpg_signature.rb
@@ -6,6 +6,6 @@ FactoryGirl.define do
project
gpg_key
gpg_key_primary_keyid { gpg_key.primary_keyid }
- valid_signature true
+ verification_status :verified
end
end
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 2e5a7c805c9..8d124dc2381 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -2,5 +2,6 @@ FactoryGirl.define do
factory :project_auto_devops do
project
enabled true
+ domain "example.com"
end
end
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index 3e3404dfdac..02f50d7e27f 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -8,8 +8,10 @@ describe 'Admin browses logs' do
it 'shows available log files' do
visit admin_logs_path
- expect(page).to have_content 'test.log'
- expect(page).to have_content 'githost.log'
- expect(page).to have_content 'application.log'
+ expect(page).to have_link 'application.log'
+ expect(page).to have_link 'githost.log'
+ expect(page).to have_link 'test.log'
+ expect(page).to have_link 'sidekiq.log'
+ expect(page).to have_link 'repocheck.log'
end
end
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index a6ad5981f8f..c480b5b7e34 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -8,8 +8,8 @@ describe 'Issue Boards add issue modal', :js do
let!(:label) { create(:label, project: project) }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: label, position: 1) }
- let!(:issue) { create(:issue, project: project) }
- let!(:issue2) { create(:issue, project: project) }
+ let!(:issue) { create(:issue, project: project, title: 'abc', description: 'def') }
+ let!(:issue2) { create(:issue, project: project, title: 'hij', description: 'klm') }
before do
project.team << [user, :master]
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 913258ca40f..e010b5f3444 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -73,15 +73,15 @@ describe 'Issue Boards', js: true do
let!(:list2) { create(:list, board: board, label: development, position: 1) }
let!(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
- let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], labels: [planning], relative_position: 8) }
- let!(:issue2) { create(:labeled_issue, project: project, author: user2, labels: [planning], relative_position: 7) }
- let!(:issue3) { create(:labeled_issue, project: project, labels: [planning], relative_position: 6) }
- let!(:issue4) { create(:labeled_issue, project: project, labels: [planning], relative_position: 5) }
- let!(:issue5) { create(:labeled_issue, project: project, labels: [planning], milestone: milestone, relative_position: 4) }
- let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development], relative_position: 3) }
- let!(:issue7) { create(:labeled_issue, project: project, labels: [development], relative_position: 2) }
- let!(:issue8) { create(:closed_issue, project: project) }
- let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let!(:issue1) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
+ let!(:issue2) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
+ let!(:issue3) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
+ let!(:issue4) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
+ let!(:issue5) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
+ let!(:issue6) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
+ let!(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
+ let!(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
+ let!(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
before do
visit project_board_path(project, board)
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 0c9fcc60d30..479fb713297 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -203,105 +203,4 @@ describe 'Commits' do
end
end
end
-
- describe 'GPG signed commits', :js do
- it 'changes from unverified to verified when the user changes his email to match the gpg key' do
- user = create :user, email: 'unrelated.user@example.org'
- project.team << [user, :master]
-
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- sign_in(user)
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
-
- # user changes his email which makes the gpg key verified
- Sidekiq::Testing.inline! do
- user.skip_reconfirmation!
- user.update_attributes!(email: GpgHelpers::User1.emails.first)
- end
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
- end
-
- it 'changes from unverified to verified when the user adds the missing gpg key' do
- user = create :user, email: GpgHelpers::User1.emails.first
- project.team << [user, :master]
-
- sign_in(user)
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
-
- # user adds the gpg key which makes the signature valid
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- visit project_commits_path(project, :'signed-commits')
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
- end
-
- it 'shows popover badges' do
- gpg_user = create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
- Sidekiq::Testing.inline! do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: gpg_user
- end
-
- user = create :user
- project.team << [user, :master]
-
- sign_in(user)
- visit project_commits_path(project, :'signed-commits')
-
- # unverified signature
- click_on 'Unverified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with an unverified signature.'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
- end
-
- # verified and the gpg user has a gitlab profile
- click_on 'Verified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with a verified signature.'
- expect(page).to have_content 'Nannie Bernhard'
- expect(page).to have_content '@nannie.bernhard'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
- end
-
- # verified and the gpg user's profile doesn't exist anymore
- gpg_user.destroy!
-
- visit project_commits_path(project, :'signed-commits')
-
- click_on 'Verified', match: :first
- within '.popover' do
- expect(page).to have_content 'This commit was signed with a verified signature.'
- expect(page).to have_content 'Nannie Bernhard'
- expect(page).to have_content 'nannie.bernhard@example.com'
- expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
- end
- end
- end
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 4297bfff3d9..2db6f9a2982 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -166,12 +166,10 @@ describe 'New/edit issue', :js do
end
end
- page.within '.issuable-meta' do
+ page.within '.breadcrumbs' do
issue = Issue.find_by(title: 'title')
- expect(page).to have_text("Issue #{issue.to_reference}")
- # compare paths because the host differ in test
- expect(find_link(issue.to_reference)[:href]).to end_with(issue_path(issue))
+ expect(page).to have_text("Issues #{issue.to_reference}")
end
end
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index c470cb7c716..28b636f9359 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -40,18 +40,4 @@ feature 'Issue Detail', :js do
end
end
end
-
- context 'when authored by a user who is later deleted' do
- before do
- issue.update_attribute(:author_id, nil)
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'shows the issue' do
- page.within('.issuable-details') do
- expect(find('h2')).to have_content(issue.title)
- end
- end
- end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 494c309c9ea..b2724945da4 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -15,11 +15,11 @@ feature 'issue move to another project' do
background do
old_project.team << [user, :guest]
- edit_issue(issue)
+ visit issue_path(issue)
end
scenario 'moving issue to another project not allowed' do
- expect(page).to have_no_selector('#move_to_project_id')
+ expect(page).to have_no_selector('.js-sidebar-move-issue-block')
end
end
@@ -34,12 +34,14 @@ feature 'issue move to another project' do
old_project.team << [user, :reporter]
new_project.team << [user, :reporter]
- edit_issue(issue)
+ visit issue_path(issue)
end
scenario 'moving issue to another project', js: true do
- find('#issuable-move', visible: false).set(new_project.id)
- click_button('Save changes')
+ find('.js-move-issue').trigger('click')
+ wait_for_requests
+ all('.js-move-issue-dropdown-item')[0].click
+ find('.js-move-issue-confirmation-button').click
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
@@ -50,13 +52,12 @@ feature 'issue move to another project' do
scenario 'searching project dropdown', js: true do
new_project_search.team << [user, :reporter]
- page.within '.detail-page-description' do
- first('.select2-choice').click
- end
+ find('.js-move-issue').trigger('click')
+ wait_for_requests
- fill_in('s2id_autogen1_search', with: new_project_search.name)
+ page.within '.js-sidebar-move-issue-block' do
+ fill_in('sidebar-move-issue-dropdown-search', with: new_project_search.name)
- page.within '.select2-drop' do
expect(page).to have_content(new_project_search.name)
expect(page).not_to have_content(new_project.name)
end
@@ -68,10 +69,10 @@ feature 'issue move to another project' do
background { another_project.team << [user, :guest] }
scenario 'browsing projects in projects select' do
- click_link 'Move to a different project'
+ find('.js-move-issue').trigger('click')
+ wait_for_requests
- page.within '.select2-results' do
- expect(page).to have_content 'No project'
+ page.within '.js-sidebar-move-issue-block' do
expect(page).to have_content new_project.name_with_namespace
end
end
@@ -89,11 +90,6 @@ feature 'issue move to another project' do
end
end
- def edit_issue(issue)
- visit issue_path(issue)
- page.within('.issuable-actions') { first(:link, 'Edit').click }
- end
-
def issue_path(issue)
project_issue_path(issue.project, issue)
end
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index e77f1f92731..ca536f2800c 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -139,7 +139,7 @@ feature 'Diff note avatars', js: true do
end
page.within find("[id='#{position.line_code(project.repository)}']") do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').trigger('click')
expect(page).to have_selector('img.js-diff-comment-avatar', count: 2)
end
@@ -152,7 +152,7 @@ feature 'Diff note avatars', js: true do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
- find('.js-comment-button').trigger 'click'
+ find('.js-comment-button').trigger('click')
wait_for_requests
end
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 89410b0e90f..de98b147d04 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -84,13 +84,10 @@ describe 'New/edit merge request', :js do
end
end
- page.within '.issuable-meta' do
+ page.within '.breadcrumbs' do
merge_request = MergeRequest.find_by(source_branch: 'fix')
- expect(page).to have_text("Merge request #{merge_request.to_reference}")
- # compare paths because the host differ in test
- expect(find_link(merge_request.to_reference)[:href])
- .to end_with(merge_request_path(merge_request))
+ expect(page).to have_text("Merge Requests #{merge_request.to_reference}")
end
end
diff --git a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
new file mode 100644
index 00000000000..55a82bdf2b9
--- /dev/null
+++ b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+feature 'Resolve outdated diff discussions', js: true do
+ let(:project) { create(:project, :repository, :public) }
+
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: 'csv', target_branch: 'master')
+ end
+
+ let(:outdated_diff_refs) { project.commit('926c6595b263b2a40da6b17f3e3b7ea08344fad6').diff_refs }
+ let(:current_diff_refs) { merge_request.diff_refs }
+
+ let(:outdated_position) do
+ Gitlab::Diff::Position.new(
+ old_path: 'files/csv/Book1.csv',
+ new_path: 'files/csv/Book1.csv',
+ old_line: nil,
+ new_line: 9,
+ diff_refs: outdated_diff_refs
+ )
+ end
+
+ let(:current_position) do
+ Gitlab::Diff::Position.new(
+ old_path: 'files/csv/Book1.csv',
+ new_path: 'files/csv/Book1.csv',
+ old_line: nil,
+ new_line: 1,
+ diff_refs: current_diff_refs
+ )
+ end
+
+ let!(:outdated_discussion) do
+ create(:diff_note_on_merge_request,
+ project: project,
+ noteable: merge_request,
+ position: outdated_position).to_discussion
+ end
+
+ let!(:current_discussion) do
+ create(:diff_note_on_merge_request,
+ noteable: merge_request,
+ project: project,
+ position: current_position).to_discussion
+ end
+
+ before do
+ sign_in(merge_request.author)
+ end
+
+ context 'when a discussion was resolved by a push' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+
+ merge_request.update_diff_discussion_positions(
+ old_diff_refs: outdated_diff_refs,
+ new_diff_refs: current_diff_refs,
+ current_user: merge_request.author
+ )
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows that as automatically resolved' do
+ within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
+ expect(page).to have_css('.discussion-body', visible: false)
+ expect(page).to have_content('Automatically resolved')
+ end
+ end
+
+ it 'does not show that for active discussions' do
+ within(".discussion[data-discussion-id='#{current_discussion.id}']") do
+ expect(page).to have_css('.discussion-body', visible: true)
+ expect(page).not_to have_content('Automatically resolved')
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 877f305120e..442ce14eb7e 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -97,6 +97,16 @@ feature 'Merge requests > User posts diff notes', :js do
visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
+ context 'after deleteing a note' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+
+ first('.js-note-delete', visible: false).trigger('click')
+
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
context 'with a new line' do
it 'allows commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index 6edc482b47e..623e4f341c5 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -42,7 +42,7 @@ feature 'Profile > GPG Keys' do
scenario 'User revokes a key via the key index' do
gpg_key = create :gpg_key, user: user, key: GpgHelpers::User2.public_key
- gpg_signature = create :gpg_signature, gpg_key: gpg_key, valid_signature: true
+ gpg_signature = create :gpg_signature, gpg_key: gpg_key, verification_status: :verified
visit profile_gpg_keys_path
@@ -51,7 +51,7 @@ feature 'Profile > GPG Keys' do
expect(page).to have_content('Your GPG keys (0)')
expect(gpg_signature.reload).to have_attributes(
- valid_signature: false,
+ verification_status: 'unknown_key',
gpg_key: nil
)
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 2eb6fab129d..ad2db1a34f4 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -18,23 +18,25 @@ feature 'Import/Export - project import integration test', js: true do
context 'when selecting the namespace' do
let(:user) { create(:admin) }
- let!(:namespace) { create(:namespace, name: "asd", owner: user) }
+ let!(:namespace) { create(:namespace, name: 'asd', owner: user) }
+ let(:project_path) { 'test-project-path' + SecureRandom.hex }
context 'prefilled the path' do
scenario 'user imports an exported project successfully' do
visit new_project_path
select2(namespace.id, from: '#project_namespace_id')
- fill_in :project_path, with: 'test-project-path', visible: true
+ fill_in :project_path, with: project_path, visible: true
click_link 'GitLab export'
expect(page).to have_content('Import an exported GitLab project')
- expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path")
- expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original
+ expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
+ expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
attach_file('file', file)
+ click_on 'Import project'
- expect { click_on 'Import project' }.to change { Project.count }.by(1)
+ expect(Project.count).to eq(1)
project = Project.last
expect(project).not_to be_nil
@@ -64,7 +66,7 @@ feature 'Import/Export - project import integration test', js: true do
end
scenario 'invalid project' do
- namespace = create(:namespace, name: "asd", owner: user)
+ namespace = create(:namespace, name: 'asdf', owner: user)
project = create(:project, namespace: namespace)
visit new_project_path
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 037ac00d39f..3b5c6966287 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -292,26 +292,44 @@ feature 'Jobs' do
end
feature 'Variables' do
- let(:trigger_request) { create(:ci_trigger_request_with_variables) }
+ let(:trigger_request) { create(:ci_trigger_request) }
let(:job) do
create :ci_build, pipeline: pipeline, trigger_request: trigger_request
end
- before do
- visit project_job_path(project, job)
+ shared_examples 'expected variables behavior' do
+ it 'shows variable key and value after click', js: true do
+ expect(page).to have_css('.reveal-variables')
+ expect(page).not_to have_css('.js-build-variable')
+ expect(page).not_to have_css('.js-build-value')
+
+ click_button 'Reveal Variables'
+
+ expect(page).not_to have_css('.reveal-variables')
+ expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+ expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ end
end
- it 'shows variable key and value after click', js: true do
- expect(page).to have_css('.reveal-variables')
- expect(page).not_to have_css('.js-build-variable')
- expect(page).not_to have_css('.js-build-value')
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
- click_button 'Reveal Variables'
+ visit project_job_path(project, job)
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+
+ visit project_job_path(project, job)
+ end
- expect(page).not_to have_css('.reveal-variables')
- expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
- expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ it_behaves_like 'expected variables behavior'
end
end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index b2b39dbd24c..eb2d3ff50a0 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -26,7 +26,6 @@ describe 'Subgroup Issuables', :js, :nested_groups do
def expect_to_have_full_subgroup_title
title = find('.breadcrumbs-links')
- expect(title).not_to have_selector '.initializing'
- expect(title).to have_content 'group / subgroup / project'
+ expect(title).to have_content 'group subgroup project'
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index baf3d29e6c5..81f7ab80a04 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -95,49 +95,6 @@ feature 'Project' do
end
end
- describe 'project title' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
-
- before do
- sign_in(user)
- project.add_user(user, Gitlab::Access::MASTER)
- visit project_path(project)
- end
-
- it 'clicks toggle and shows dropdown', js: true do
- find('.js-projects-dropdown-toggle').click
- expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 1)
- end
- end
-
- describe 'project title' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
- let(:project2) { create(:project, namespace: user.namespace, path: 'test') }
- let(:issue) { create(:issue, project: project) }
-
- context 'on issues page', js: true do
- before do
- sign_in(user)
- project.add_user(user, Gitlab::Access::MASTER)
- project2.add_user(user, Gitlab::Access::MASTER)
- visit project_issue_path(project, issue)
- end
-
- it 'clicks toggle and shows dropdown' do
- find('.js-projects-dropdown-toggle').click
- expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 2)
-
- page.within '.dropdown-menu-projects' do
- click_link project.name_with_namespace
- end
-
- expect(page).to have_content project.name
- end
- end
- end
-
describe 'tree view (default view is set to Files)' do
let(:user) { create(:user, project_view: 'files') }
let(:project) { create(:forked_project_with_submodules) }
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 785cfeb34bd..c7f0e342809 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -43,6 +43,21 @@ feature 'Runners' do
expect(page).not_to have_content(specific_runner.display_name)
end
+ scenario 'user edits the runner to be protected' do
+ visit runners_path(project)
+
+ within '.activated-specific-runners' do
+ first('.edit-runner > a').click
+ end
+
+ expect(page.find_field('runner[access_level]')).not_to be_checked
+
+ check 'runner_access_level'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Protected Yes'
+ end
+
context 'when a runner has a tag' do
background do
specific_runner.update(tag_list: ['tag'])
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
new file mode 100644
index 00000000000..8efa5b58141
--- /dev/null
+++ b/spec/features/signed_commits_spec.rb
@@ -0,0 +1,179 @@
+require 'spec_helper'
+
+describe 'GPG signed commits', :js do
+ let(:project) { create(:project, :repository) }
+
+ it 'changes from unverified to verified when the user changes his email to match the gpg key' do
+ user = create :user, email: 'unrelated.user@example.org'
+ project.team << [user, :master]
+
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user changes his email which makes the gpg key verified
+ Sidekiq::Testing.inline! do
+ user.skip_reconfirmation!
+ user.update_attributes!(email: GpgHelpers::User1.emails.first)
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ end
+
+ it 'changes from unverified to verified when the user adds the missing gpg key' do
+ user = create :user, email: GpgHelpers::User1.emails.first
+ project.team << [user, :master]
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user adds the gpg key which makes the signature valid
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ end
+
+ context 'shows popover badges' do
+ let(:user_1) do
+ create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
+ end
+
+ let(:user_1_key) do
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user_1
+ end
+ end
+
+ let(:user_2) do
+ create(:user, email: GpgHelpers::User2.emails.first, username: 'bette.cartwright', name: 'Bette Cartwright').tap do |user|
+ # secondary, unverified email
+ create :email, user: user, email: GpgHelpers::User2.emails.last
+ end
+ end
+
+ let(:user_2_key) do
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User2.public_key, user: user_2
+ end
+ end
+
+ before do
+ user = create :user
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ it 'unverified signature' do
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed commit by bette cartwright')) do
+ click_on 'Unverified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with an unverified signature.'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ it 'unverified signature: user email does not match the committer email, but is the same user' do
+ user_2_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do
+ click_on 'Unverified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.'
+ expect(page).to have_content 'Bette Cartwright'
+ expect(page).to have_content '@bette.cartwright'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ it 'unverified signature: user email does not match the committer email' do
+ user_2_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed commit by bette cartwright')) do
+ click_on 'Unverified'
+ within '.popover' do
+ expect(page).to have_content "This commit was signed with a different user's verified signature."
+ expect(page).to have_content 'Bette Cartwright'
+ expect(page).to have_content '@bette.cartwright'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+ end
+ end
+
+ it 'verified and the gpg user has a gitlab profile' do
+ user_1_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
+ click_on 'Verified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content '@nannie.bernhard'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+ end
+ end
+
+ it "verified and the gpg user's profile doesn't exist anymore" do
+ user_1_key
+
+ visit project_commits_path(project, :'signed-commits')
+
+ # wait for the signature to get generated
+ within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
+ expect(page).to have_content 'Verified'
+ end
+
+ user_1.destroy!
+
+ refresh
+
+ within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
+ click_on 'Verified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content 'nannie.bernhard@example.com'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index ff6f71d7528..aeb0534b733 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -200,9 +200,9 @@ feature 'Task Lists' do
visit_issue(project, issue)
expect(page).to have_selector('.js-task-list-container')
- logout(:user)
+ gitlab_sign_out
- login_as(user2)
+ gitlab_sign_in(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 0e80df94e18..47b173dea0a 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -15,8 +15,8 @@ describe IssuesFinder do
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
describe '#execute' do
- set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
- set(:label_link) { create(:label_link, label: label, target: issue2) }
+ let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
+ let!(:label_link) { create(:label_link, label: label, target: issue2) }
let(:search_user) { user }
let(:params) { {} }
let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
@@ -347,6 +347,20 @@ describe IssuesFinder do
end
end
+ describe '#row_count', :request_store do
+ it 'returns the number of rows for the default state' do
+ finder = described_class.new(user)
+
+ expect(finder.row_count).to eq(3)
+ end
+
+ it 'returns the number of rows for a given state' do
+ finder = described_class.new(user, state: 'closed')
+
+ expect(finder.row_count).to be_zero
+ end
+ end
+
describe '#with_confidentiality_access_check' do
let(:guest) { create(:user) }
set(:authorized_user) { create(:user) }
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index b54155a6704..95f445e7905 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -108,4 +108,18 @@ describe MergeRequestsFinder do
end
end
end
+
+ describe '#row_count', :request_store do
+ it 'returns the number of rows for the default state' do
+ finder = described_class.new(user)
+
+ expect(finder.row_count).to eq(3)
+ end
+
+ it 'returns the number of rows for a given state' do
+ finder = described_class.new(user, state: 'closed')
+
+ expect(finder.row_count).to eq(1)
+ end
+ end
end
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
index f6346bd0fb6..c76c6945117 100644
--- a/spec/fixtures/api/schemas/pipeline_schedule.json
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -31,6 +31,10 @@
"web_url": { "type": "uri" }
},
"additionalProperties": false
+ },
+ "variables": {
+ "type": ["array", "null"],
+ "items": { "$ref": "pipeline_schedule_variable.json" }
}
},
"required": [
diff --git a/spec/fixtures/api/schemas/pipeline_schedule_variable.json b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
new file mode 100644
index 00000000000..f7ccb2d44a0
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
@@ -0,0 +1,8 @@
+{
+ "type": ["object", "null"],
+ "properties": {
+ "key": { "type": "string" },
+ "value": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/helpers/blame_helper_spec.rb b/spec/helpers/blame_helper_spec.rb
index b4368516d83..722d21c566f 100644
--- a/spec/helpers/blame_helper_spec.rb
+++ b/spec/helpers/blame_helper_spec.rb
@@ -35,25 +35,32 @@ describe BlameHelper do
end
describe '#age_map_class' do
- let(:dates) do
- [Time.zone.local(2014, 3, 17, 0, 0, 0)]
- end
- let(:blame_groups) do
- [
- { commit: double(committed_date: dates[0]) }
- ]
- end
+ let(:date) { Time.zone.local(2014, 3, 17, 0, 0, 0) }
+ let(:blame_groups) { [{ commit: double(committed_date: date) }] }
let(:duration) do
- project = double(created_at: dates[0])
+ project = double(created_at: date)
helper.age_map_duration(blame_groups, project)
end
it 'returns blame-commit-age-9 when oldest' do
- expect(helper.age_map_class(dates[0], duration)).to eq 'blame-commit-age-9'
+ expect(helper.age_map_class(date, duration)).to eq 'blame-commit-age-9'
end
it 'returns blame-commit-age-0 class when newest' do
expect(helper.age_map_class(duration[:now], duration)).to eq 'blame-commit-age-0'
end
+
+ context 'when called on the same day as project creation' do
+ let(:same_day_duration) do
+ project = double(created_at: now)
+ helper.age_map_duration(today_blame_groups, project)
+ end
+ let(:today_blame_groups) { [{ commit: double(committed_date: now) }] }
+ let(:now) { Time.zone.now }
+
+ it 'returns blame-commit-age-0 class' do
+ expect(helper.age_map_class(duration[:now], same_day_duration)).to eq 'blame-commit-age-0'
+ end
+ end
end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 9d6e03e3868..05f969904f5 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -91,7 +91,8 @@ describe GroupsHelper do
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'outputs the groups in the correct order' do
- expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
+ expect(helper.group_title(very_deep_nested_group))
+ .to match(/<li style="text-indent: 16px;"><a.*>#{deep_nested_group.name}.*<\/li>.*<a.*>#{very_deep_nested_group.name}<\/a>/m)
end
end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index dc3100311f8..ddf881a7b6f 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -58,16 +58,6 @@ describe IssuesHelper do
end
end
- describe "merge_requests_sentence" do
- subject { merge_requests_sentence(merge_requests)}
- let(:merge_requests) do
- [build(:merge_request, iid: 1), build(:merge_request, iid: 2),
- build(:merge_request, iid: 3)]
- end
-
- it { is_expected.to eq("!1, !2, or !3") }
- end
-
describe '#award_user_list' do
it "returns a comma-separated list of the first X users" do
user = build_stubbed(:user, name: 'Joe')
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 70eb01c9c44..03d706062b7 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -52,12 +52,71 @@ describe MarkupHelper do
end
end
- describe '#link_to_gfm' do
+ describe '#markdown_field' do
+ let(:attribute) { :title }
+
+ describe 'with already redacted attribute' do
+ it 'returns the redacted attribute' do
+ commit.redacted_title_html = 'commit title'
+
+ expect(Banzai).not_to receive(:render_field)
+
+ expect(helper.markdown_field(commit, attribute)).to eq('commit title')
+ end
+ end
+
+ describe 'without redacted attribute' do
+ it 'renders the markdown value' do
+ expect(Banzai).to receive(:render_field).with(commit, attribute).and_call_original
+
+ helper.markdown_field(commit, attribute)
+ end
+ end
+ end
+
+ describe '#link_to_markdown_field' do
+ let(:link) { '/commits/0a1b2c3d' }
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ it 'handles references nested in links with all the text' do
+ allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real")
+
+ actual = helper.link_to_markdown_field(commit, :title, link)
+ doc = Nokogiri::HTML.parse(actual)
+
+ # Make sure we didn't create invalid markup
+ expect(doc.errors).to be_empty
+
+ # Leading commit link
+ expect(doc.css('a')[0].attr('href')).to eq link
+ expect(doc.css('a')[0].text).to eq 'This should finally fix '
+
+ # First issue link
+ expect(doc.css('a')[1].attr('href'))
+ .to eq project_issue_path(project, issues[0])
+ expect(doc.css('a')[1].text).to eq issues[0].to_reference
+
+ # Internal commit link
+ expect(doc.css('a')[2].attr('href')).to eq link
+ expect(doc.css('a')[2].text).to eq ' and '
+
+ # Second issue link
+ expect(doc.css('a')[3].attr('href'))
+ .to eq project_issue_path(project, issues[1])
+ expect(doc.css('a')[3].text).to eq issues[1].to_reference
+
+ # Trailing commit link
+ expect(doc.css('a')[4].attr('href')).to eq link
+ expect(doc.css('a')[4].text).to eq ' for real'
+ end
+ end
+
+ describe '#link_to_markdown' do
let(:link) { '/commits/0a1b2c3d' }
let(:issues) { create_list(:issue, 2, project: project) }
it 'handles references nested in links with all the text' do
- actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link)
+ actual = helper.link_to_markdown("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
@@ -87,7 +146,7 @@ describe MarkupHelper do
end
it 'forwards HTML options' do
- actual = helper.link_to_gfm("Fixed in #{commit.id}", link, class: 'foo')
+ actual = helper.link_to_markdown("Fixed in #{commit.id}", link, class: 'foo')
doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v|
@@ -98,23 +157,43 @@ describe MarkupHelper do
it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
- expect(helper.link_to_gfm(actual, link))
+ expect(helper.link_to_markdown(actual, link))
.to match('&lt;h1&gt;test&lt;/h1&gt;')
end
it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference
- act = helper.link_to_gfm(text, '/foo')
+ act = helper.link_to_markdown(text, '/foo')
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end
it 'replaces commit message with emoji to link' do
- actual = link_to_gfm(':book: Book', '/foo')
+ actual = link_to_markdown(':book: Book', '/foo')
expect(actual)
.to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
end
end
+ describe '#link_to_html' do
+ it 'wraps the rendered content in a link' do
+ link = '/commits/0a1b2c3d'
+ issue = create(:issue, project: project)
+
+ rendered = helper.markdown("This should finally fix #{issue.to_reference} for real", pipeline: :single_line)
+ doc = Nokogiri::HTML.parse(rendered)
+
+ expect(doc.css('a')[0].attr('href'))
+ .to eq project_issue_path(project, issue)
+ expect(doc.css('a')[0].text).to eq issue.to_reference
+
+ wrapped = helper.link_to_html(rendered, link)
+ doc = Nokogiri::HTML.parse(wrapped)
+
+ expect(doc.css('a')[0].attr('href')).to eq link
+ expect(doc.css('a')[0].text).to eq 'This should finally fix '
+ end
+ end
+
describe '#render_wiki_content' do
before do
@wiki = double('WikiPage')
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 9921ca1af33..cd15e27b497 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -23,10 +23,10 @@ describe NotesHelper do
end
describe "#notes_max_access_for_users" do
- it 'returns human access levels' do
- expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
- expect(helper.note_max_access_for_user(master_note)).to eq('Master')
- expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
+ it 'returns access levels' do
+ expect(helper.note_max_access_for_user(owner_note)).to eq(Gitlab::Access::OWNER)
+ expect(helper.note_max_access_for_user(master_note)).to eq(Gitlab::Access::MASTER)
+ expect(helper.note_max_access_for_user(reporter_note)).to eq(Gitlab::Access::REPORTER)
end
it 'handles access in different projects' do
@@ -34,8 +34,8 @@ describe NotesHelper do
second_project.team << [master, :reporter]
other_note = create(:note, author: master, project: second_project)
- expect(helper.note_max_access_for_user(master_note)).to eq('Master')
- expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
+ expect(helper.note_max_access_for_user(master_note)).to eq(Gitlab::Access::MASTER)
+ expect(helper.note_max_access_for_user(other_note)).to eq(Gitlab::Access::REPORTER)
end
end
@@ -231,7 +231,7 @@ describe NotesHelper do
end
end
- describe '#form_resurces' do
+ describe '#form_resources' do
it 'returns note for personal snippet' do
@snippet = create(:personal_snippet)
@note = create(:note_on_personal_snippet)
@@ -266,4 +266,22 @@ describe NotesHelper do
expect(noteable_note_url(note)).to match("/#{project.namespace.path}/#{project.path}/issues/#{issue.iid}##{dom_id(note)}")
end
end
+
+ describe '#discussion_resolved_intro' do
+ context 'when the discussion was resolved by a push' do
+ let(:discussion) { double(:discussion, resolved_by_push?: true) }
+
+ it 'returns "Automatically resolved"' do
+ expect(discussion_resolved_intro(discussion)).to eq('Automatically resolved')
+ end
+ end
+
+ context 'when the discussion was not resolved by a push' do
+ let(:discussion) { double(:discussion, resolved_by_push?: false) }
+
+ it 'returns "Resolved"' do
+ expect(discussion_resolved_intro(discussion)).to eq('Resolved')
+ end
+ end
+ end
end
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
index b33b3f3a228..c1d0614c79e 100644
--- a/spec/helpers/profiles_helper_spec.rb
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -6,22 +6,41 @@ describe ProfilesHelper do
user = create(:user)
allow(helper).to receive(:current_user).and_return(user)
- expect(helper.email_provider_label).to be_nil
+ expect(helper.attribute_provider_label(:email)).to be_nil
end
- it "returns omniauth provider label for users with external email" do
+ it "returns omniauth provider label for users with external attributes" do
+ stub_omniauth_setting(sync_profile_from_provider: ['cas3'])
+ stub_omniauth_setting(sync_profile_attributes: true)
stub_cas_omniauth_provider
- cas_user = create(:omniauth_user, provider: 'cas3', external_email: true, email_provider: 'cas3')
+ cas_user = create(:omniauth_user, provider: 'cas3')
+ cas_user.create_user_synced_attributes_metadata(provider: 'cas3', name_synced: true, email_synced: true, location_synced: true)
allow(helper).to receive(:current_user).and_return(cas_user)
- expect(helper.email_provider_label).to eq('CAS')
+ expect(helper.attribute_provider_label(:email)).to eq('CAS')
+ expect(helper.attribute_provider_label(:name)).to eq('CAS')
+ expect(helper.attribute_provider_label(:location)).to eq('CAS')
+ end
+
+ it "returns the correct omniauth provider label for users with some external attributes" do
+ stub_omniauth_setting(sync_profile_from_provider: ['cas3'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+ stub_cas_omniauth_provider
+ cas_user = create(:omniauth_user, provider: 'cas3')
+ cas_user.create_user_synced_attributes_metadata(provider: 'cas3', name_synced: false, email_synced: true, location_synced: false)
+ allow(helper).to receive(:current_user).and_return(cas_user)
+
+ expect(helper.attribute_provider_label(:name)).to be_nil
+ expect(helper.attribute_provider_label(:email)).to eq('CAS')
+ expect(helper.attribute_provider_label(:location)).to be_nil
end
it "returns 'LDAP' for users with external email but no email provider" do
- ldap_user = create(:omniauth_user, external_email: true)
+ ldap_user = create(:omniauth_user)
+ ldap_user.create_user_synced_attributes_metadata(email_synced: true)
allow(helper).to receive(:current_user).and_return(ldap_user)
- expect(helper.email_provider_label).to eq('LDAP')
+ expect(helper.attribute_provider_label(:email)).to eq('LDAP')
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 463af15930d..ab647401e14 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -17,7 +17,7 @@ describe SearchHelper do
end
end
- context "with a user" do
+ context "with a standard user" do
let(:user) { create(:user) }
before do
@@ -29,7 +29,11 @@ describe SearchHelper do
end
it "includes default sections" do
- expect(search_autocomplete_opts("adm").size).to eq(1)
+ expect(search_autocomplete_opts("dash").size).to eq(1)
+ end
+
+ it "does not include admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(0)
end
it "does not allow regular expression in search term" do
@@ -67,6 +71,18 @@ describe SearchHelper do
end
end
end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(admin)
+ end
+
+ it "includes admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(1)
+ end
+ end
end
describe 'search_filter_input_options' do
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 8c68ceff914..2aa4fb1f6c6 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -101,12 +101,13 @@ describe('Api', () => {
it('fetches projects with membership when logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
window.gon.current_user_id = 1;
const expectedData = Object.assign({
search: query,
per_page: 20,
membership: true,
+ simple: true,
}, options);
spyOn(jQuery, 'ajax').and.callFake((request) => {
expect(request.url).toEqual(expectedUrl);
@@ -124,10 +125,11 @@ describe('Api', () => {
it('fetches projects without membership when not logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
const expectedData = Object.assign({
search: query,
per_page: 20,
+ simple: true,
}, options);
spyOn(jQuery, 'ajax').and.callFake((request) => {
expect(request.url).toEqual(expectedUrl);
diff --git a/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
new file mode 100644
index 00000000000..114d282e48a
--- /dev/null
+++ b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
@@ -0,0 +1,219 @@
+import Cookies from 'js-cookie';
+import {
+ getCookieName,
+ getSelector,
+ showPopover,
+ hidePopover,
+ dismiss,
+ mouseleave,
+ mouseenter,
+ setupDismissButton,
+} from '~/feature_highlight/feature_highlight_helper';
+
+describe('feature highlight helper', () => {
+ describe('getCookieName', () => {
+ it('returns `feature-highlighted-` prefix', () => {
+ const cookieId = 'cookieId';
+ expect(getCookieName(cookieId)).toEqual(`feature-highlighted-${cookieId}`);
+ });
+ });
+
+ describe('getSelector', () => {
+ it('returns js-feature-highlight selector', () => {
+ const highlightId = 'highlightId';
+ expect(getSelector(highlightId)).toEqual(`.js-feature-highlight[data-highlight=${highlightId}]`);
+ });
+ });
+
+ describe('showPopover', () => {
+ it('returns true when popover is shown', () => {
+ const context = {
+ hasClass: () => false,
+ popover: () => {},
+ addClass: () => {},
+ };
+
+ expect(showPopover.call(context)).toEqual(true);
+ });
+
+ it('returns false when popover is already shown', () => {
+ const context = {
+ hasClass: () => true,
+ };
+
+ expect(showPopover.call(context)).toEqual(false);
+ });
+
+ it('shows popover', (done) => {
+ const context = {
+ hasClass: () => false,
+ popover: () => {},
+ addClass: () => {},
+ };
+
+ spyOn(context, 'popover').and.callFake((method) => {
+ expect(method).toEqual('show');
+ done();
+ });
+
+ showPopover.call(context);
+ });
+
+ it('adds disable-animation and js-popover-show class', (done) => {
+ const context = {
+ hasClass: () => false,
+ popover: () => {},
+ addClass: () => {},
+ };
+
+ spyOn(context, 'addClass').and.callFake((classNames) => {
+ expect(classNames).toEqual('disable-animation js-popover-show');
+ done();
+ });
+
+ showPopover.call(context);
+ });
+ });
+
+ describe('hidePopover', () => {
+ it('returns true when popover is hidden', () => {
+ const context = {
+ hasClass: () => true,
+ popover: () => {},
+ removeClass: () => {},
+ };
+
+ expect(hidePopover.call(context)).toEqual(true);
+ });
+
+ it('returns false when popover is already hidden', () => {
+ const context = {
+ hasClass: () => false,
+ };
+
+ expect(hidePopover.call(context)).toEqual(false);
+ });
+
+ it('hides popover', (done) => {
+ const context = {
+ hasClass: () => true,
+ popover: () => {},
+ removeClass: () => {},
+ };
+
+ spyOn(context, 'popover').and.callFake((method) => {
+ expect(method).toEqual('hide');
+ done();
+ });
+
+ hidePopover.call(context);
+ });
+
+ it('removes disable-animation and js-popover-show class', (done) => {
+ const context = {
+ hasClass: () => true,
+ popover: () => {},
+ removeClass: () => {},
+ };
+
+ spyOn(context, 'removeClass').and.callFake((classNames) => {
+ expect(classNames).toEqual('disable-animation js-popover-show');
+ done();
+ });
+
+ hidePopover.call(context);
+ });
+ });
+
+ describe('dismiss', () => {
+ const context = {
+ hide: () => {},
+ };
+
+ beforeEach(() => {
+ spyOn(Cookies, 'set').and.callFake(() => {});
+ spyOn(hidePopover, 'call').and.callFake(() => {});
+ spyOn(context, 'hide').and.callFake(() => {});
+ dismiss.call(context);
+ });
+
+ it('sets cookie to true', () => {
+ expect(Cookies.set).toHaveBeenCalled();
+ });
+
+ it('calls hide popover', () => {
+ expect(hidePopover.call).toHaveBeenCalled();
+ });
+
+ it('calls hide', () => {
+ expect(context.hide).toHaveBeenCalled();
+ });
+ });
+
+ describe('mouseleave', () => {
+ it('calls hide popover if .popover:hover is false', () => {
+ const fakeJquery = {
+ length: 0,
+ };
+
+ spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
+ spyOn(hidePopover, 'call');
+ mouseleave();
+ expect(hidePopover.call).toHaveBeenCalled();
+ });
+
+ it('does not call hide popover if .popover:hover is true', () => {
+ const fakeJquery = {
+ length: 1,
+ };
+
+ spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
+ spyOn(hidePopover, 'call');
+ mouseleave();
+ expect(hidePopover.call).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('mouseenter', () => {
+ const context = {};
+
+ it('shows popover', () => {
+ spyOn(showPopover, 'call').and.returnValue(false);
+ mouseenter.call(context);
+ expect(showPopover.call).toHaveBeenCalled();
+ });
+
+ it('registers mouseleave event if popover is showed', (done) => {
+ spyOn(showPopover, 'call').and.returnValue(true);
+ spyOn($.fn, 'on').and.callFake((eventName) => {
+ expect(eventName).toEqual('mouseleave');
+ done();
+ });
+ mouseenter.call(context);
+ });
+
+ it('does not register mouseleave event if popover is not showed', () => {
+ spyOn(showPopover, 'call').and.returnValue(false);
+ const spy = spyOn($.fn, 'on').and.callFake(() => {});
+ mouseenter.call(context);
+ expect(spy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('setupDismissButton', () => {
+ it('registers click event callback', (done) => {
+ const context = {
+ getAttribute: () => 'popoverId',
+ dataset: {
+ highlight: 'cookieId',
+ },
+ };
+
+ spyOn($.fn, 'on').and.callFake((event) => {
+ expect(event).toEqual('click');
+ done();
+ });
+ setupDismissButton.call(context);
+ });
+ });
+});
diff --git a/spec/javascripts/feature_highlight/feature_highlight_options_spec.js b/spec/javascripts/feature_highlight/feature_highlight_options_spec.js
new file mode 100644
index 00000000000..7feb361edec
--- /dev/null
+++ b/spec/javascripts/feature_highlight/feature_highlight_options_spec.js
@@ -0,0 +1,45 @@
+import domContentLoaded from '~/feature_highlight/feature_highlight_options';
+import bp from '~/breakpoints';
+
+describe('feature highlight options', () => {
+ describe('domContentLoaded', () => {
+ const highlightOrder = [];
+
+ beforeEach(() => {
+ // Check for when highlightFeatures is called
+ spyOn(highlightOrder, 'find').and.callFake(() => {});
+ });
+
+ it('should not call highlightFeatures when breakpoint is xs', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('xs');
+
+ domContentLoaded(highlightOrder);
+ expect(bp.getBreakpointSize).toHaveBeenCalled();
+ expect(highlightOrder.find).not.toHaveBeenCalled();
+ });
+
+ it('should not call highlightFeatures when breakpoint is sm', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
+
+ domContentLoaded(highlightOrder);
+ expect(bp.getBreakpointSize).toHaveBeenCalled();
+ expect(highlightOrder.find).not.toHaveBeenCalled();
+ });
+
+ it('should not call highlightFeatures when breakpoint is md', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('md');
+
+ domContentLoaded(highlightOrder);
+ expect(bp.getBreakpointSize).toHaveBeenCalled();
+ expect(highlightOrder.find).not.toHaveBeenCalled();
+ });
+
+ it('should call highlightFeatures when breakpoint is lg', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('lg');
+
+ domContentLoaded(highlightOrder);
+ expect(bp.getBreakpointSize).toHaveBeenCalled();
+ expect(highlightOrder.find).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/feature_highlight/feature_highlight_spec.js b/spec/javascripts/feature_highlight/feature_highlight_spec.js
new file mode 100644
index 00000000000..6abe8425ee7
--- /dev/null
+++ b/spec/javascripts/feature_highlight/feature_highlight_spec.js
@@ -0,0 +1,122 @@
+import Cookies from 'js-cookie';
+import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
+import * as featureHighlight from '~/feature_highlight/feature_highlight';
+
+describe('feature highlight', () => {
+ describe('setupFeatureHighlightPopover', () => {
+ const selector = '.js-feature-highlight[data-highlight=test]';
+ beforeEach(() => {
+ setFixtures(`
+ <div>
+ <div class="js-feature-highlight" data-highlight="test" disabled>
+ Trigger
+ </div>
+ </div>
+ <div class="feature-highlight-popover-content">
+ Content
+ <div class="dismiss-feature-highlight">
+ Dismiss
+ </div>
+ </div>
+ `);
+ spyOn(window, 'addEventListener');
+ spyOn(window, 'removeEventListener');
+ featureHighlight.setupFeatureHighlightPopover('test', 0);
+ });
+
+ it('setups popover content', () => {
+ const $popoverContent = $('.feature-highlight-popover-content');
+ const outerHTML = $popoverContent.prop('outerHTML');
+
+ expect($(selector).data('content')).toEqual(outerHTML);
+ });
+
+ it('setups mouseenter', () => {
+ const showSpy = spyOn(featureHighlightHelper.showPopover, 'call');
+ $(selector).trigger('mouseenter');
+
+ expect(showSpy).toHaveBeenCalled();
+ });
+
+ it('setups debounced mouseleave', (done) => {
+ const hideSpy = spyOn(featureHighlightHelper.hidePopover, 'call');
+ $(selector).trigger('mouseleave');
+
+ // Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
+ setTimeout(() => {
+ expect(hideSpy).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+
+ it('setups inserted.bs.popover', () => {
+ $(selector).trigger('mouseenter');
+ const popoverId = $(selector).attr('aria-describedby');
+ const spyEvent = spyOnEvent(`#${popoverId} .dismiss-feature-highlight`, 'click');
+
+ $(`#${popoverId} .dismiss-feature-highlight`).click();
+ expect(spyEvent).toHaveBeenTriggered();
+ });
+
+ it('setups show.bs.popover', () => {
+ $(selector).trigger('show.bs.popover');
+ expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
+ });
+
+ it('setups hide.bs.popover', () => {
+ $(selector).trigger('hide.bs.popover');
+ expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
+ });
+
+ it('removes disabled attribute', () => {
+ expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
+ });
+
+ it('displays popover', () => {
+ expect($(selector).attr('aria-describedby')).toBeFalsy();
+ $(selector).trigger('mouseenter');
+ expect($(selector).attr('aria-describedby')).toBeTruthy();
+ });
+ });
+
+ describe('shouldHighlightFeature', () => {
+ it('should return false if element is not found', () => {
+ spyOn(document, 'querySelector').and.returnValue(null);
+ spyOn(Cookies, 'get').and.returnValue(null);
+
+ expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
+ });
+
+ it('should return false if previouslyDismissed', () => {
+ spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
+ spyOn(Cookies, 'get').and.returnValue('true');
+
+ expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
+ });
+
+ it('should return true if element is found and not previouslyDismissed', () => {
+ spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
+ spyOn(Cookies, 'get').and.returnValue(null);
+
+ expect(featureHighlight.shouldHighlightFeature()).toBeTruthy();
+ });
+ });
+
+ describe('highlightFeatures', () => {
+ it('calls setupFeatureHighlightPopover if shouldHighlightFeature returns true', () => {
+ // Mimic shouldHighlightFeature set to true
+ const highlightOrder = ['issue-boards'];
+ spyOn(highlightOrder, 'find').and.returnValue(highlightOrder[0]);
+
+ expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(true);
+ });
+
+ it('does not call setupFeatureHighlightPopover if shouldHighlightFeature returns false', () => {
+ // Mimic shouldHighlightFeature set to false
+ const highlightOrder = ['issue-boards'];
+ spyOn(highlightOrder, 'find').and.returnValue(null);
+
+ expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(false);
+ });
+ });
+});
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 10fcc590c89..dcb8dbce178 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -4,7 +4,10 @@ import '~/gl_dropdown';
import '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
-(() => {
+describe('glDropdown', function describeDropdown() {
+ preloadFixtures('static/gl_dropdown.html.raw');
+ loadJSONFixtures('projects.json');
+
const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
const SEARCH_INPUT_SELECTOR = '.dropdown-input-field';
const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`;
@@ -39,187 +42,217 @@ import '~/lib/utils/url_utility';
remoteCallback = callback.bind({}, data);
};
- describe('Dropdown', function describeDropdown() {
- preloadFixtures('static/gl_dropdown.html.raw');
- loadJSONFixtures('projects.json');
-
- function initDropDown(hasRemote, isFilterable, extraOpts = {}) {
- const options = Object.assign({
- selectable: true,
- filterable: isFilterable,
- data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData,
- search: {
- fields: ['name']
- },
- text: project => (project.name_with_namespace || project.name),
- id: project => project.id,
- }, extraOpts);
- this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options);
- }
+ function initDropDown(hasRemote, isFilterable, extraOpts = {}) {
+ const options = Object.assign({
+ selectable: true,
+ filterable: isFilterable,
+ data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData,
+ search: {
+ fields: ['name']
+ },
+ text: project => (project.name_with_namespace || project.name),
+ id: project => project.id,
+ }, extraOpts);
+ this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options);
+ }
+
+ beforeEach(() => {
+ loadFixtures('static/gl_dropdown.html.raw');
+ this.dropdownContainerElement = $('.dropdown.inline');
+ this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
+ this.projectsData = getJSONFixture('projects.json');
+ });
- beforeEach(() => {
- loadFixtures('static/gl_dropdown.html.raw');
- this.dropdownContainerElement = $('.dropdown.inline');
- this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
- this.projectsData = getJSONFixture('projects.json');
- });
+ afterEach(() => {
+ $('body').unbind('keydown');
+ this.dropdownContainerElement.unbind('keyup');
+ });
- afterEach(() => {
- $('body').unbind('keydown');
- this.dropdownContainerElement.unbind('keyup');
- });
+ it('should open on click', () => {
+ initDropDown.call(this, false);
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ this.dropdownButtonElement.click();
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ });
- it('should open on click', () => {
- initDropDown.call(this, false);
- expect(this.dropdownContainerElement).not.toHaveClass('open');
- this.dropdownButtonElement.click();
- expect(this.dropdownContainerElement).toHaveClass('open');
- });
+ it('escapes HTML as text', () => {
+ this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>';
- it('escapes HTML as text', () => {
- this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>';
+ initDropDown.call(this, false);
- initDropDown.call(this, false);
+ this.dropdownButtonElement.click();
- this.dropdownButtonElement.click();
+ expect(
+ $('.dropdown-content li:first-child').text(),
+ ).toBe('<script>alert("testing");</script>');
+ });
- expect(
- $('.dropdown-content li:first-child').text(),
- ).toBe('<script>alert("testing");</script>');
- });
+ it('should output HTML when highlighting', () => {
+ this.projectsData[0].name_with_namespace = 'testing';
+ $('.dropdown-input .dropdown-input-field').val('test');
- it('should output HTML when highlighting', () => {
- this.projectsData[0].name_with_namespace = 'testing';
- $('.dropdown-input .dropdown-input-field').val('test');
+ initDropDown.call(this, false, true, {
+ highlight: true,
+ });
- initDropDown.call(this, false, true, {
- highlight: true,
- });
+ this.dropdownButtonElement.click();
- this.dropdownButtonElement.click();
+ expect(
+ $('.dropdown-content li:first-child').text(),
+ ).toBe('testing');
- expect(
- $('.dropdown-content li:first-child').text(),
- ).toBe('testing');
+ expect(
+ $('.dropdown-content li:first-child a').html(),
+ ).toBe('<b>t</b><b>e</b><b>s</b><b>t</b>ing');
+ });
- expect(
- $('.dropdown-content li:first-child a').html(),
- ).toBe('<b>t</b><b>e</b><b>s</b><b>t</b>ing');
+ describe('that is open', () => {
+ beforeEach(() => {
+ initDropDown.call(this, false, false);
+ this.dropdownButtonElement.click();
});
- describe('that is open', () => {
- beforeEach(() => {
- initDropDown.call(this, false, false);
- this.dropdownButtonElement.click();
+ it('should select a following item on DOWN keypress', () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
+ navigateWithKeys('down', randomIndex, () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
+ expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
});
+ });
- it('should select a following item on DOWN keypress', () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
- const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
- navigateWithKeys('down', randomIndex, () => {
+ it('should select a previous item on UP keypress', () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
+ navigateWithKeys('down', (this.projectsData.length - 1), () => {
+ expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
+ navigateWithKeys('up', randomIndex, () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
+ expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
});
});
+ });
- it('should select a previous item on UP keypress', () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
- navigateWithKeys('down', (this.projectsData.length - 1), () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
- navigateWithKeys('up', randomIndex, () => {
- expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
- });
+ it('should click the selected item on ENTER keypress', () => {
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
+ navigateWithKeys('down', randomIndex, () => {
+ spyOn(gl.utils, 'visitUrl').and.stub();
+ navigateWithKeys('enter', null, () => {
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
+ expect(link).toHaveClass('is-active');
+ const linkedLocation = link.attr('href');
+ if (linkedLocation && linkedLocation !== '#') expect(gl.utils.visitUrl).toHaveBeenCalledWith(linkedLocation);
});
});
+ });
- it('should click the selected item on ENTER keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
- const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
- navigateWithKeys('down', randomIndex, () => {
- spyOn(gl.utils, 'visitUrl').and.stub();
- navigateWithKeys('enter', null, () => {
- expect(this.dropdownContainerElement).not.toHaveClass('open');
- const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
- expect(link).toHaveClass('is-active');
- const linkedLocation = link.attr('href');
- if (linkedLocation && linkedLocation !== '#') expect(gl.utils.visitUrl).toHaveBeenCalledWith(linkedLocation);
- });
- });
+ it('should close on ESC keypress', () => {
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ this.dropdownContainerElement.trigger({
+ type: 'keyup',
+ which: ARROW_KEYS.ESC,
+ keyCode: ARROW_KEYS.ESC
});
+ expect(this.dropdownContainerElement).not.toHaveClass('open');
+ });
+ });
- it('should close on ESC keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
- this.dropdownContainerElement.trigger({
- type: 'keyup',
- which: ARROW_KEYS.ESC,
- keyCode: ARROW_KEYS.ESC
- });
- expect(this.dropdownContainerElement).not.toHaveClass('open');
+ describe('opened and waiting for a remote callback', () => {
+ beforeEach(() => {
+ initDropDown.call(this, true, true);
+ this.dropdownButtonElement.click();
+ });
+
+ it('should show loading indicator while search results are being fetched by backend', () => {
+ const dropdownMenu = document.querySelector('.dropdown-menu');
+
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
+ remoteCallback();
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
+ });
+
+ it('should not focus search input while remote task is not complete', () => {
+ expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
+ remoteCallback();
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+
+ it('should focus search input after remote task is complete', () => {
+ remoteCallback();
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+
+ it('should focus on input when opening for the second time after transition', () => {
+ remoteCallback();
+ this.dropdownContainerElement.trigger({
+ type: 'keyup',
+ which: ARROW_KEYS.ESC,
+ keyCode: ARROW_KEYS.ESC
});
+ this.dropdownButtonElement.click();
+ this.dropdownContainerElement.trigger('transitionend');
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
+ });
+
+ describe('input focus with array data', () => {
+ it('should focus input when passing array data to drop down', () => {
+ initDropDown.call(this, false, true);
+ this.dropdownButtonElement.click();
+ this.dropdownContainerElement.trigger('transitionend');
+ expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ });
+ });
+
+ it('should still have input value on close and restore', () => {
+ const $searchInput = $(SEARCH_INPUT_SELECTOR);
+ initDropDown.call(this, false, true);
+ $searchInput
+ .trigger('focus')
+ .val('g')
+ .trigger('input');
+ expect($searchInput.val()).toEqual('g');
+ this.dropdownButtonElement.trigger('hidden.bs.dropdown');
+ $searchInput
+ .trigger('blur')
+ .trigger('focus');
+ expect($searchInput.val()).toEqual('g');
+ });
+
+ describe('renderItem', () => {
+ describe('without selected value', () => {
+ let dropdown;
- describe('opened and waiting for a remote callback', () => {
beforeEach(() => {
- initDropDown.call(this, true, true);
- this.dropdownButtonElement.click();
+ const dropdownOptions = {
+
+ };
+ const $dropdownDiv = $('<div />');
+ $dropdownDiv.glDropdown(dropdownOptions);
+ dropdown = $dropdownDiv.data('glDropdown');
});
- it('should show loading indicator while search results are being fetched by backend', () => {
- const dropdownMenu = document.querySelector('.dropdown-menu');
+ it('marks items without ID as active', () => {
+ const dummyData = { };
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
- remoteCallback();
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
- });
+ const html = dropdown.renderItem(dummyData, null, null);
- it('should not focus search input while remote task is not complete', () => {
- expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
- remoteCallback();
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ const link = html.querySelector('a');
+ expect(link).toHaveClass('is-active');
});
- it('should focus search input after remote task is complete', () => {
- remoteCallback();
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
- });
+ it('does not mark items with ID as active', () => {
+ const dummyData = {
+ id: 'ea'
+ };
- it('should focus on input when opening for the second time after transition', () => {
- remoteCallback();
- this.dropdownContainerElement.trigger({
- type: 'keyup',
- which: ARROW_KEYS.ESC,
- keyCode: ARROW_KEYS.ESC
- });
- this.dropdownButtonElement.click();
- this.dropdownContainerElement.trigger('transitionend');
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
- });
- });
+ const html = dropdown.renderItem(dummyData, null, null);
- describe('input focus with array data', () => {
- it('should focus input when passing array data to drop down', () => {
- initDropDown.call(this, false, true);
- this.dropdownButtonElement.click();
- this.dropdownContainerElement.trigger('transitionend');
- expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
+ const link = html.querySelector('a');
+ expect(link).not.toHaveClass('is-active');
});
});
-
- it('should still have input value on close and restore', () => {
- const $searchInput = $(SEARCH_INPUT_SELECTOR);
- initDropDown.call(this, false, true);
- $searchInput
- .trigger('focus')
- .val('g')
- .trigger('input');
- expect($searchInput.val()).toEqual('g');
- this.dropdownButtonElement.trigger('hidden.bs.dropdown');
- $searchInput
- .trigger('blur')
- .trigger('focus');
- expect($searchInput.val()).toEqual('g');
- });
});
-})();
+});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 3af26e2f28f..39065814bc2 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -34,7 +34,6 @@ describe('Issuable output', () => {
propsData: {
canUpdate: true,
canDestroy: true,
- canMove: true,
endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes',
issuableRef: '#1',
initialTitleHtml: '',
@@ -43,7 +42,6 @@ describe('Issuable output', () => {
initialDescriptionText: '',
markdownPreviewPath: '/',
markdownDocsPath: '/',
- projectsAutocompletePath: '/',
isConfidential: false,
projectNamespace: '/',
projectPath: '/',
@@ -226,7 +224,7 @@ describe('Issuable output', () => {
});
});
- it('redirects if issue is moved', (done) => {
+ it('redirects if returned web_url has changed', (done) => {
spyOn(gl.utils, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
@@ -250,23 +248,6 @@ describe('Issuable output', () => {
});
});
- it('does not update issuable if project move confirm is false', (done) => {
- spyOn(window, 'confirm').and.returnValue(false);
- spyOn(vm.service, 'updateIssuable');
-
- vm.store.formState.move_to_project_id = 1;
-
- vm.updateIssuable();
-
- setTimeout(() => {
- expect(
- vm.service.updateIssuable,
- ).not.toHaveBeenCalled();
-
- done();
- });
- });
-
it('closes form on error', (done) => {
spyOn(window, 'Flash').and.callThrough();
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => {
diff --git a/spec/javascripts/issue_show/components/fields/project_move_spec.js b/spec/javascripts/issue_show/components/fields/project_move_spec.js
deleted file mode 100644
index 8b6ed6a03a9..00000000000
--- a/spec/javascripts/issue_show/components/fields/project_move_spec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import Vue from 'vue';
-import projectMove from '~/issue_show/components/fields/project_move.vue';
-
-describe('Project move field component', () => {
- let vm;
- let formState;
-
- beforeEach((done) => {
- const Component = Vue.extend(projectMove);
-
- formState = {
- move_to_project_id: 0,
- };
-
- vm = new Component({
- propsData: {
- formState,
- projectsAutocompletePath: '/autocomplete',
- },
- }).$mount();
-
- Vue.nextTick(done);
- });
-
- it('mounts select2 element', () => {
- expect(
- vm.$el.querySelector('.select2-container'),
- ).not.toBeNull();
- });
-
- it('updates formState on change', () => {
- $(vm.$refs['move-dropdown']).val(2).trigger('change');
-
- expect(
- formState.move_to_project_id,
- ).toBe(2);
- });
-});
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index d8af5287431..6e89528a3ea 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -12,7 +12,6 @@ describe('Inline edit form component', () => {
vm = new Component({
propsData: {
canDestroy: true,
- canMove: true,
formState: {
title: 'b',
description: 'a',
@@ -20,7 +19,6 @@ describe('Inline edit form component', () => {
},
markdownPreviewPath: '/',
markdownDocsPath: '/',
- projectsAutocompletePath: '/',
projectPath: '/',
projectNamespace: '/',
},
diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js
index 731076a7d2a..14794cbfd50 100644
--- a/spec/javascripts/monitoring/graph/flag_spec.js
+++ b/spec/javascripts/monitoring/graph/flag_spec.js
@@ -32,10 +32,6 @@ describe('GraphFlag', () => {
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.selected-metric-line', 'x2'))
.toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.circle-metric', 'cx'))
- .toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.circle-metric', 'cy'))
- .toEqual(component.currentYCoordinate);
});
it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js
index e877832dffd..da2fbd26e23 100644
--- a/spec/javascripts/monitoring/graph/legend_spec.js
+++ b/spec/javascripts/monitoring/graph/legend_spec.js
@@ -1,6 +1,8 @@
import Vue from 'vue';
import GraphLegend from '~/monitoring/components/graph/legend.vue';
import measurements from '~/monitoring/utils/measurements';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(GraphLegend);
@@ -10,6 +12,28 @@ const createComponent = (propsData) => {
}).$mount();
};
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
+const defaultValuesComponent = {
+ graphWidth: 500,
+ graphHeight: 300,
+ graphHeightOffset: 120,
+ margin: measurements.large.margin,
+ measurements: measurements.large,
+ areaColorRgb: '#f0f0f0',
+ legendTitle: 'Title',
+ yAxisLabel: 'Values',
+ metricUsage: 'Value',
+ unitOfDisplay: 'Req/Sec',
+ currentDataIndex: 0,
+};
+
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result,
+ defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
+ defaultValuesComponent.graphHeightOffset);
+
+defaultValuesComponent.timeSeries = timeSeries;
+
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
@@ -17,95 +41,67 @@ function getTextFromNode(component, selector) {
describe('GraphLegend', () => {
describe('Computed props', () => {
it('textTransform', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
});
it('xPosition', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.xPosition).toEqual(180);
});
it('yPosition', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.yPosition).toEqual(240);
});
it('rectTransform', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
});
});
- it('has 2 rect-axis-text rect svg elements', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
+ describe('methods', () => {
+ it('translateLegendGroup should only change Y direction', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ const translatedCoordinate = component.translateLegendGroup(1);
+ expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1);
});
+ it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
+ const component = createComponent(defaultValuesComponent);
+
+ const formattedMetricUsage = component.formatMetricUsage(timeSeries[0]);
+ const valueFromSeries = timeSeries[0].values[component.currentDataIndex].value;
+ expect(formattedMetricUsage.indexOf(component.unitOfDisplay)).not.toEqual(-1);
+ expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
+ });
+ });
+
+ it('has 2 rect-axis-text rect svg elements', () => {
+ const component = createComponent(defaultValuesComponent);
+
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
});
it('contains text to signal the usage, title and time', () => {
- const component = createComponent({
- graphWidth: 500,
- graphHeight: 300,
- margin: measurements.large.margin,
- measurements: measurements.large,
- areaColorRgb: '#f0f0f0',
- legendTitle: 'Title',
- yAxisLabel: 'Values',
- metricUsage: 'Value',
- });
+ const component = createComponent(defaultValuesComponent);
+ const titles = component.$el.querySelectorAll('.legend-metric-title');
+
+ expect(getTextFromNode(component, '.legend-metric-title').indexOf(component.legendTitle)).not.toEqual(-1);
+ expect(titles[0].textContent.indexOf('Title')).not.toEqual(-1);
+ expect(titles[1].textContent.indexOf('Series')).not.toEqual(-1);
+ expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
+ });
+
+ it('should contain the same number of legend groups as the timeSeries length', () => {
+ const component = createComponent(defaultValuesComponent);
- expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle);
- expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage);
- expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
+ expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length);
});
});
diff --git a/spec/javascripts/monitoring/graph_row_spec.js b/spec/javascripts/monitoring/graph_row_spec.js
deleted file mode 100644
index dd485473ccf..00000000000
--- a/spec/javascripts/monitoring/graph_row_spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import Vue from 'vue';
-import GraphRow from '~/monitoring/components/graph_row.vue';
-import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
-import { deploymentData, singleRowMetrics } from './mock_data';
-
-const createComponent = (propsData) => {
- const Component = Vue.extend(GraphRow);
-
- return new Component({
- propsData,
- }).$mount();
-};
-
-describe('GraphRow', () => {
- beforeEach(() => {
- spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
- });
-
- describe('Computed props', () => {
- it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.bootstrapClass).toEqual('col-md-6');
- });
-
- it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
- const component = createComponent({
- rowData: [singleRowMetrics[0]],
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.bootstrapClass).toEqual('col-md-12');
- });
- });
-
- it('has one column', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.$el.querySelectorAll('.prometheus-svg-container').length)
- .toEqual(component.rowData.length);
- });
-
- it('has two columns', () => {
- const component = createComponent({
- rowData: singleRowMetrics,
- updateAspectRatio: false,
- deploymentData,
- });
-
- expect(component.$el.querySelectorAll('.col-md-6').length)
- .toEqual(component.rowData.length);
- });
-});
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index 6d6fe410113..7d8b0744af1 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -1,9 +1,8 @@
import Vue from 'vue';
-import _ from 'underscore';
import Graph from '~/monitoring/components/graph.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub';
-import { deploymentData, singleRowMetrics } from './mock_data';
+import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(Graph);
@@ -13,6 +12,8 @@ const createComponent = (propsData) => {
}).$mount();
};
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
describe('Graph', () => {
beforeEach(() => {
spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
@@ -20,7 +21,7 @@ describe('Graph', () => {
it('has a title', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -29,29 +30,10 @@ describe('Graph', () => {
expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title);
});
- it('creates a path for the line and area of the graph', (done) => {
- const component = createComponent({
- graphData: singleRowMetrics[0],
- classType: 'col-md-6',
- updateAspectRatio: false,
- deploymentData,
- });
-
- Vue.nextTick(() => {
- expect(component.area).toBeDefined();
- expect(component.line).toBeDefined();
- expect(typeof component.area).toEqual('string');
- expect(typeof component.line).toEqual('string');
- expect(_.isFunction(component.xScale)).toBe(true);
- expect(_.isFunction(component.yScale)).toBe(true);
- done();
- });
- });
-
describe('Computed props', () => {
it('axisTransform translates an element Y position depending of its height', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -64,7 +46,7 @@ describe('Graph', () => {
it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -79,7 +61,7 @@ describe('Graph', () => {
it('sends an event to the eventhub when it has finished resizing', (done) => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
@@ -95,7 +77,7 @@ describe('Graph', () => {
it('has a title for the y-axis and the chart legend that comes from the backend', () => {
const component = createComponent({
- graphData: singleRowMetrics[0],
+ graphData: convertedMetrics[1],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index b69f4eddffc..3d399f2bb95 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -2473,1754 +2473,5848 @@ export const statePaths = {
documentationPath: '/help/administration/monitoring/prometheus/index.md',
};
-export const singleRowMetrics = [
- {
- 'title': 'CPU usage',
- 'weight': 1,
- 'y_label': 'Memory',
- 'queries': [
- {
- 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
- 'label': 'Container CPU',
- 'result': [
- {
- 'metric': {
-
- },
- 'values': [
- {
- 'time': '2017-06-04T21:22:59.508Z',
- 'value': '0.06335544298150002'
- },
- {
- 'time': '2017-06-04T21:23:59.508Z',
- 'value': '0.0420347312480917'
- },
- {
- 'time': '2017-06-04T21:24:59.508Z',
- 'value': '0.0023175131665412706'
- },
- {
- 'time': '2017-06-04T21:25:59.508Z',
- 'value': '0.002315870476190476'
- },
- {
- 'time': '2017-06-04T21:26:59.508Z',
- 'value': '0.0025005961904761894'
- },
- {
- 'time': '2017-06-04T21:27:59.508Z',
- 'value': '0.0024612605834341264'
- },
- {
- 'time': '2017-06-04T21:28:59.508Z',
- 'value': '0.002313129398767631'
- },
- {
- 'time': '2017-06-04T21:29:59.508Z',
- 'value': '0.002411067353663882'
- },
- {
- 'time': '2017-06-04T21:30:59.508Z',
- 'value': '0.002577309263721303'
- },
- {
- 'time': '2017-06-04T21:31:59.508Z',
- 'value': '0.00242688307730403'
- },
- {
- 'time': '2017-06-04T21:32:59.508Z',
- 'value': '0.0024168360301330457'
- },
- {
- 'time': '2017-06-04T21:33:59.508Z',
- 'value': '0.0020449528090743714'
- },
- {
- 'time': '2017-06-04T21:34:59.508Z',
- 'value': '0.0019149619047619036'
- },
- {
- 'time': '2017-06-04T21:35:59.508Z',
- 'value': '0.0024491714364625094'
- },
- {
- 'time': '2017-06-04T21:36:59.508Z',
- 'value': '0.002728773131172677'
- },
- {
- 'time': '2017-06-04T21:37:59.508Z',
- 'value': '0.0028439119047618997'
- },
- {
- 'time': '2017-06-04T21:38:59.508Z',
- 'value': '0.0026307480952380917'
- },
- {
- 'time': '2017-06-04T21:39:59.508Z',
- 'value': '0.0025024842620546446'
- },
- {
- 'time': '2017-06-04T21:40:59.508Z',
- 'value': '0.002300662387260825'
- },
- {
- 'time': '2017-06-04T21:41:59.508Z',
- 'value': '0.002052890924848337'
- },
- {
- 'time': '2017-06-04T21:42:59.508Z',
- 'value': '0.0023711195238095275'
- },
- {
- 'time': '2017-06-04T21:43:59.508Z',
- 'value': '0.002513477619047618'
- },
- {
- 'time': '2017-06-04T21:44:59.508Z',
- 'value': '0.0023489776287844897'
- },
- {
- 'time': '2017-06-04T21:45:59.508Z',
- 'value': '0.002542572310212481'
- },
- {
- 'time': '2017-06-04T21:46:59.508Z',
- 'value': '0.0024579470671707952'
- },
- {
- 'time': '2017-06-04T21:47:59.508Z',
- 'value': '0.0028725150236664403'
- },
- {
- 'time': '2017-06-04T21:48:59.508Z',
- 'value': '0.0024356089105610525'
- },
- {
- 'time': '2017-06-04T21:49:59.508Z',
- 'value': '0.002544015828269929'
- },
- {
- 'time': '2017-06-04T21:50:59.508Z',
- 'value': '0.0029595013380824906'
- },
- {
- 'time': '2017-06-04T21:51:59.508Z',
- 'value': '0.0023084015085858'
- },
- {
- 'time': '2017-06-04T21:52:59.508Z',
- 'value': '0.0021070500000000083'
- },
- {
- 'time': '2017-06-04T21:53:59.508Z',
- 'value': '0.0022950066191106617'
- },
- {
- 'time': '2017-06-04T21:54:59.508Z',
- 'value': '0.002492719454470995'
- },
- {
- 'time': '2017-06-04T21:55:59.508Z',
- 'value': '0.00244312761904762'
- },
- {
- 'time': '2017-06-04T21:56:59.508Z',
- 'value': '0.0023495500000000028'
- },
- {
- 'time': '2017-06-04T21:57:59.508Z',
- 'value': '0.0020597072353070005'
- },
- {
- 'time': '2017-06-04T21:58:59.508Z',
- 'value': '0.0021482352044800866'
- },
- {
- 'time': '2017-06-04T21:59:59.508Z',
- 'value': '0.002333490000000004'
- },
- {
- 'time': '2017-06-04T22:00:59.508Z',
- 'value': '0.0025899442857142815'
- },
- {
- 'time': '2017-06-04T22:01:59.508Z',
- 'value': '0.002430299999999999'
- },
- {
- 'time': '2017-06-04T22:02:59.508Z',
- 'value': '0.0023550328092113476'
- },
- {
- 'time': '2017-06-04T22:03:59.508Z',
- 'value': '0.0026521871636872793'
- },
- {
- 'time': '2017-06-04T22:04:59.508Z',
- 'value': '0.0023080671428571398'
- },
- {
- 'time': '2017-06-04T22:05:59.508Z',
- 'value': '0.0024108401032390896'
- },
- {
- 'time': '2017-06-04T22:06:59.508Z',
- 'value': '0.002433249366678738'
- },
- {
- 'time': '2017-06-04T22:07:59.508Z',
- 'value': '0.0023242202306688682'
- },
- {
- 'time': '2017-06-04T22:08:59.508Z',
- 'value': '0.002388222857142859'
- },
- {
- 'time': '2017-06-04T22:09:59.508Z',
- 'value': '0.002115974914046794'
- },
- {
- 'time': '2017-06-04T22:10:59.508Z',
- 'value': '0.0025090043331269917'
- },
- {
- 'time': '2017-06-04T22:11:59.508Z',
- 'value': '0.002445507057277277'
- },
- {
- 'time': '2017-06-04T22:12:59.508Z',
- 'value': '0.0026348773751130976'
- },
- {
- 'time': '2017-06-04T22:13:59.508Z',
- 'value': '0.0025616258583088104'
- },
- {
- 'time': '2017-06-04T22:14:59.508Z',
- 'value': '0.0021544093415751505'
- },
- {
- 'time': '2017-06-04T22:15:59.508Z',
- 'value': '0.002649394767668881'
- },
- {
- 'time': '2017-06-04T22:16:59.508Z',
- 'value': '0.0024023332666685705'
- },
- {
- 'time': '2017-06-04T22:17:59.508Z',
- 'value': '0.0025444105294235306'
- },
- {
- 'time': '2017-06-04T22:18:59.508Z',
- 'value': '0.0027298872305772806'
- },
- {
- 'time': '2017-06-04T22:19:59.508Z',
- 'value': '0.0022880104956379287'
- },
- {
- 'time': '2017-06-04T22:20:59.508Z',
- 'value': '0.002473246666666661'
- },
- {
- 'time': '2017-06-04T22:21:59.508Z',
- 'value': '0.002259948381935587'
- },
- {
- 'time': '2017-06-04T22:22:59.508Z',
- 'value': '0.0025778470886268835'
- },
- {
- 'time': '2017-06-04T22:23:59.508Z',
- 'value': '0.002246127910852894'
- },
- {
- 'time': '2017-06-04T22:24:59.508Z',
- 'value': '0.0020697466666666758'
- },
- {
- 'time': '2017-06-04T22:25:59.508Z',
- 'value': '0.00225859722473547'
- },
- {
- 'time': '2017-06-04T22:26:59.508Z',
- 'value': '0.0026466728254554814'
- },
- {
- 'time': '2017-06-04T22:27:59.508Z',
- 'value': '0.002151247619047619'
- },
- {
- 'time': '2017-06-04T22:28:59.508Z',
- 'value': '0.002324161444543914'
- },
- {
- 'time': '2017-06-04T22:29:59.508Z',
- 'value': '0.002476474313796452'
- },
- {
- 'time': '2017-06-04T22:30:59.508Z',
- 'value': '0.0023922184232080517'
- },
- {
- 'time': '2017-06-04T22:31:59.508Z',
- 'value': '0.0025094934237468933'
- },
- {
- 'time': '2017-06-04T22:32:59.508Z',
- 'value': '0.0025665311098200883'
- },
- {
- 'time': '2017-06-04T22:33:59.508Z',
- 'value': '0.0024154900681661374'
- },
- {
- 'time': '2017-06-04T22:34:59.508Z',
- 'value': '0.0023267450166192037'
- },
- {
- 'time': '2017-06-04T22:35:59.508Z',
- 'value': '0.002156521904761904'
- },
- {
- 'time': '2017-06-04T22:36:59.508Z',
- 'value': '0.0025474356898637007'
- },
- {
- 'time': '2017-06-04T22:37:59.508Z',
- 'value': '0.0025989409624670233'
- },
- {
- 'time': '2017-06-04T22:38:59.508Z',
- 'value': '0.002348336664762987'
- },
- {
- 'time': '2017-06-04T22:39:59.508Z',
- 'value': '0.002665888246554726'
- },
- {
- 'time': '2017-06-04T22:40:59.508Z',
- 'value': '0.002652684787474174'
- },
- {
- 'time': '2017-06-04T22:41:59.508Z',
- 'value': '0.002472620430865355'
- },
- {
- 'time': '2017-06-04T22:42:59.508Z',
- 'value': '0.0020616469210110247'
- },
- {
- 'time': '2017-06-04T22:43:59.508Z',
- 'value': '0.0022434546372311934'
- },
- {
- 'time': '2017-06-04T22:44:59.508Z',
- 'value': '0.0024469386784827982'
- },
- {
- 'time': '2017-06-04T22:45:59.508Z',
- 'value': '0.0026192823809523787'
- },
- {
- 'time': '2017-06-04T22:46:59.508Z',
- 'value': '0.003451999542852798'
- },
- {
- 'time': '2017-06-04T22:47:59.508Z',
- 'value': '0.0031780314285714288'
- },
- {
- 'time': '2017-06-04T22:48:59.508Z',
- 'value': '0.0024403352380952415'
- },
- {
- 'time': '2017-06-04T22:49:59.508Z',
- 'value': '0.001998824761904764'
- },
- {
- 'time': '2017-06-04T22:50:59.508Z',
- 'value': '0.0023792404761904806'
- },
- {
- 'time': '2017-06-04T22:51:59.508Z',
- 'value': '0.002725906190476185'
- },
- {
- 'time': '2017-06-04T22:52:59.508Z',
- 'value': '0.0020989528671155624'
- },
- {
- 'time': '2017-06-04T22:53:59.508Z',
- 'value': '0.00228808226745016'
- },
- {
- 'time': '2017-06-04T22:54:59.508Z',
- 'value': '0.0019860807413192147'
- },
- {
- 'time': '2017-06-04T22:55:59.508Z',
- 'value': '0.0022698085714285897'
- },
- {
- 'time': '2017-06-04T22:56:59.508Z',
- 'value': '0.0022839098467604415'
- },
- {
- 'time': '2017-06-04T22:57:59.508Z',
- 'value': '0.002531114761904749'
- },
- {
- 'time': '2017-06-04T22:58:59.508Z',
- 'value': '0.0028941072550999016'
- },
- {
- 'time': '2017-06-04T22:59:59.508Z',
- 'value': '0.002547169523809506'
- },
- {
- 'time': '2017-06-04T23:00:59.508Z',
- 'value': '0.0024062999999999958'
- },
- {
- 'time': '2017-06-04T23:01:59.508Z',
- 'value': '0.0026939518471604386'
- },
- {
- 'time': '2017-06-04T23:02:59.508Z',
- 'value': '0.002362901428571429'
- },
- {
- 'time': '2017-06-04T23:03:59.508Z',
- 'value': '0.002663927142857154'
- },
- {
- 'time': '2017-06-04T23:04:59.508Z',
- 'value': '0.0026173314285714354'
- },
- {
- 'time': '2017-06-04T23:05:59.508Z',
- 'value': '0.002326527366406044'
- },
- {
- 'time': '2017-06-04T23:06:59.508Z',
- 'value': '0.002035313809523809'
- },
- {
- 'time': '2017-06-04T23:07:59.508Z',
- 'value': '0.002421447414786533'
- },
- {
- 'time': '2017-06-04T23:08:59.508Z',
- 'value': '0.002898313809523804'
- },
- {
- 'time': '2017-06-04T23:09:59.508Z',
- 'value': '0.002544891856112907'
- },
- {
- 'time': '2017-06-04T23:10:59.508Z',
- 'value': '0.002290625356938882'
- },
- {
- 'time': '2017-06-04T23:11:59.508Z',
- 'value': '0.002483028095238096'
- },
- {
- 'time': '2017-06-04T23:12:59.508Z',
- 'value': '0.0023396832350784237'
- },
- {
- 'time': '2017-06-04T23:13:59.508Z',
- 'value': '0.002085529248176153'
- },
- {
- 'time': '2017-06-04T23:14:59.508Z',
- 'value': '0.0022417815068428012'
- },
- {
- 'time': '2017-06-04T23:15:59.508Z',
- 'value': '0.002660293333333341'
- },
- {
- 'time': '2017-06-04T23:16:59.508Z',
- 'value': '0.0029845149093818226'
- },
- {
- 'time': '2017-06-04T23:17:59.508Z',
- 'value': '0.0027716655079475464'
- },
- {
- 'time': '2017-06-04T23:18:59.508Z',
- 'value': '0.0025217708908741128'
- },
- {
- 'time': '2017-06-04T23:19:59.508Z',
- 'value': '0.0025811235131094055'
- },
- {
- 'time': '2017-06-04T23:20:59.508Z',
- 'value': '0.002209904761904762'
- },
- {
- 'time': '2017-06-04T23:21:59.508Z',
- 'value': '0.0025053322926383344'
- },
- {
- 'time': '2017-06-04T23:22:59.508Z',
- 'value': '0.002350917636526411'
- },
- {
- 'time': '2017-06-04T23:23:59.508Z',
- 'value': '0.0018477500000000078'
- },
- {
- 'time': '2017-06-04T23:24:59.508Z',
- 'value': '0.002427629523809527'
- },
- {
- 'time': '2017-06-04T23:25:59.508Z',
- 'value': '0.0019305498147601655'
- },
- {
- 'time': '2017-06-04T23:26:59.508Z',
- 'value': '0.002097250000000006'
- },
- {
- 'time': '2017-06-04T23:27:59.508Z',
- 'value': '0.002675020952780041'
- },
- {
- 'time': '2017-06-04T23:28:59.508Z',
- 'value': '0.0023142214285714374'
- },
- {
- 'time': '2017-06-04T23:29:59.508Z',
- 'value': '0.0023644723809523737'
- },
- {
- 'time': '2017-06-04T23:30:59.508Z',
- 'value': '0.002108696190476198'
- },
- {
- 'time': '2017-06-04T23:31:59.508Z',
- 'value': '0.0019918289697997194'
- },
- {
- 'time': '2017-06-04T23:32:59.508Z',
- 'value': '0.001583584285714283'
- },
- {
- 'time': '2017-06-04T23:33:59.508Z',
- 'value': '0.002073770226383112'
- },
- {
- 'time': '2017-06-04T23:34:59.508Z',
- 'value': '0.0025877664234966818'
- },
- {
- 'time': '2017-06-04T23:35:59.508Z',
- 'value': '0.0021138238095238147'
- },
- {
- 'time': '2017-06-04T23:36:59.508Z',
- 'value': '0.0022140838095238303'
- },
- {
- 'time': '2017-06-04T23:37:59.508Z',
- 'value': '0.0018592674425248847'
- },
- {
- 'time': '2017-06-04T23:38:59.508Z',
- 'value': '0.0020461969533657016'
- },
- {
- 'time': '2017-06-04T23:39:59.508Z',
- 'value': '0.0021593628571428543'
- },
- {
- 'time': '2017-06-04T23:40:59.508Z',
- 'value': '0.0024330682564928188'
- },
- {
- 'time': '2017-06-04T23:41:59.508Z',
- 'value': '0.0021501804779093174'
- },
- {
- 'time': '2017-06-04T23:42:59.508Z',
- 'value': '0.0025787493928397945'
- },
- {
- 'time': '2017-06-04T23:43:59.508Z',
- 'value': '0.002593657082448396'
- },
- {
- 'time': '2017-06-04T23:44:59.508Z',
- 'value': '0.0021316752380952306'
- },
- {
- 'time': '2017-06-04T23:45:59.508Z',
- 'value': '0.0026972905019952086'
- },
- {
- 'time': '2017-06-04T23:46:59.508Z',
- 'value': '0.002580250764292983'
- },
- {
- 'time': '2017-06-04T23:47:59.508Z',
- 'value': '0.00227103000000001'
- },
- {
- 'time': '2017-06-04T23:48:59.508Z',
- 'value': '0.0023678515647321146'
- },
- {
- 'time': '2017-06-04T23:49:59.508Z',
- 'value': '0.002371472857142866'
- },
- {
- 'time': '2017-06-04T23:50:59.508Z',
- 'value': '0.0026181353688500978'
- },
- {
- 'time': '2017-06-04T23:51:59.508Z',
- 'value': '0.0025609667711121217'
- },
- {
- 'time': '2017-06-04T23:52:59.508Z',
- 'value': '0.0027145308139922557'
- },
- {
- 'time': '2017-06-04T23:53:59.508Z',
- 'value': '0.0024249397613310512'
- },
- {
- 'time': '2017-06-04T23:54:59.508Z',
- 'value': '0.002399907142857147'
- },
- {
- 'time': '2017-06-04T23:55:59.508Z',
- 'value': '0.0024753357142857195'
- },
- {
- 'time': '2017-06-04T23:56:59.508Z',
- 'value': '0.0026179149325231575'
- },
- {
- 'time': '2017-06-04T23:57:59.508Z',
- 'value': '0.0024261340368186956'
- },
- {
- 'time': '2017-06-04T23:58:59.508Z',
- 'value': '0.0021061071428571517'
- },
- {
- 'time': '2017-06-04T23:59:59.508Z',
- 'value': '0.0024033971105037015'
- },
- {
- 'time': '2017-06-05T00:00:59.508Z',
- 'value': '0.0028287676190475956'
- },
- {
- 'time': '2017-06-05T00:01:59.508Z',
- 'value': '0.002499719050294778'
- },
- {
- 'time': '2017-06-05T00:02:59.508Z',
- 'value': '0.0026726102153353856'
- },
- {
- 'time': '2017-06-05T00:03:59.508Z',
- 'value': '0.00262582619047618'
- },
- {
- 'time': '2017-06-05T00:04:59.508Z',
- 'value': '0.002280473147363316'
- },
- {
- 'time': '2017-06-05T00:05:59.508Z',
- 'value': '0.002095581470652675'
- },
- {
- 'time': '2017-06-05T00:06:59.508Z',
- 'value': '0.002270768490828408'
- },
- {
- 'time': '2017-06-05T00:07:59.508Z',
- 'value': '0.002728577415023017'
- },
- {
- 'time': '2017-06-05T00:08:59.508Z',
- 'value': '0.002652512857142863'
- },
- {
- 'time': '2017-06-05T00:09:59.508Z',
- 'value': '0.0022781033924455674'
- },
- {
- 'time': '2017-06-05T00:10:59.508Z',
- 'value': '0.0025345038095238234'
- },
- {
- 'time': '2017-06-05T00:11:59.508Z',
- 'value': '0.002376050020000397'
- },
- {
- 'time': '2017-06-05T00:12:59.508Z',
- 'value': '0.002455068143506122'
- },
- {
- 'time': '2017-06-05T00:13:59.508Z',
- 'value': '0.002826705714285719'
- },
- {
- 'time': '2017-06-05T00:14:59.508Z',
- 'value': '0.002343833692070314'
- },
- {
- 'time': '2017-06-05T00:15:59.508Z',
- 'value': '0.00264853297122164'
- },
- {
- 'time': '2017-06-05T00:16:59.508Z',
- 'value': '0.0027656335117426257'
- },
- {
- 'time': '2017-06-05T00:17:59.508Z',
- 'value': '0.0025896543842439564'
- },
- {
- 'time': '2017-06-05T00:18:59.508Z',
- 'value': '0.002180053237081201'
- },
- {
- 'time': '2017-06-05T00:19:59.508Z',
- 'value': '0.002475245002333342'
- },
- {
- 'time': '2017-06-05T00:20:59.508Z',
- 'value': '0.0027559767805101065'
- },
- {
- 'time': '2017-06-05T00:21:59.508Z',
- 'value': '0.0022294836141296607'
- },
- {
- 'time': '2017-06-05T00:22:59.508Z',
- 'value': '0.0021383590476190643'
- },
- {
- 'time': '2017-06-05T00:23:59.508Z',
- 'value': '0.002085417956361494'
- },
- {
- 'time': '2017-06-05T00:24:59.508Z',
- 'value': '0.0024140319047619013'
- },
- {
- 'time': '2017-06-05T00:25:59.508Z',
- 'value': '0.0024513114285714304'
- },
- {
- 'time': '2017-06-05T00:26:59.508Z',
- 'value': '0.0026932152380952446'
- },
- {
- 'time': '2017-06-05T00:27:59.508Z',
- 'value': '0.0022656844350898517'
- },
- {
- 'time': '2017-06-05T00:28:59.508Z',
- 'value': '0.0024483785714285704'
- },
- {
- 'time': '2017-06-05T00:29:59.508Z',
- 'value': '0.002559505804817207'
- },
- {
- 'time': '2017-06-05T00:30:59.508Z',
- 'value': '0.0019485681088751649'
- },
- {
- 'time': '2017-06-05T00:31:59.508Z',
- 'value': '0.00228367984456996'
- },
- {
- 'time': '2017-06-05T00:32:59.508Z',
- 'value': '0.002522149047619049'
- },
- {
- 'time': '2017-06-05T00:33:59.508Z',
- 'value': '0.0026860117715406737'
- },
- {
- 'time': '2017-06-05T00:34:59.508Z',
- 'value': '0.002679669523809523'
- },
- {
- 'time': '2017-06-05T00:35:59.508Z',
- 'value': '0.0022201920970675937'
- },
- {
- 'time': '2017-06-05T00:36:59.508Z',
- 'value': '0.0022917647619047615'
- },
- {
- 'time': '2017-06-05T00:37:59.508Z',
- 'value': '0.0021774059294673576'
- },
- {
- 'time': '2017-06-05T00:38:59.508Z',
- 'value': '0.0024637766666666763'
- },
- {
- 'time': '2017-06-05T00:39:59.508Z',
- 'value': '0.002470468290174195'
- },
- {
- 'time': '2017-06-05T00:40:59.508Z',
- 'value': '0.0022188616082057812'
- },
- {
- 'time': '2017-06-05T00:41:59.508Z',
- 'value': '0.002421840744373875'
- },
- {
- 'time': '2017-06-05T00:42:59.508Z',
- 'value': '0.0023918266666666547'
- },
- {
- 'time': '2017-06-05T00:43:59.508Z',
- 'value': '0.002195743809523809'
- },
- {
- 'time': '2017-06-05T00:44:59.508Z',
- 'value': '0.0025514828571428687'
- },
- {
- 'time': '2017-06-05T00:45:59.508Z',
- 'value': '0.0027981709349612694'
- },
- {
- 'time': '2017-06-05T00:46:59.508Z',
- 'value': '0.002557977142857146'
- },
- {
- 'time': '2017-06-05T00:47:59.508Z',
- 'value': '0.002213244285714286'
- },
- {
- 'time': '2017-06-05T00:48:59.508Z',
- 'value': '0.0025706738095238046'
- },
- {
- 'time': '2017-06-05T00:49:59.508Z',
- 'value': '0.002210976666666671'
- },
- {
- 'time': '2017-06-05T00:50:59.508Z',
- 'value': '0.002055377091646749'
- },
- {
- 'time': '2017-06-05T00:51:59.508Z',
- 'value': '0.002308368095238119'
- },
- {
- 'time': '2017-06-05T00:52:59.508Z',
- 'value': '0.0024687939885141615'
- },
- {
- 'time': '2017-06-05T00:53:59.508Z',
- 'value': '0.002563018571428578'
- },
- {
- 'time': '2017-06-05T00:54:59.508Z',
- 'value': '0.00240563291078959'
- }
- ]
- }
+export const singleRowMetricsMultipleSeries = [
+ {
+ 'title': 'Multiple Time Series',
+ 'weight': 1,
+ 'y_label': 'Request Rates',
+ 'queries': [
+ {
+ 'query_range': 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
+ 'label': 'Requests',
+ 'unit': 'Req/sec',
+ 'result': [
+ {
+ 'metric': {
+ 'status_code': '1xx'
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '0'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '0'
+ }
+ ]
+ },
+ {
+ 'metric': {
+ 'status_code': '2xx'
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '1.3333460318669703'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '1.2952627669098458'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '1.3333079369916765'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '1.276190476190476'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '1.3333460318669703'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '1.3333587306424883'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '1.3142982314117277'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '1.333320635041571'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '1.31427319739812'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '1.2952504309564854'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '1.295225759754669'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '1.2571428571428571'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '1.580952380952381'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '1.7333333333333334'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '2.057142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '2.1904761904761902'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '1.8285714285714287'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '2.1142857142857143'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '1.619047619047619'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '1.2952504309564854'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '1.3333333333333333'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '1.314285714285714'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '1.295238095238095'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '1.7142857142857142'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '1.7333333333333334'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '1.3904761904761904'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '1.5047619047619047'
+ }
+ ]
+ },
+ ]
+ }
]
- }
- ]
- },
- {
- 'title': 'Memory usage',
- 'weight': 1,
- 'y_label': 'Values',
- 'queries': [
- {
- 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
- 'label': 'Container memory',
- 'unit': 'MiB',
- 'result': [
- {
- 'metric': {
+ },
+ {
+ 'title': 'Throughput',
+ 'weight': 1,
+ 'y_label': 'Requests / Sec',
+ 'queries': [
+ {
+ 'query_range': 'sum(rate(nginx_requests_total{server_zone!=\'*\', server_zone!=\'_\', container_name!=\'POD\',environment=\'production\'}[2m]))',
+ 'label': 'Total',
+ 'unit': 'req / sec',
+ 'result': [
+ {
+ 'metric': {
- },
- 'values': [
- {
- 'time': '2017-06-04T21:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T21:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T22:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:54:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:55:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:56:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:57:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:58:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-04T23:59:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:00:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:01:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:02:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:03:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:04:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:05:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:06:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:07:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:08:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:09:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:10:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:11:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:12:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:13:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:14:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:15:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:16:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:17:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:18:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:19:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:20:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:21:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:22:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:23:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:24:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:25:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:26:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:27:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:28:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:29:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:30:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:31:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:32:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:33:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:34:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:35:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:36:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:37:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:38:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:39:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:40:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:41:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:42:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:43:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:44:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:45:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:46:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:47:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:48:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:49:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:50:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:51:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:52:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:53:59.508Z',
- 'value': '15.0859375'
- },
- {
- 'time': '2017-06-05T00:54:59.508Z',
- 'value': '15.0859375'
- }
- ]
- }
+ },
+ 'values': [
+ {
+ 'time': '2017-08-27T11:01:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:02:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T11:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:07:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:14:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:18:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:21:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:32:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:34:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T11:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:39:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:41:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T11:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:43:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:45:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:46:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:47:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:48:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:49:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T11:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T11:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T11:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:01:51.462Z',
+ 'value': '0.49524281183630325'
+ },
+ {
+ 'time': '2017-08-27T12:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:04:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:05:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T12:06:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:10:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:13:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:18:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:23:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:33:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:34:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:42:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:43:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:46:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:47:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:49:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:53:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T12:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:56:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T12:58:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T12:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:01:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T13:02:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:03:51.462Z',
+ 'value': '0.4761995466580315'
+ },
+ {
+ 'time': '2017-08-27T13:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:06:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:14:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:15:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:16:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T13:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:22:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:23:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T13:24:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:32:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:34:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:35:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:41:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:43:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:46:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T13:47:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T13:48:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:49:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T13:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:51:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:52:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:53:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:54:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:55:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:56:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T13:57:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T13:58:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T13:59:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T14:00:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:01:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:16:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:17:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:18:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:19:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:21:51.462Z',
+ 'value': '0.4952286623111941'
+ },
+ {
+ 'time': '2017-08-27T14:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:24:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:25:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:30:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:33:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T14:34:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:38:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:40:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:41:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:42:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:43:51.462Z',
+ 'value': '0.4666666666666667'
+ },
+ {
+ 'time': '2017-08-27T14:44:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T14:45:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:46:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:47:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:49:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:51:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:53:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T14:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T14:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T14:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T14:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:04:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T15:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:08:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:09:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:10:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:11:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:12:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T15:13:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:20:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:22:51.462Z',
+ 'value': '0.49524281183630325'
+ },
+ {
+ 'time': '2017-08-27T15:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:24:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:25:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:27:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:31:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:33:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:34:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:35:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:37:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:38:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:39:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:42:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:44:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:45:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:49:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T15:50:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:55:51.462Z',
+ 'value': '0.49524752852435283'
+ },
+ {
+ 'time': '2017-08-27T15:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T15:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:58:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T15:59:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:00:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:01:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:03:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:04:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:05:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:07:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:08:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:09:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:12:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:15:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:16:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:17:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:18:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:19:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:24:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T16:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:26:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:27:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:29:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:30:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:32:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:34:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:36:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:42:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:44:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:45:51.462Z',
+ 'value': '0.485718911608682'
+ },
+ {
+ 'time': '2017-08-27T16:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:48:51.462Z',
+ 'value': '0.4952333787297264'
+ },
+ {
+ 'time': '2017-08-27T16:49:51.462Z',
+ 'value': '0.4857096599080009'
+ },
+ {
+ 'time': '2017-08-27T16:50:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:51:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:53:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:55:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T16:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:57:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T16:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T16:59:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:02:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:03:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:04:51.462Z',
+ 'value': '0.47619501138106085'
+ },
+ {
+ 'time': '2017-08-27T17:05:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:06:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:07:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:08:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:12:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:16:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:20:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:21:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:22:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:23:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:24:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:25:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:26:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:27:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:28:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:29:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T17:30:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:31:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:32:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:33:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:34:51.462Z',
+ 'value': '0.4761859410862754'
+ },
+ {
+ 'time': '2017-08-27T17:35:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:36:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:37:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:38:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:41:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:42:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:43:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:44:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:45:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:46:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:47:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:48:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:49:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:50:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:51:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:52:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:53:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:54:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:55:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T17:56:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:57:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T17:58:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T17:59:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:00:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:01:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:02:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:03:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:04:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:05:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:06:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:07:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:08:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:09:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:10:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:11:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:12:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:13:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:14:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:15:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:16:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:17:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:18:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:19:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:20:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:21:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:22:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:23:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:24:51.462Z',
+ 'value': '0.45714285714285713'
+ },
+ {
+ 'time': '2017-08-27T18:25:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:26:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:27:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:28:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:29:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:30:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:31:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:32:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:33:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:34:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:35:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:36:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:37:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:38:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:39:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:40:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:41:51.462Z',
+ 'value': '0.6190476190476191'
+ },
+ {
+ 'time': '2017-08-27T18:42:51.462Z',
+ 'value': '0.6952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:43:51.462Z',
+ 'value': '0.857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:44:51.462Z',
+ 'value': '0.9238095238095239'
+ },
+ {
+ 'time': '2017-08-27T18:45:51.462Z',
+ 'value': '0.7428571428571429'
+ },
+ {
+ 'time': '2017-08-27T18:46:51.462Z',
+ 'value': '0.8857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:47:51.462Z',
+ 'value': '0.638095238095238'
+ },
+ {
+ 'time': '2017-08-27T18:48:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:49:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:50:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:51:51.462Z',
+ 'value': '0.47619501138106085'
+ },
+ {
+ 'time': '2017-08-27T18:52:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:53:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:54:51.462Z',
+ 'value': '0.4952380952380952'
+ },
+ {
+ 'time': '2017-08-27T18:55:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:56:51.462Z',
+ 'value': '0.4857142857142857'
+ },
+ {
+ 'time': '2017-08-27T18:57:51.462Z',
+ 'value': '0.47619047619047616'
+ },
+ {
+ 'time': '2017-08-27T18:58:51.462Z',
+ 'value': '0.6857142857142856'
+ },
+ {
+ 'time': '2017-08-27T18:59:51.462Z',
+ 'value': '0.6952380952380952'
+ },
+ {
+ 'time': '2017-08-27T19:00:51.462Z',
+ 'value': '0.5238095238095237'
+ },
+ {
+ 'time': '2017-08-27T19:01:51.462Z',
+ 'value': '0.5904761904761905'
+ }
+ ]
+ }
+ ]
+ }
]
- }
- ]
- }
+ }
];
+export function convertDatesMultipleSeries(multipleSeries) {
+ const convertedMultiple = multipleSeries;
+ multipleSeries.forEach((column, index) => {
+ let convertedResult = [];
+ convertedResult = column.queries[0].result.map((resultObj) => {
+ const convertedMetrics = {};
+ convertedMetrics.values = resultObj.values.map(val => ({
+ time: new Date(val.time),
+ value: val.value,
+ }));
+ convertedMetrics.metric = resultObj.metric;
+ return convertedMetrics;
+ });
+ convertedMultiple[index].queries[0].result = convertedResult;
+ });
+ return convertedMultiple;
+}
+
export function MonitorMockInterceptor(request, next) {
const body = responseMockData[request.method.toUpperCase()][request.url];
diff --git a/spec/javascripts/monitoring/monitoring_paths_spec.js b/spec/javascripts/monitoring/monitoring_paths_spec.js
new file mode 100644
index 00000000000..d39db945e17
--- /dev/null
+++ b/spec/javascripts/monitoring/monitoring_paths_spec.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import MonitoringPaths from '~/monitoring/components/monitoring_paths.vue';
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(MonitoringPaths);
+
+ return new Component({
+ propsData,
+ }).$mount();
+};
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
+
+describe('Monitoring Paths', () => {
+ it('renders two paths to represent a line and the area underneath it', () => {
+ const component = createComponent({
+ generatedLinePath: timeSeries[0].linePath,
+ generatedAreaPath: timeSeries[0].areaPath,
+ lineColor: '#ccc',
+ areaColor: '#fff',
+ });
+ const metricArea = component.$el.querySelector('.metric-area');
+ const metricLine = component.$el.querySelector('.metric-line');
+
+ expect(metricArea.getAttribute('fill')).toBe('#fff');
+ expect(metricArea.getAttribute('d')).toBe(timeSeries[0].areaPath);
+ expect(metricLine.getAttribute('stroke')).toBe('#ccc');
+ expect(metricLine.getAttribute('d')).toBe(timeSeries[0].linePath);
+ });
+});
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
index 20c1e6a0005..88aa7659275 100644
--- a/spec/javascripts/monitoring/monitoring_store_spec.js
+++ b/spec/javascripts/monitoring/monitoring_store_spec.js
@@ -5,10 +5,10 @@ describe('MonitoringStore', () => {
this.store = new MonitoringStore();
this.store.storeMetrics(MonitoringMock.data);
- it('contains one group that contains two queries sorted by priority in one row', () => {
+ it('contains one group that contains two queries sorted by priority', () => {
expect(this.store.groups).toBeDefined();
expect(this.store.groups.length).toEqual(1);
- expect(this.store.groups[0].metrics.length).toEqual(1);
+ expect(this.store.groups[0].metrics.length).toEqual(2);
});
it('gets the metrics count for every group', () => {
diff --git a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js
new file mode 100644
index 00000000000..3daf6bf82df
--- /dev/null
+++ b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js
@@ -0,0 +1,21 @@
+import createTimeSeries from '~/monitoring/utils/multiple_time_series';
+import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
+
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
+const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
+
+describe('Multiple time series', () => {
+ it('createTimeSeries returned array contains an object for each element', () => {
+ expect(typeof timeSeries[0].linePath).toEqual('string');
+ expect(typeof timeSeries[0].areaPath).toEqual('string');
+ expect(typeof timeSeries[0].timeSeriesScaleX).toEqual('function');
+ expect(typeof timeSeries[0].areaColor).toEqual('string');
+ expect(typeof timeSeries[0].lineColor).toEqual('string');
+ expect(timeSeries[0].values instanceof Array).toEqual(true);
+ });
+
+ it('createTimeSeries returns an array', () => {
+ expect(timeSeries instanceof Array).toEqual(true);
+ expect(timeSeries.length).toEqual(2);
+ });
+});
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
deleted file mode 100644
index 3d36bb3e4d4..00000000000
--- a/spec/javascripts/project_title_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/* global Project */
-
-import 'select2/select2';
-import '~/gl_dropdown';
-import '~/api';
-import '~/project_select';
-import '~/project';
-
-describe('Project Title', () => {
- const dummyApiVersion = 'v3000';
- preloadFixtures('issues/open-issue.html.raw');
- loadJSONFixtures('projects.json');
-
- beforeEach(() => {
- loadFixtures('issues/open-issue.html.raw');
-
- window.gon = {};
- window.gon.api_version = dummyApiVersion;
-
- // eslint-disable-next-line no-new
- new Project();
- });
-
- describe('project list', () => {
- let reqUrl;
- let reqData;
-
- beforeEach(() => {
- const fakeResponseData = getJSONFixture('projects.json');
- spyOn(jQuery, 'ajax').and.callFake((req) => {
- const def = $.Deferred();
- reqUrl = req.url;
- reqData = req.data;
- def.resolve(fakeResponseData);
- return def.promise();
- });
- });
-
- it('toggles dropdown', () => {
- const $menu = $('.js-dropdown-menu-projects');
- window.gon.current_user_id = 1;
- $('.js-projects-dropdown-toggle').click();
- expect($menu).toHaveClass('open');
- expect(reqUrl).toBe(`/api/${dummyApiVersion}/projects.json?simple=true`);
- expect(reqData).toEqual({
- search: '',
- order_by: 'last_activity_at',
- per_page: 20,
- membership: true,
- });
- $menu.find('.dropdown-menu-close-icon').click();
- expect($menu).not.toHaveClass('open');
- });
- });
-
- afterEach(() => {
- window.gon = {};
- });
-});
diff --git a/spec/javascripts/projects_dropdown/components/app_spec.js b/spec/javascripts/projects_dropdown/components/app_spec.js
new file mode 100644
index 00000000000..42f0f6fc1af
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/app_spec.js
@@ -0,0 +1,348 @@
+import Vue from 'vue';
+
+import bp from '~/breakpoints';
+import appComponent from '~/projects_dropdown/components/app.vue';
+import eventHub from '~/projects_dropdown/event_hub';
+import ProjectsStore from '~/projects_dropdown/store/projects_store';
+import ProjectsService from '~/projects_dropdown/service/projects_service';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { currentSession, mockProject, mockRawProject } from '../mock_data';
+
+const createComponent = () => {
+ gon.api_version = currentSession.apiVersion;
+ const Component = Vue.extend(appComponent);
+ const store = new ProjectsStore();
+ const service = new ProjectsService(currentSession.username);
+
+ return mountComponent(Component, {
+ store,
+ service,
+ currentUserName: currentSession.username,
+ currentProject: currentSession.project,
+ });
+};
+
+const returnServicePromise = (data, failed) => new Promise((resolve, reject) => {
+ if (failed) {
+ reject(data);
+ } else {
+ resolve({
+ json() {
+ return data;
+ },
+ });
+ }
+});
+
+describe('AppComponent', () => {
+ describe('computed', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('frequentProjects', () => {
+ it('should return list of frequently accessed projects from store', () => {
+ expect(vm.frequentProjects).toBeDefined();
+ expect(vm.frequentProjects.length).toBe(0);
+
+ vm.store.setFrequentProjects([mockProject]);
+ expect(vm.frequentProjects).toBeDefined();
+ expect(vm.frequentProjects.length).toBe(1);
+ });
+ });
+
+ describe('searchProjects', () => {
+ it('should return list of frequently accessed projects from store', () => {
+ expect(vm.searchProjects).toBeDefined();
+ expect(vm.searchProjects.length).toBe(0);
+
+ vm.store.setSearchedProjects([mockRawProject]);
+ expect(vm.searchProjects).toBeDefined();
+ expect(vm.searchProjects.length).toBe(1);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('toggleFrequentProjectsList', () => {
+ it('should toggle props which control visibility of Frequent Projects list from state passed', () => {
+ vm.toggleFrequentProjectsList(true);
+ expect(vm.isLoadingProjects).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+
+ vm.toggleFrequentProjectsList(false);
+ expect(vm.isLoadingProjects).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ });
+ });
+
+ describe('toggleSearchProjectsList', () => {
+ it('should toggle props which control visibility of Searched Projects list from state passed', () => {
+ vm.toggleSearchProjectsList(true);
+ expect(vm.isLoadingProjects).toBeFalsy();
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+
+ vm.toggleSearchProjectsList(false);
+ expect(vm.isLoadingProjects).toBeTruthy();
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ });
+ });
+
+ describe('toggleLoader', () => {
+ it('should toggle props which control visibility of list loading animation from state passed', () => {
+ vm.toggleLoader(true);
+ expect(vm.isFrequentsListVisible).toBeFalsy();
+ expect(vm.isSearchListVisible).toBeFalsy();
+ expect(vm.isLoadingProjects).toBeTruthy();
+
+ vm.toggleLoader(false);
+ expect(vm.isFrequentsListVisible).toBeTruthy();
+ expect(vm.isSearchListVisible).toBeTruthy();
+ expect(vm.isLoadingProjects).toBeFalsy();
+ });
+ });
+
+ describe('fetchFrequentProjects', () => {
+ it('should set props for loading animation to `true` while frequent projects list is being loaded', () => {
+ spyOn(vm, 'toggleLoader');
+
+ vm.fetchFrequentProjects();
+ expect(vm.isLocalStorageFailed).toBeFalsy();
+ expect(vm.toggleLoader).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for loading animation to `false` and props for frequent projects list to `true` once data is loaded', () => {
+ const mockData = [mockProject];
+
+ spyOn(vm.service, 'getFrequentProjects').and.returnValue(mockData);
+ spyOn(vm.store, 'setFrequentProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.store.setFrequentProjects).toHaveBeenCalledWith(mockData);
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for failure message to `true` when method fails to fetch frequent projects list', () => {
+ spyOn(vm.service, 'getFrequentProjects').and.returnValue(null);
+ spyOn(vm.store, 'setFrequentProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ expect(vm.isLocalStorageFailed).toBeFalsy();
+
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.store.setFrequentProjects).toHaveBeenCalledWith([]);
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.isLocalStorageFailed).toBeTruthy();
+ });
+
+ it('should set props for search results list to `true` if search query was already made previously', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('md');
+ spyOn(vm.service, 'getFrequentProjects');
+ spyOn(vm, 'toggleSearchProjectsList');
+
+ vm.searchQuery = 'test';
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).not.toHaveBeenCalled();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ });
+
+ it('should set props for frequent projects list to `true` if search query was already made but screen size is less than 768px', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getFrequentProjects');
+
+ vm.searchQuery = 'test';
+ vm.fetchFrequentProjects();
+ expect(vm.service.getFrequentProjects).toHaveBeenCalled();
+ expect(vm.toggleSearchProjectsList).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('fetchSearchedProjects', () => {
+ const searchQuery = 'test';
+
+ it('should perform search with provided search query', (done) => {
+ const mockData = [mockRawProject];
+ spyOn(vm, 'toggleLoader');
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getSearchedProjects').and.returnValue(returnServicePromise(mockData));
+ spyOn(vm.store, 'setSearchedProjects');
+
+ vm.fetchSearchedProjects(searchQuery);
+ setTimeout(() => {
+ expect(vm.searchQuery).toBe(searchQuery);
+ expect(vm.toggleLoader).toHaveBeenCalledWith(true);
+ expect(vm.service.getSearchedProjects).toHaveBeenCalledWith(searchQuery);
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.store.setSearchedProjects).toHaveBeenCalledWith(mockData);
+ done();
+ }, 0);
+ });
+
+ it('should update props for showing search failure', (done) => {
+ spyOn(vm, 'toggleSearchProjectsList');
+ spyOn(vm.service, 'getSearchedProjects').and.returnValue(returnServicePromise({}, true));
+
+ vm.fetchSearchedProjects(searchQuery);
+ setTimeout(() => {
+ expect(vm.searchQuery).toBe(searchQuery);
+ expect(vm.service.getSearchedProjects).toHaveBeenCalledWith(searchQuery);
+ expect(vm.isSearchFailed).toBeTruthy();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ done();
+ }, 0);
+ });
+ });
+
+ describe('logCurrentProjectAccess', () => {
+ it('should log current project access via service', (done) => {
+ spyOn(vm.service, 'logProjectAccess');
+
+ vm.currentProject = mockProject;
+ vm.logCurrentProjectAccess();
+
+ setTimeout(() => {
+ expect(vm.service.logProjectAccess).toHaveBeenCalledWith(mockProject);
+ done();
+ }, 1);
+ });
+ });
+
+ describe('handleSearchClear', () => {
+ it('should show frequent projects list when search input is cleared', () => {
+ spyOn(vm.store, 'clearSearchedProjects');
+ spyOn(vm, 'toggleFrequentProjectsList');
+
+ vm.handleSearchClear();
+
+ expect(vm.toggleFrequentProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.store.clearSearchedProjects).toHaveBeenCalled();
+ expect(vm.searchQuery).toBe('');
+ });
+ });
+
+ describe('handleSearchFailure', () => {
+ it('should show failure message within dropdown', () => {
+ spyOn(vm, 'toggleSearchProjectsList');
+
+ vm.handleSearchFailure();
+ expect(vm.toggleSearchProjectsList).toHaveBeenCalledWith(true);
+ expect(vm.isSearchFailed).toBeTruthy();
+ });
+ });
+ });
+
+ describe('created', () => {
+ it('should bind event listeners on eventHub', (done) => {
+ spyOn(eventHub, '$on');
+
+ createComponent().$mount();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$on).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchProjects', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchCleared', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('searchFailed', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('beforeDestroy', () => {
+ it('should unbind event listeners on eventHub', (done) => {
+ const vm = createComponent();
+ spyOn(eventHub, '$off');
+
+ vm.$mount();
+ vm.$destroy();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$off).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchProjects', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchCleared', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('searchFailed', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render search input', () => {
+ expect(vm.$el.querySelector('.search-input-container')).toBeDefined();
+ });
+
+ it('should render loading animation', (done) => {
+ vm.toggleLoader(true);
+ Vue.nextTick(() => {
+ const loadingEl = vm.$el.querySelector('.loading-animation');
+
+ expect(loadingEl).toBeDefined();
+ expect(loadingEl.classList.contains('prepend-top-20')).toBeTruthy();
+ expect(loadingEl.querySelector('i').getAttribute('aria-label')).toBe('Loading projects');
+ done();
+ });
+ });
+
+ it('should render frequent projects list header', (done) => {
+ vm.toggleFrequentProjectsList(true);
+ Vue.nextTick(() => {
+ const sectionHeaderEl = vm.$el.querySelector('.section-header');
+
+ expect(sectionHeaderEl).toBeDefined();
+ expect(sectionHeaderEl.innerText.trim()).toBe('Frequently visited');
+ done();
+ });
+ });
+
+ it('should render frequent projects list', (done) => {
+ vm.toggleFrequentProjectsList(true);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.projects-list-frequent-container')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should render searched projects list', (done) => {
+ vm.toggleSearchProjectsList(true);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.section-header')).toBe(null);
+ expect(vm.$el.querySelector('.projects-list-search-container')).toBeDefined();
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js
new file mode 100644
index 00000000000..fcd0f6a3630
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_frequent_spec.js
@@ -0,0 +1,72 @@
+import Vue from 'vue';
+
+import projectsListFrequentComponent from '~/projects_dropdown/components/projects_list_frequent.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockFrequents } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListFrequentComponent);
+
+ return mountComponent(Component, {
+ projects: mockFrequents,
+ localStorageFailed: false,
+ });
+};
+
+describe('ProjectsListFrequentComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isListEmpty', () => {
+ it('should return `true` or `false` representing whether if `projects` is empty of not', () => {
+ vm.projects = [];
+ expect(vm.isListEmpty).toBeTruthy();
+
+ vm.projects = mockFrequents;
+ expect(vm.isListEmpty).toBeFalsy();
+ });
+ });
+
+ describe('listEmptyMessage', () => {
+ it('should return appropriate empty list message based on value of `localStorageFailed` prop', () => {
+ vm.localStorageFailed = true;
+ expect(vm.listEmptyMessage).toBe('This feature requires browser localStorage support');
+
+ vm.localStorageFailed = false;
+ expect(vm.listEmptyMessage).toBe('Projects you visit often will appear here');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element with list of projects', (done) => {
+ vm.projects = mockFrequents;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.classList.contains('projects-list-frequent-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('ul.list-unstyled').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(5);
+ done();
+ });
+ });
+
+ it('should render component element with empty message', (done) => {
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
new file mode 100644
index 00000000000..171629fcd6b
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+
+import projectsListItemComponent from '~/projects_dropdown/components/projects_list_item.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockProject } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListItemComponent);
+
+ return mountComponent(Component, {
+ projectId: mockProject.id,
+ projectName: mockProject.name,
+ namespace: mockProject.namespace,
+ webUrl: mockProject.webUrl,
+ avatarUrl: mockProject.avatarUrl,
+ });
+};
+
+describe('ProjectsListItemComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('hasAvatar', () => {
+ it('should return `true` or `false` if whether avatar is present or not', () => {
+ vm.avatarUrl = 'path/to/avatar.png';
+ expect(vm.hasAvatar).toBeTruthy();
+
+ vm.avatarUrl = null;
+ expect(vm.hasAvatar).toBeFalsy();
+ });
+ });
+
+ describe('highlightedProjectName', () => {
+ it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => {
+ vm.matcher = 'lab';
+ expect(vm.highlightedProjectName).toContain('<b>Lab</b>');
+ });
+
+ it('should return project name as it is if `matcher` is not available', () => {
+ vm.matcher = null;
+ expect(vm.highlightedProjectName).toBe(mockProject.name);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element', () => {
+ expect(vm.$el.classList.contains('projects-list-item-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('a').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-item-avatar-container').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-item-metadata-container').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-title').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.project-namespace').length).toBe(1);
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js
new file mode 100644
index 00000000000..59fc2dedba5
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/projects_list_search_spec.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+
+import projectsListSearchComponent from '~/projects_dropdown/components/projects_list_search.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { mockProject } from '../mock_data';
+
+const createComponent = () => {
+ const Component = Vue.extend(projectsListSearchComponent);
+
+ return mountComponent(Component, {
+ projects: [mockProject],
+ matcher: 'lab',
+ searchFailed: false,
+ });
+};
+
+describe('ProjectsListSearchComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isListEmpty', () => {
+ it('should return `true` or `false` representing whether if `projects` is empty of not', () => {
+ vm.projects = [];
+ expect(vm.isListEmpty).toBeTruthy();
+
+ vm.projects = [mockProject];
+ expect(vm.isListEmpty).toBeFalsy();
+ });
+ });
+
+ describe('listEmptyMessage', () => {
+ it('should return appropriate empty list message based on value of `searchFailed` prop', () => {
+ vm.searchFailed = true;
+ expect(vm.listEmptyMessage).toBe('Something went wrong on our end.');
+
+ vm.searchFailed = false;
+ expect(vm.listEmptyMessage).toBe('No projects matched your query');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render component element with list of projects', (done) => {
+ vm.projects = [mockProject];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.classList.contains('projects-list-search-container')).toBeTruthy();
+ expect(vm.$el.querySelectorAll('ul.list-unstyled').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(1);
+ done();
+ });
+ });
+
+ it('should render component element with empty message', (done) => {
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+
+ it('should render component element with failure message', (done) => {
+ vm.searchFailed = true;
+ vm.projects = [];
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelectorAll('li.section-empty.section-failure').length).toBe(1);
+ expect(vm.$el.querySelectorAll('li.projects-list-item-container').length).toBe(0);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/search_spec.js b/spec/javascripts/projects_dropdown/components/search_spec.js
new file mode 100644
index 00000000000..f2a23e33325
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/components/search_spec.js
@@ -0,0 +1,101 @@
+import Vue from 'vue';
+
+import searchComponent from '~/projects_dropdown/components/search.vue';
+import eventHub from '~/projects_dropdown/event_hub';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(searchComponent);
+
+ return mountComponent(Component);
+};
+
+describe('SearchComponent', () => {
+ describe('methods', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('setFocus', () => {
+ it('should set focus to search input', () => {
+ spyOn(vm.$refs.search, 'focus');
+
+ vm.setFocus();
+ expect(vm.$refs.search.focus).toHaveBeenCalled();
+ });
+ });
+
+ describe('emitSearchEvents', () => {
+ it('should emit `searchProjects` event via eventHub when `searchQuery` present', () => {
+ const searchQuery = 'test';
+ spyOn(eventHub, '$emit');
+ vm.searchQuery = searchQuery;
+ vm.emitSearchEvents();
+ expect(eventHub.$emit).toHaveBeenCalledWith('searchProjects', searchQuery);
+ });
+
+ it('should emit `searchCleared` event via eventHub when `searchQuery` is cleared', () => {
+ spyOn(eventHub, '$emit');
+ vm.searchQuery = '';
+ vm.emitSearchEvents();
+ expect(eventHub.$emit).toHaveBeenCalledWith('searchCleared');
+ });
+ });
+ });
+
+ describe('mounted', () => {
+ it('should listen `dropdownOpen` event', (done) => {
+ spyOn(eventHub, '$on');
+ createComponent();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$on).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('beforeDestroy', () => {
+ it('should unbind event listeners on eventHub', (done) => {
+ const vm = createComponent();
+ spyOn(eventHub, '$off');
+
+ vm.$mount();
+ vm.$destroy();
+
+ Vue.nextTick(() => {
+ expect(eventHub.$off).toHaveBeenCalledWith('dropdownOpen', jasmine.any(Function));
+ done();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render component element', () => {
+ const inputEl = vm.$el.querySelector('input.form-control');
+
+ expect(vm.$el.classList.contains('search-input-container')).toBeTruthy();
+ expect(vm.$el.classList.contains('hidden-xs')).toBeTruthy();
+ expect(inputEl).not.toBe(null);
+ expect(inputEl.getAttribute('placeholder')).toBe('Search projects');
+ expect(vm.$el.querySelector('.search-icon')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/mock_data.js b/spec/javascripts/projects_dropdown/mock_data.js
new file mode 100644
index 00000000000..d6a79fb8ac1
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/mock_data.js
@@ -0,0 +1,96 @@
+export const currentSession = {
+ username: 'root',
+ storageKey: 'root/frequent-projects',
+ apiVersion: 'v4',
+ project: {
+ id: 1,
+ name: 'dummy-project',
+ namespace: 'SamepleGroup / Dummy-Project',
+ webUrl: 'http://127.0.0.1/samplegroup/dummy-project',
+ avatarUrl: null,
+ lastAccessedOn: Date.now(),
+ },
+};
+
+export const mockProject = {
+ id: 1,
+ name: 'GitLab Community Edition',
+ namespace: 'gitlab-org / gitlab-ce',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatarUrl: null,
+};
+
+export const mockRawProject = {
+ id: 1,
+ name: 'GitLab Community Edition',
+ name_with_namespace: 'gitlab-org / gitlab-ce',
+ web_url: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatar_url: null,
+};
+
+export const mockFrequents = [
+ {
+ id: 1,
+ name: 'GitLab Community Edition',
+ namespace: 'gitlab-org / gitlab-ce',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ce',
+ avatarUrl: null,
+ },
+ {
+ id: 2,
+ name: 'GitLab CI',
+ namespace: 'gitlab-org / gitlab-ci',
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-ci',
+ avatarUrl: null,
+ },
+ {
+ id: 3,
+ name: 'Typeahead.Js',
+ namespace: 'twitter / typeahead-js',
+ webUrl: 'http://127.0.0.1:3000/twitter/typeahead-js',
+ avatarUrl: '/uploads/-/system/project/avatar/7/TWBS.png',
+ },
+ {
+ id: 4,
+ name: 'Intel',
+ namespace: 'platform / hardware / bsp / intel',
+ webUrl: 'http://127.0.0.1:3000/platform/hardware/bsp/intel',
+ avatarUrl: null,
+ },
+ {
+ id: 5,
+ name: 'v4.4',
+ namespace: 'platform / hardware / bsp / kernel / common / v4.4',
+ webUrl: 'http://localhost:3000/platform/hardware/bsp/kernel/common/v4.4',
+ avatarUrl: null,
+ },
+];
+
+export const unsortedFrequents = [
+ { id: 1, frequency: 12, lastAccessedOn: 1491400843391 },
+ { id: 2, frequency: 14, lastAccessedOn: 1488240890738 },
+ { id: 3, frequency: 44, lastAccessedOn: 1497675908472 },
+ { id: 4, frequency: 8, lastAccessedOn: 1497979281815 },
+ { id: 5, frequency: 34, lastAccessedOn: 1488089211943 },
+ { id: 6, frequency: 14, lastAccessedOn: 1493517292488 },
+ { id: 7, frequency: 42, lastAccessedOn: 1486815299875 },
+ { id: 8, frequency: 33, lastAccessedOn: 1500762279114 },
+ { id: 10, frequency: 46, lastAccessedOn: 1483251641543 },
+];
+
+/**
+ * This const has a specific order which tests authenticity
+ * of `ProjectsService.getTopFrequentProjects` method so
+ * DO NOT change order of items in this const.
+ */
+export const sortedFrequents = [
+ { id: 10, frequency: 46, lastAccessedOn: 1483251641543 },
+ { id: 3, frequency: 44, lastAccessedOn: 1497675908472 },
+ { id: 7, frequency: 42, lastAccessedOn: 1486815299875 },
+ { id: 5, frequency: 34, lastAccessedOn: 1488089211943 },
+ { id: 8, frequency: 33, lastAccessedOn: 1500762279114 },
+ { id: 6, frequency: 14, lastAccessedOn: 1493517292488 },
+ { id: 2, frequency: 14, lastAccessedOn: 1488240890738 },
+ { id: 1, frequency: 12, lastAccessedOn: 1491400843391 },
+ { id: 4, frequency: 8, lastAccessedOn: 1497979281815 },
+];
diff --git a/spec/javascripts/projects_dropdown/service/projects_service_spec.js b/spec/javascripts/projects_dropdown/service/projects_service_spec.js
new file mode 100644
index 00000000000..d5dd8b3449a
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/service/projects_service_spec.js
@@ -0,0 +1,179 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+import bp from '~/breakpoints';
+import ProjectsService from '~/projects_dropdown/service/projects_service';
+import { FREQUENT_PROJECTS } from '~/projects_dropdown/constants';
+import { currentSession, unsortedFrequents, sortedFrequents } from '../mock_data';
+
+Vue.use(VueResource);
+
+FREQUENT_PROJECTS.MAX_COUNT = 3;
+
+describe('ProjectsService', () => {
+ let service;
+
+ beforeEach(() => {
+ gon.api_version = currentSession.apiVersion;
+ gon.current_user_id = 1;
+ service = new ProjectsService(currentSession.username);
+ });
+
+ describe('contructor', () => {
+ it('should initialize default properties of class', () => {
+ expect(service.isLocalStorageAvailable).toBeTruthy();
+ expect(service.currentUserName).toBe(currentSession.username);
+ expect(service.storageKey).toBe(currentSession.storageKey);
+ expect(service.projectsPath).toBeDefined();
+ });
+ });
+
+ describe('getSearchedProjects', () => {
+ it('should return promise from VueResource HTTP GET', () => {
+ spyOn(service.projectsPath, 'get').and.stub();
+
+ const searchQuery = 'lab';
+ const queryParams = {
+ simple: false,
+ per_page: 20,
+ membership: true,
+ order_by: 'last_activity_at',
+ search: searchQuery,
+ };
+
+ service.getSearchedProjects(searchQuery);
+ expect(service.projectsPath.get).toHaveBeenCalledWith(queryParams);
+ });
+ });
+
+ describe('logProjectAccess', () => {
+ let storage;
+
+ beforeEach(() => {
+ storage = {};
+
+ spyOn(window.localStorage, 'setItem').and.callFake((storageKey, value) => {
+ storage[storageKey] = value;
+ });
+
+ spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
+ if (storage[storageKey]) {
+ return storage[storageKey];
+ }
+
+ return null;
+ });
+ });
+
+ it('should create a project store if it does not exist and adds a project', () => {
+ service.logProjectAccess(currentSession.project);
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(1);
+ expect(projects[0].frequency).toBe(1);
+ expect(projects[0].lastAccessedOn).toBeDefined();
+ });
+
+ it('should prevent inserting same report multiple times into store', () => {
+ service.logProjectAccess(currentSession.project);
+ service.logProjectAccess(currentSession.project);
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(1);
+ });
+
+ it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
+ let projects;
+ spyOn(Math, 'abs').and.returnValue(3600001); // this will lead to `diff` > 1;
+ service.logProjectAccess(currentSession.project);
+
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].frequency).toBe(1);
+
+ service.logProjectAccess(currentSession.project);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].frequency).toBe(2);
+ expect(projects[0].lastAccessedOn).not.toBe(currentSession.project.lastAccessedOn);
+ });
+
+ it('should always update project metadata', () => {
+ let projects;
+ const oldProject = {
+ ...currentSession.project,
+ };
+
+ const newProject = {
+ ...currentSession.project,
+ name: 'New Name',
+ avatarUrl: 'new/avatar.png',
+ namespace: 'New / Namespace',
+ webUrl: 'http://localhost/new/web/url',
+ };
+
+ service.logProjectAccess(oldProject);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].name).toBe(oldProject.name);
+ expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl);
+ expect(projects[0].namespace).toBe(oldProject.namespace);
+ expect(projects[0].webUrl).toBe(oldProject.webUrl);
+
+ service.logProjectAccess(newProject);
+ projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects[0].name).toBe(newProject.name);
+ expect(projects[0].avatarUrl).toBe(newProject.avatarUrl);
+ expect(projects[0].namespace).toBe(newProject.namespace);
+ expect(projects[0].webUrl).toBe(newProject.webUrl);
+ });
+
+ it('should not add more than 20 projects in store', () => {
+ for (let i = 1; i <= 5; i += 1) {
+ const project = Object.assign(currentSession.project, { id: i });
+ service.logProjectAccess(project);
+ }
+
+ const projects = JSON.parse(storage[currentSession.storageKey]);
+ expect(projects.length).toBe(3);
+ });
+ });
+
+ describe('getTopFrequentProjects', () => {
+ let storage = {};
+
+ beforeEach(() => {
+ storage[currentSession.storageKey] = JSON.stringify(unsortedFrequents);
+
+ spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
+ if (storage[storageKey]) {
+ return storage[storageKey];
+ }
+
+ return null;
+ });
+ });
+
+ it('should return top 5 frequently accessed projects for desktop screens', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('md');
+ const frequentProjects = service.getTopFrequentProjects();
+
+ expect(frequentProjects.length).toBe(5);
+ frequentProjects.forEach((project, index) => {
+ expect(project.id).toBe(sortedFrequents[index].id);
+ });
+ });
+
+ it('should return top 3 frequently accessed projects for mobile screens', () => {
+ spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
+ const frequentProjects = service.getTopFrequentProjects();
+
+ expect(frequentProjects.length).toBe(3);
+ frequentProjects.forEach((project, index) => {
+ expect(project.id).toBe(sortedFrequents[index].id);
+ });
+ });
+
+ it('should return empty array if there are no projects available in store', () => {
+ storage = {};
+ expect(service.getTopFrequentProjects().length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/store/projects_store_spec.js b/spec/javascripts/projects_dropdown/store/projects_store_spec.js
new file mode 100644
index 00000000000..e57399d37cd
--- /dev/null
+++ b/spec/javascripts/projects_dropdown/store/projects_store_spec.js
@@ -0,0 +1,41 @@
+import ProjectsStore from '~/projects_dropdown/store/projects_store';
+import { mockProject, mockRawProject } from '../mock_data';
+
+describe('ProjectsStore', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new ProjectsStore();
+ });
+
+ describe('setFrequentProjects', () => {
+ it('should set frequent projects list to state', () => {
+ store.setFrequentProjects([mockProject]);
+
+ expect(store.getFrequentProjects().length).toBe(1);
+ expect(store.getFrequentProjects()[0].id).toBe(mockProject.id);
+ });
+ });
+
+ describe('setSearchedProjects', () => {
+ it('should set searched projects list to state', () => {
+ store.setSearchedProjects([mockRawProject]);
+
+ const processedProjects = store.getSearchedProjects();
+ expect(processedProjects.length).toBe(1);
+ expect(processedProjects[0].id).toBe(mockRawProject.id);
+ expect(processedProjects[0].namespace).toBe(mockRawProject.name_with_namespace);
+ expect(processedProjects[0].webUrl).toBe(mockRawProject.web_url);
+ expect(processedProjects[0].avatarUrl).toBe(mockRawProject.avatar_url);
+ });
+ });
+
+ describe('clearSearchedProjects', () => {
+ it('should clear searched projects list from state', () => {
+ store.setSearchedProjects([mockRawProject]);
+ expect(store.getSearchedProjects().length).toBe(1);
+ store.clearSearchedProjects();
+ expect(store.getSearchedProjects().length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index 9fc8667ecc9..e2b6bcabc98 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -66,17 +66,57 @@ const sidebarMockData = {
},
labels: [],
},
+ '/autocomplete/projects?project_id=15': [
+ {
+ 'id': 0,
+ 'name_with_namespace': 'No project',
+ }, {
+ 'id': 20,
+ 'name_with_namespace': 'foo / bar',
+ },
+ ],
},
'PUT': {
'/gitlab-org/gitlab-shell/issues/5.json': {
data: {},
},
},
+ 'POST': {
+ '/gitlab-org/gitlab-shell/issues/5/move': {
+ id: 123,
+ iid: 5,
+ author_id: 1,
+ description: 'some description',
+ lock_version: 5,
+ milestone_id: null,
+ state: 'opened',
+ title: 'some title',
+ updated_by_id: 1,
+ created_at: '2017-06-27T19:54:42.437Z',
+ updated_at: '2017-08-18T03:39:49.222Z',
+ deleted_at: null,
+ time_estimate: 0,
+ total_time_spent: 0,
+ human_time_estimate: null,
+ human_total_time_spent: null,
+ branch_name: null,
+ confidential: false,
+ assignees: [],
+ due_date: null,
+ moved_to_id: null,
+ project_id: 7,
+ milestone: null,
+ labels: [],
+ web_url: '/root/some-project/issues/5',
+ },
+ },
};
export default {
mediator: {
endpoint: '/gitlab-org/gitlab-shell/issues/5.json',
+ moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
+ projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
editable: true,
currentUser: {
id: 1,
@@ -85,6 +125,7 @@ export default {
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
},
rootPath: '/',
+ fullPath: '/gitlab-org/gitlab-shell',
},
time: {
time_estimate: 3600,
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index e246f41ee82..3aa8ca5db0d 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -30,7 +30,7 @@ describe('Sidebar mediator', () => {
expect(resp.status).toEqual(200);
done();
})
- .catch(() => {});
+ .catch(done.fail);
});
it('fetches the data', () => {
@@ -38,4 +38,42 @@ describe('Sidebar mediator', () => {
this.mediator.fetch();
expect(this.mediator.service.get).toHaveBeenCalled();
});
+
+ it('sets moveToProjectId', () => {
+ const projectId = 7;
+ spyOn(this.mediator.store, 'setMoveToProjectId').and.callThrough();
+
+ this.mediator.setMoveToProjectId(projectId);
+
+ expect(this.mediator.store.setMoveToProjectId).toHaveBeenCalledWith(projectId);
+ });
+
+ it('fetches autocomplete projects', (done) => {
+ const searchTerm = 'foo';
+ spyOn(this.mediator.service, 'getProjectsAutocomplete').and.callThrough();
+ spyOn(this.mediator.store, 'setAutocompleteProjects').and.callThrough();
+
+ this.mediator.fetchAutocompleteProjects(searchTerm)
+ .then(() => {
+ expect(this.mediator.service.getProjectsAutocomplete).toHaveBeenCalledWith(searchTerm);
+ expect(this.mediator.store.setAutocompleteProjects).toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('moves issue', (done) => {
+ const moveToProjectId = 7;
+ this.mediator.store.setMoveToProjectId(moveToProjectId);
+ spyOn(this.mediator.service, 'moveIssue').and.callThrough();
+ spyOn(gl.utils, 'visitUrl');
+
+ this.mediator.moveIssue()
+ .then(() => {
+ expect(this.mediator.service.moveIssue).toHaveBeenCalledWith(moveToProjectId);
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5');
+ done();
+ })
+ .catch(done.fail);
+ });
});
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
new file mode 100644
index 00000000000..8b0d51bbcc8
--- /dev/null
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -0,0 +1,142 @@
+import Vue from 'vue';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
+import SidebarStore from '~/sidebar/stores/sidebar_store';
+import SidebarService from '~/sidebar/services/sidebar_service';
+import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
+import Mock from './mock_data';
+
+describe('SidebarMoveIssue', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
+ this.mediator = new SidebarMediator(Mock.mediator);
+ this.$content = $(`
+ <div class="dropdown">
+ <div class="js-toggle"></div>
+ <div class="dropdown-content"></div>
+ <div class="js-confirm-button"></div>
+ </div>
+ `);
+ this.$toggleButton = this.$content.find('.js-toggle');
+ this.$confirmButton = this.$content.find('.js-confirm-button');
+
+ this.sidebarMoveIssue = new SidebarMoveIssue(
+ this.mediator,
+ this.$toggleButton,
+ this.$confirmButton,
+ );
+ this.sidebarMoveIssue.init();
+ });
+
+ afterEach(() => {
+ SidebarService.singleton = null;
+ SidebarStore.singleton = null;
+ SidebarMediator.singleton = null;
+
+ this.sidebarMoveIssue.destroy();
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
+ });
+
+ describe('init', () => {
+ it('should initialize the dropdown and listeners', () => {
+ spyOn(this.sidebarMoveIssue, 'initDropdown');
+ spyOn(this.sidebarMoveIssue, 'addEventListeners');
+
+ this.sidebarMoveIssue.init();
+
+ expect(this.sidebarMoveIssue.initDropdown).toHaveBeenCalled();
+ expect(this.sidebarMoveIssue.addEventListeners).toHaveBeenCalled();
+ });
+ });
+
+ describe('destroy', () => {
+ it('should remove the listeners', () => {
+ spyOn(this.sidebarMoveIssue, 'removeEventListeners');
+
+ this.sidebarMoveIssue.destroy();
+
+ expect(this.sidebarMoveIssue.removeEventListeners).toHaveBeenCalled();
+ });
+ });
+
+ describe('initDropdown', () => {
+ it('should initialize the gl_dropdown', () => {
+ spyOn($.fn, 'glDropdown');
+
+ this.sidebarMoveIssue.initDropdown();
+
+ expect($.fn.glDropdown).toHaveBeenCalled();
+ });
+ });
+
+ describe('onConfirmClicked', () => {
+ it('should move the issue with valid project ID', () => {
+ spyOn(this.mediator, 'moveIssue').and.returnValue(Promise.resolve());
+ this.mediator.setMoveToProjectId(7);
+
+ this.sidebarMoveIssue.onConfirmClicked();
+
+ expect(this.mediator.moveIssue).toHaveBeenCalled();
+ expect(this.$confirmButton.attr('disabled')).toBe('disabled');
+ expect(this.$confirmButton.hasClass('is-loading')).toBe(true);
+ });
+
+ it('should remove loading state from confirm button on failure', (done) => {
+ spyOn(window, 'Flash');
+ spyOn(this.mediator, 'moveIssue').and.returnValue(Promise.reject());
+ this.mediator.setMoveToProjectId(7);
+
+ this.sidebarMoveIssue.onConfirmClicked();
+
+ expect(this.mediator.moveIssue).toHaveBeenCalled();
+ // Wait for the move issue request to fail
+ setTimeout(() => {
+ expect(window.Flash).toHaveBeenCalled();
+ expect(this.$confirmButton.attr('disabled')).toBe(undefined);
+ expect(this.$confirmButton.hasClass('is-loading')).toBe(false);
+ done();
+ });
+ });
+
+ it('should not move the issue with id=0', () => {
+ spyOn(this.mediator, 'moveIssue');
+ this.mediator.setMoveToProjectId(0);
+
+ this.sidebarMoveIssue.onConfirmClicked();
+
+ expect(this.mediator.moveIssue).not.toHaveBeenCalled();
+ });
+ });
+
+ it('should set moveToProjectId on dropdown item "No project" click', (done) => {
+ spyOn(this.mediator, 'setMoveToProjectId');
+
+ // Open the dropdown
+ this.$toggleButton.dropdown('toggle');
+
+ // Wait for the autocomplete request to finish
+ setTimeout(() => {
+ this.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
+
+ expect(this.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
+ expect(this.$confirmButton.attr('disabled')).toBe('disabled');
+ done();
+ }, 0);
+ });
+
+ it('should set moveToProjectId on dropdown item click', (done) => {
+ spyOn(this.mediator, 'setMoveToProjectId');
+
+ // Open the dropdown
+ this.$toggleButton.dropdown('toggle');
+
+ // Wait for the autocomplete request to finish
+ setTimeout(() => {
+ this.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
+
+ expect(this.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
+ expect(this.$confirmButton.attr('disabled')).toBe(undefined);
+ done();
+ }, 0);
+ });
+});
diff --git a/spec/javascripts/sidebar/sidebar_service_spec.js b/spec/javascripts/sidebar/sidebar_service_spec.js
index 91a4dd669a7..a4bd8ba8d88 100644
--- a/spec/javascripts/sidebar/sidebar_service_spec.js
+++ b/spec/javascripts/sidebar/sidebar_service_spec.js
@@ -5,7 +5,11 @@ import Mock from './mock_data';
describe('Sidebar service', () => {
beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
- this.service = new SidebarService('/gitlab-org/gitlab-shell/issues/5.json');
+ this.service = new SidebarService({
+ endpoint: '/gitlab-org/gitlab-shell/issues/5.json',
+ moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
+ projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
+ });
});
afterEach(() => {
@@ -19,7 +23,7 @@ describe('Sidebar service', () => {
expect(resp).toBeDefined();
done();
})
- .catch(() => {});
+ .catch(done.fail);
});
it('updates the data', (done) => {
@@ -28,6 +32,24 @@ describe('Sidebar service', () => {
expect(resp).toBeDefined();
done();
})
- .catch(() => {});
+ .catch(done.fail);
+ });
+
+ it('gets projects for autocomplete', (done) => {
+ this.service.getProjectsAutocomplete()
+ .then((resp) => {
+ expect(resp).toBeDefined();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('moves the issue to another project', (done) => {
+ this.service.moveIssue(123)
+ .then((resp) => {
+ expect(resp).toBeDefined();
+ done();
+ })
+ .catch(done.fail);
});
});
diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js
index b3fa156eb64..69eb3839d67 100644
--- a/spec/javascripts/sidebar/sidebar_store_spec.js
+++ b/spec/javascripts/sidebar/sidebar_store_spec.js
@@ -82,4 +82,18 @@ describe('Sidebar store', () => {
expect(this.store.humanTimeEstimate).toEqual(Mock.time.human_time_estimate);
expect(this.store.humanTotalTimeSpent).toEqual(Mock.time.human_total_time_spent);
});
+
+ it('set autocomplete projects', () => {
+ const projects = [{ id: 0 }];
+ this.store.setAutocompleteProjects(projects);
+
+ expect(this.store.autocompleteProjects).toEqual(projects);
+ });
+
+ it('set move to project ID', () => {
+ const projectId = 7;
+ this.store.setMoveToProjectId(projectId);
+
+ expect(this.store.moveToProjectId).toEqual(projectId);
+ });
});
diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/javascripts/vue_shared/components/identicon_spec.js
index 4f194e5a64e..647680f00f7 100644
--- a/spec/javascripts/vue_shared/components/identicon_spec.js
+++ b/spec/javascripts/vue_shared/components/identicon_spec.js
@@ -1,25 +1,30 @@
import Vue from 'vue';
import identiconComponent from '~/vue_shared/components/identicon.vue';
-const createComponent = () => {
+const createComponent = (sizeClass) => {
const Component = Vue.extend(identiconComponent);
return new Component({
propsData: {
entityId: 1,
entityName: 'entity-name',
+ sizeClass,
},
}).$mount();
};
describe('IdenticonComponent', () => {
- let vm;
+ describe('computed', () => {
+ let vm;
- beforeEach(() => {
- vm = createComponent();
- });
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
- describe('computed', () => {
describe('identiconStyles', () => {
it('should return styles attribute value with `background-color` property', () => {
vm.entityId = 4;
@@ -48,9 +53,20 @@ describe('IdenticonComponent', () => {
describe('template', () => {
it('should render identicon', () => {
+ const vm = createComponent();
+
expect(vm.$el.nodeName).toBe('DIV');
expect(vm.$el.classList.contains('identicon')).toBeTruthy();
+ expect(vm.$el.classList.contains('s40')).toBeTruthy();
expect(vm.$el.getAttribute('style').indexOf('background-color') > -1).toBeTruthy();
+ vm.$destroy();
+ });
+
+ it('should render identicon with provided sizing class', () => {
+ const vm = createComponent('s32');
+
+ expect(vm.$el.classList.contains('s32')).toBeTruthy();
+ vm.$destroy();
});
});
});
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
new file mode 100644
index 00000000000..049d025a5b9
--- /dev/null
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Banzai::CommitRenderer do
+ describe '.render' do
+ it 'renders a commit description and title' do
+ user = double(:user)
+ project = create(:project, :repository)
+
+ expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original
+
+ described_class::ATTRIBUTES.each do |attr|
+ expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr)
+ end
+
+ described_class.render([project.commit], project, user)
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index ff6b19459bb..85eddde732e 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -96,5 +96,41 @@ describe Banzai::Filter::TableOfContentsFilter do
expect(links.last.attr('href')).to eq '#header-2'
expect(links.last.text).to eq 'Header 2'
end
+
+ context 'table of contents nesting' do
+ let(:results) do
+ result(
+ header(1, 'Header 1') <<
+ header(2, 'Header 1-1') <<
+ header(3, 'Header 1-1-1') <<
+ header(2, 'Header 1-2') <<
+ header(1, 'Header 2') <<
+ header(2, 'Header 2-1')
+ )
+ end
+
+ it 'keeps list levels regarding header levels' do
+ items = doc.css('li')
+
+ # Header 1
+ expect(items[0].ancestors).to satisfy_none { |node| node.name == 'li' }
+
+ # Header 1-1
+ expect(items[1].ancestors).to include(items[0])
+
+ # Header 1-1-1
+ expect(items[2].ancestors).to include(items[0], items[1])
+
+ # Header 1-2
+ expect(items[3].ancestors).to include(items[0])
+ expect(items[3].ancestors).not_to include(items[1])
+
+ # Header 2
+ expect(items[4].ancestors).to satisfy_none { |node| node.name == 'li' }
+
+ # Header 2-1
+ expect(items[5].ancestors).to include(items[4])
+ end
+ end
end
end
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 7f5d481c36c..b172a1b718c 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -1,53 +1,77 @@
require 'spec_helper'
describe Banzai::ObjectRenderer do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:renderer) { described_class.new(project, user, custom_value: 'value') }
let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
describe '#render' do
- it 'renders and redacts an Array of objects' do
- renderer.render([object], :note)
+ context 'with cache' do
+ it 'renders and redacts an Array of objects' do
+ renderer.render([object], :note)
- expect(object.redacted_note_html).to eq '<p dir="auto">hello</p>'
- expect(object.user_visible_reference_count).to eq 0
- end
+ expect(object.redacted_note_html).to eq '<p dir="auto">hello</p>'
+ expect(object.user_visible_reference_count).to eq 0
+ end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::Redactor to perform redaction' do
+ expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
- renderer.render([object], :note)
- end
+ renderer.render([object], :note)
+ end
- it 'retrieves field content using Banzai.render_field' do
- expect(Banzai).to receive(:render_field).with(object, :note).and_call_original
+ it 'retrieves field content using Banzai::Renderer.render_field' do
+ expect(Banzai::Renderer).to receive(:render_field).with(object, :note).and_call_original
- renderer.render([object], :note)
- end
+ renderer.render([object], :note)
+ end
- it 'passes context to PostProcessPipeline' do
- another_user = create(:user)
- another_project = create(:project)
- object = Note.new(
- note: 'hello',
- note_html: 'hello',
- author: another_user,
- project: another_project
- )
-
- expect(Banzai::Pipeline::PostProcessPipeline).to receive(:to_document).with(
- anything,
- hash_including(
- skip_redaction: true,
- current_user: user,
- project: another_project,
+ it 'passes context to PostProcessPipeline' do
+ another_user = create(:user)
+ another_project = create(:project)
+ object = Note.new(
+ note: 'hello',
+ note_html: 'hello',
author: another_user,
- custom_value: 'value'
+ project: another_project
)
- ).and_call_original
- renderer.render([object], :note)
+ expect(Banzai::Pipeline::PostProcessPipeline).to receive(:to_document).with(
+ anything,
+ hash_including(
+ skip_redaction: true,
+ current_user: user,
+ project: another_project,
+ author: another_user,
+ custom_value: 'value'
+ )
+ ).and_call_original
+
+ renderer.render([object], :note)
+ end
+ end
+
+ context 'without cache' do
+ let(:commit) { project.commit }
+
+ it 'renders and redacts an Array of objects' do
+ renderer.render([commit], :title)
+
+ expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
+ end
+
+ it 'calls Banzai::Redactor to perform redaction' do
+ expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+
+ renderer.render([commit], :title)
+ end
+
+ it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title).and_call_original
+
+ renderer.render([commit], :title)
+ end
end
end
end
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index 0e094405e33..da42272bbef 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -4,6 +4,7 @@ describe Banzai::Renderer do
def fake_object(fresh:)
object = double('object')
+ allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(true)
allow(object).to receive(:cached_html_up_to_date?).with(:field).and_return(fresh)
allow(object).to receive(:cached_html_for).with(:field).and_return('field_html')
@@ -12,25 +13,38 @@ describe Banzai::Renderer do
describe '#render_field' do
let(:renderer) { described_class }
- subject { renderer.render_field(object, :field) }
- context 'with a stale cache' do
- let(:object) { fake_object(fresh: false) }
+ context 'without cache' do
+ let(:commit) { create(:project, :repository).commit }
- it 'caches and returns the result' do
- expect(object).to receive(:refresh_markdown_cache!).with(do_update: true)
+ it 'returns cacheless render field' do
+ expect(renderer).to receive(:cacheless_render_field).with(commit, :title)
- is_expected.to eq('field_html')
+ renderer.render_field(commit, :title)
end
end
- context 'with an up-to-date cache' do
- let(:object) { fake_object(fresh: true) }
+ context 'with cache' do
+ subject { renderer.render_field(object, :field) }
- it 'uses the cache' do
- expect(object).to receive(:refresh_markdown_cache!).never
+ context 'with a stale cache' do
+ let(:object) { fake_object(fresh: false) }
- is_expected.to eq('field_html')
+ it 'caches and returns the result' do
+ expect(object).to receive(:refresh_markdown_cache!).with(do_update: true)
+
+ is_expected.to eq('field_html')
+ end
+ end
+
+ context 'with an up-to-date cache' do
+ let(:object) { fake_object(fresh: true) }
+
+ it 'uses the cache' do
+ expect(object).to receive(:refresh_markdown_cache!).never
+
+ is_expected.to eq('field_html')
+ end
end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index c70a4cb55fe..1efd3113a43 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -164,9 +164,46 @@ module Ci
expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
end
end
+
+ context 'when kubernetes policy is specified' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ let(:config) do
+ YAML.dump(
+ spinach: { stage: 'test', script: 'spinach' },
+ production: {
+ stage: 'deploy',
+ script: 'cap',
+ only: { kubernetes: 'active' }
+ }
+ )
+ end
+
+ context 'when kubernetes is active' do
+ let(:project) { create(:kubernetes_project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+ it 'returns seeds for kubernetes dependent job' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 2
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ expect(seeds.second.builds.dig(0, :name)).to eq 'production'
+ end
+ end
+
+ context 'when kubernetes is not active' do
+ it 'does not return seeds for kubernetes dependent job' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+ end
end
- describe "#builds_for_ref" do
+ describe "#builds_for_stage_and_ref" do
let(:type) { 'test' }
it "returns builds if no branch specified" do
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index 0d5fffa38ff..c56b08b18a2 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -214,7 +214,7 @@ end
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
#
-describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170608152748 do
+describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do
let(:migration) { described_class.new }
let(:project) { create(:project_empty_repo) }
let(:author) { create(:user) }
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
new file mode 100644
index 00000000000..7bd6a2ead25
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Artifacts::Path do
+ describe '#valid?' do
+ context 'when path contains a zero character' do
+ it 'is not valid' do
+ expect(described_class.new("something/\255")).not_to be_valid
+ end
+ end
+
+ context 'when path is not utf8 string' do
+ it 'is not valid' do
+ expect(described_class.new("something/\0")).not_to be_valid
+ end
+ end
+
+ context 'when path is valid' do
+ it 'is valid' do
+ expect(described_class.new("some/file/path")).to be_valid
+ end
+ end
+ end
+
+ describe '#directory?' do
+ context 'when path ends with a directory indicator' do
+ it 'is a directory' do
+ expect(described_class.new("some/file/dir/")).to be_directory
+ end
+ end
+
+ context 'when path does not end with a directory indicator' do
+ it 'is not a directory' do
+ expect(described_class.new("some/file")).not_to be_directory
+ end
+ end
+ end
+
+ describe '#name' do
+ it 'returns a base name' do
+ expect(described_class.new("some/file").name).to eq 'file'
+ end
+ end
+
+ describe '#nodes' do
+ it 'returns number of path nodes' do
+ expect(described_class.new("some/dir/file").nodes).to eq 2
+ end
+ end
+
+ describe '#to_s' do
+ context 'when path is valid' do
+ it 'returns a string representation of a path' do
+ expect(described_class.new('some/path').to_s).to eq 'some/path'
+ end
+ end
+
+ context 'when path is invalid' do
+ it 'raises an error' do
+ expect { described_class.new("invalid/\0").to_s }
+ .to raise_error ArgumentError
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 36a84da4a52..5e83abf645b 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -16,8 +16,8 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
describe '#value' do
- it 'returns key value' do
- expect(entry.value).to eq config
+ it 'returns refs hash' do
+ expect(entry.value).to eq(refs: config)
end
end
end
@@ -56,6 +56,50 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when using complex policy' do
+ context 'when specifiying refs policy' do
+ let(:config) { { refs: ['master'] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(refs: %w[master])
+ end
+ end
+
+ context 'when specifying kubernetes policy' do
+ let(:config) { { kubernetes: 'active' } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(kubernetes: 'active')
+ end
+ end
+
+ context 'when specifying invalid kubernetes policy' do
+ let(:config) { { kubernetes: 'something' } }
+
+ it 'reports an error about invalid policy' do
+ expect(entry.errors).to include /unknown value: something/
+ end
+ end
+
+ context 'when specifying unknown policy' do
+ let(:config) { { refs: ['master'], invalid: :something } }
+
+ it 'returns error about invalid key' do
+ expect(entry.errors).to include /unknown keys: invalid/
+ end
+ end
+
+ context 'when policy is empty' do
+ let(:config) { {} }
+
+ it 'is not a valid configuration' do
+ expect(entry.errors).to include /can't be blank/
+ end
+ end
+ end
+
context 'when policy strategy does not match' do
let(:config) { 'string strategy' }
diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb
index d7e91a5a62c..9ecd128faca 100644
--- a/spec/lib/gitlab/ci/stage/seed_spec.rb
+++ b/spec/lib/gitlab/ci/stage/seed_spec.rb
@@ -27,6 +27,26 @@ describe Gitlab::Ci::Stage::Seed do
expect(subject.builds)
.to all(include(trigger_request: pipeline.trigger_requests.first))
end
+
+ context 'when a ref is protected' do
+ before do
+ allow_any_instance_of(Project).to receive(:protected_for?).and_return(true)
+ end
+
+ it 'returns protected builds' do
+ expect(subject.builds).to all(include(protected: true))
+ end
+ end
+
+ context 'when a ref is unprotected' do
+ before do
+ allow_any_instance_of(Project).to receive(:protected_for?).and_return(false)
+ end
+
+ it 'returns unprotected builds' do
+ expect(subject.builds).to all(include(protected: false))
+ end
+ end
end
describe '#user=' do
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index dfbdbee48f7..d39b33a0c05 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -273,6 +273,25 @@ EOT
end
end
+ describe '#json_safe_diff' do
+ let(:project) { create(:project, :repository) }
+
+ it 'fake binary message when it detects binary' do
+ # Rugged will not detect this as binary, but we can fake it
+ diff_message = "Binary files files/images/icn-time-tracking.pdf and files/images/icn-time-tracking.pdf differ\n"
+ binary_diff = described_class.between(project.repository, 'add-pdf-text-binary', 'add-pdf-text-binary^').first
+
+ expect(binary_diff.diff).not_to be_empty
+ expect(binary_diff.json_safe_diff).to eq(diff_message)
+ end
+
+ it 'leave non-binary diffs as-is' do
+ diff = described_class.new(@rugged_diff)
+
+ expect(diff.json_safe_diff).to eq(diff.diff)
+ end
+ end
+
describe '#submodule?' do
before do
commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 4cfb4b7d357..556a148c3bc 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -390,46 +390,73 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#delete_branch" do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- @repo.delete_branch("feature")
+ shared_examples "deleting a branch" do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+
+ after do
+ FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
+ ensure_seeds
+ end
+
+ it "removes the branch from the repo" do
+ branch_name = "to-be-deleted-soon"
+
+ repository.create_branch(branch_name)
+ expect(repository.rugged.branches[branch_name]).not_to be_nil
+
+ repository.delete_branch(branch_name)
+ expect(repository.rugged.branches[branch_name]).to be_nil
+ end
+
+ context "when branch does not exist" do
+ it "raises a DeleteBranchError exception" do
+ expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
+ end
+ end
end
- it "should remove the branch from the repo" do
- expect(@repo.rugged.branches["feature"]).to be_nil
+ context "when Gitaly delete_branch is enabled" do
+ it_behaves_like "deleting a branch"
end
- after(:all) do
- FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
- ensure_seeds
+ context "when Gitaly delete_branch is disabled", skip_gitaly_mock: true do
+ it_behaves_like "deleting a branch"
end
end
describe "#create_branch" do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ shared_examples 'creating a branch' do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- it "should create a new branch" do
- expect(@repo.create_branch('new_branch', 'master')).not_to be_nil
- end
+ after do
+ FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
+ ensure_seeds
+ end
- it "should create a new branch with the right name" do
- expect(@repo.create_branch('another_branch', 'master').name).to eq('another_branch')
- end
+ it "should create a new branch" do
+ expect(repository.create_branch('new_branch', 'master')).not_to be_nil
+ end
- it "should fail if we create an existing branch" do
- @repo.create_branch('duplicated_branch', 'master')
- expect {@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
+ it "should create a new branch with the right name" do
+ expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
+ end
+
+ it "should fail if we create an existing branch" do
+ repository.create_branch('duplicated_branch', 'master')
+ expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
+ end
+
+ it "should fail if we create a branch from a non existing ref" do
+ expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
+ end
end
- it "should fail if we create a branch from a non existing ref" do
- expect {@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
+ context 'when Gitaly create_branch feature is enabled' do
+ it_behaves_like 'creating a branch'
end
- after(:all) do
- FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
- ensure_seeds
+ context 'when Gitaly create_branch feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'creating a branch'
end
end
@@ -905,7 +932,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
it 'should set the autocrlf option to the provided option' do
@repo.autocrlf = :input
- File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, '.git', 'config')) do |config_file|
+ File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, 'config')) do |config_file|
expect(config_file.read).to match('autocrlf = input')
end
end
@@ -916,27 +943,37 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#find_branch' do
- it 'should return a Branch for master' do
- branch = repository.find_branch('master')
+ shared_examples 'finding a branch' do
+ it 'should return a Branch for master' do
+ branch = repository.find_branch('master')
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
- end
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
+
+ it 'should handle non-existent branch' do
+ branch = repository.find_branch('this-is-garbage')
- it 'should handle non-existent branch' do
- branch = repository.find_branch('this-is-garbage')
+ expect(branch).to eq(nil)
+ end
+ end
- expect(branch).to eq(nil)
+ context 'when Gitaly find_branch feature is enabled' do
+ it_behaves_like 'finding a branch'
end
- it 'should reload Rugged::Repository and return master' do
- expect(Rugged::Repository).to receive(:new).twice.and_call_original
+ context 'when Gitaly find_branch feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding a branch'
+
+ it 'should reload Rugged::Repository and return master' do
+ expect(Rugged::Repository).to receive(:new).twice.and_call_original
- repository.find_branch('master')
- branch = repository.find_branch('master', force_reload: true)
+ repository.find_branch('master')
+ branch = repository.find_branch('master', force_reload: true)
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
end
end
@@ -967,7 +1004,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'with local and remote branches' do
let(:repository) do
- Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'), '')
+ Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
before do
@@ -1014,7 +1051,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'with local and remote branches' do
let(:repository) do
- Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'), '')
+ Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
before do
@@ -1220,7 +1257,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#local_branches' do
before(:all) do
- @repo = Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'), '')
+ @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
after(:all) do
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index e521fcc6dc1..b07462e4978 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -2,45 +2,9 @@ require 'rails_helper'
describe Gitlab::Gpg::Commit do
describe '#signature' do
- let!(:project) { create :project, :repository, path: 'sample-project' }
- let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
-
- context 'unsigned commit' do
- it 'returns nil' do
- expect(described_class.new(project, commit_sha).signature).to be_nil
- end
- end
-
- context 'known and verified public key' do
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: create(:user, email: GpgHelpers::User1.emails.first)
- end
-
- before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
-
- it 'returns a valid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
- commit_sha: commit_sha,
- project: project,
- gpg_key: gpg_key,
- gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- gpg_key_user_name: GpgHelpers::User1.names.first,
- gpg_key_user_email: GpgHelpers::User1.emails.first,
- valid_signature: true
- )
- end
-
+ shared_examples 'returns the cached signature on second call' do
it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
+ gpg_commit = described_class.new(commit)
expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature
@@ -51,11 +15,140 @@ describe Gitlab::Gpg::Commit do
end
end
- context 'known but unverified public key' do
- let!(:gpg_key) { create :gpg_key, key: GpgHelpers::User1.public_key }
+ let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
- before do
- allow(Rugged::Commit).to receive(:extract_signature)
+ context 'unsigned commit' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
+ it 'returns nil' do
+ expect(described_class.new(commit).signature).to be_nil
+ end
+ end
+
+ context 'known key' do
+ context 'user matches the key uid' do
+ context 'user email matches the email committer' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
+
+ let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns a valid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'verified'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+
+ context 'user email does not match the committer email, but is the same user' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
+
+ let(:user) do
+ create(:user, email: GpgHelpers::User1.emails.first).tap do |user|
+ create :email, user: user, email: GpgHelpers::User2.emails.first
+ end
+ end
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'same_user_different_email'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+
+ context 'user email does not match the committer email' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
+
+ let(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'other_user'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+ end
+
+ context 'user does not match the key uid' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
+ let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
.and_return(
[
@@ -63,33 +156,27 @@ describe Gitlab::Gpg::Commit do
GpgHelpers::User1.signed_commit_base_data
]
)
- end
-
- it 'returns an invalid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
- commit_sha: commit_sha,
- project: project,
- gpg_key: gpg_key,
- gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- gpg_key_user_name: GpgHelpers::User1.names.first,
- gpg_key_user_email: GpgHelpers::User1.emails.first,
- valid_signature: false
- )
- end
-
- it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
-
- expect(gpg_commit).to receive(:using_keychain).and_call_original
- gpg_commit.signature
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'unverified_key'
+ )
+ end
- # consecutive call
- expect(gpg_commit).not_to receive(:using_keychain).and_call_original
- gpg_commit.signature
+ it_behaves_like 'returns the cached signature on second call'
end
end
- context 'unknown public key' do
+ context 'unknown key' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha }
+
before do
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
@@ -102,27 +189,18 @@ describe Gitlab::Gpg::Commit do
end
it 'returns an invalid signature' do
- expect(described_class.new(project, commit_sha).signature).to have_attributes(
+ expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha,
project: project,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: nil,
gpg_key_user_email: nil,
- valid_signature: false
+ verification_status: 'unknown_key'
)
end
- it 'returns the cached signature on second call' do
- gpg_commit = described_class.new(project, commit_sha)
-
- expect(gpg_commit).to receive(:using_keychain).and_call_original
- gpg_commit.signature
-
- # consecutive call
- expect(gpg_commit).not_to receive(:using_keychain).and_call_original
- gpg_commit.signature
- end
+ it_behaves_like 'returns the cached signature on second call'
end
end
end
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index 4de4419de27..b9fd4d02156 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -4,8 +4,29 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
describe '#run' do
let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:raw_commit) do
+ raw_commit = double(
+ :raw_commit,
+ signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ],
+ sha: commit_sha,
+ committer_email: GpgHelpers::User1.emails.first
+ )
+
+ allow(raw_commit).to receive :save!
+
+ raw_commit
+ end
+
+ let!(:commit) do
+ create :commit, git_commit: raw_commit, project: project
+ end
before do
+ allow_any_instance_of(Project).to receive(:commit).and_return(commit)
+
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
.and_return(
@@ -25,7 +46,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
end
it 'assigns the gpg key to the signature when the missing gpg key is added' do
@@ -39,7 +60,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -54,7 +75,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
end
@@ -68,7 +89,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
end
it 'updates the signature to being valid when the missing gpg key is added' do
@@ -82,7 +103,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -97,7 +118,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
)
end
end
@@ -115,7 +136,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unknown_key'
end
it 'updates the signature to being valid when the user updates the email address' do
@@ -123,7 +144,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
key: GpgHelpers::User1.public_key,
user: user
- expect(invalid_gpg_signature.reload.valid_signature).to be_falsey
+ expect(invalid_gpg_signature.reload.verification_status).to eq 'unverified_key'
# InvalidGpgSignatureUpdater is called by the after_update hook
user.update_attributes!(email: GpgHelpers::User1.emails.first)
@@ -133,7 +154,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: true
+ verification_status: 'verified'
)
end
@@ -147,7 +168,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unverified_key'
)
# InvalidGpgSignatureUpdater is called by the after_update hook
@@ -158,7 +179,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
commit_sha: commit_sha,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
- valid_signature: false
+ verification_status: 'unverified_key'
)
end
end
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 30ad033b204..11a2aea1915 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -42,6 +42,21 @@ describe Gitlab::Gpg do
described_class.user_infos_from_key('bogus')
).to eq []
end
+
+ it 'downcases the email' do
+ public_key = double(:key)
+ fingerprints = double(:fingerprints)
+ uid = double(:uid, name: 'Nannie Bernhard', email: 'NANNIE.BERNHARD@EXAMPLE.COM')
+ raw_key = double(:raw_key, uids: [uid])
+ allow(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(public_key).and_return(fingerprints)
+ allow(GPGME::Key).to receive(:find).with(:public, anything).and_return([raw_key])
+
+ user_infos = described_class.user_infos_from_key(public_key)
+ expect(user_infos).to eq([{
+ name: 'Nannie Bernhard',
+ email: 'nannie.bernhard@example.com'
+ }])
+ end
end
describe '.current_home_dir' do
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index cd5c2b99751..3a962ba7f22 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'simple_po_parser'
describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index dbe042adff5..3fb8edeb701 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -265,6 +265,7 @@ project:
- statistics
- container_repositories
- uploads
+- members_and_requesters
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 5b16fc5d084..d664d371028 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -11,8 +11,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
- allow(@project.repository).to receive(:fetch_ref).and_return(true)
- allow(@project.repository.raw).to receive(:rugged_branch_exists?).and_return(false)
+ allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true)
+ allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 065b0ec6658..8e3554375e8 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -117,6 +117,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['pipelines'].first['statuses'].count { |hash| hash['type'] == 'Ci::Build' }).to eq(1)
end
+ it 'has no when YML attributes but only the DB column' do
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')))
+ expect_any_instance_of(Ci::GitlabCiYamlProcessor).not_to receive(:build_attributes)
+
+ saved_project_json
+ end
+
it 'has pipeline commits' do
expect(saved_project_json['pipelines']).not_to be_empty
end
@@ -251,15 +258,11 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:label_priority, label: group_label, priority: 1)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone)
- commit_status = create(:commit_status, project: project)
- ci_pipeline = create(:ci_pipeline,
- project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch,
- statuses: [commit_status])
+ ci_build = create(:ci_build, project: project, when: nil)
+ ci_build.pipeline.update(project: project)
+ create(:commit_status, project: project, pipeline: ci_build.pipeline)
- create(:ci_build, pipeline: ci_pipeline, project: project)
create(:milestone, project: project)
create(:note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
@@ -267,7 +270,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:note_on_commit,
author: user,
project: project,
- commit_id: ci_pipeline.sha)
+ commit_id: ci_build.pipeline.sha)
create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index f2224bc3aa6..6503d8fb0ac 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -65,6 +65,7 @@ Note:
- change_position
- resolved_at
- resolved_by_id
+- resolved_by_push
- discussion_id
- original_discussion_id
LabelLink:
@@ -225,6 +226,7 @@ Ci::Pipeline:
- auto_canceled_by_id
- pipeline_schedule_id
- config_source
+- protected
Ci::Stage:
- id
- name
@@ -277,6 +279,8 @@ CommitStatus:
- coverage_regex
- auto_canceled_by_id
- retried
+- protected
+- failure_reason
Ci::Variable:
- id
- project_id
@@ -396,6 +400,7 @@ Project:
- public_builds
- last_repository_check_failed
- last_repository_check_at
+- collapse_outdated_diff_comments
- container_registry_enabled
- only_allow_merge_if_pipeline_succeeds
- has_external_issue_tracker
@@ -404,6 +409,7 @@ Project:
- only_allow_merge_if_all_discussions_are_resolved
- auto_cancel_pending_pipelines
- printing_merge_request_link_enabled
+- resolve_outdated_diff_discussions
- build_allow_git_fetch
- last_repository_updated_at
- ci_config_path
diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb
new file mode 100644
index 00000000000..c262fdfcb61
--- /dev/null
+++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::IssuablesCountForState do
+ let(:finder) do
+ double(:finder, count_by_state: { opened: 2, closed: 1 })
+ end
+
+ let(:counter) { described_class.new(finder) }
+
+ describe '#for_state_or_opened' do
+ it 'returns the number of issuables for the given state' do
+ expect(counter.for_state_or_opened(:closed)).to eq(1)
+ end
+
+ it 'returns the number of open issuables when no state is given' do
+ expect(counter.for_state_or_opened).to eq(2)
+ end
+
+ it 'returns the number of open issuables when a nil value is given' do
+ expect(counter.for_state_or_opened(nil)).to eq(2)
+ end
+ end
+
+ describe '#[]' do
+ it 'returns the number of issuables for the given state' do
+ expect(counter[:closed]).to eq(1)
+ end
+
+ it 'casts valid states from Strings to Symbols' do
+ expect(counter['closed']).to eq(1)
+ end
+
+ it 'returns 0 when using an invalid state name as a String' do
+ expect(counter['kittens']).to be_zero
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 5100a5a609e..6a6e465cea2 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -37,7 +37,8 @@ describe Gitlab::LDAP::User do
end
it "does not mark existing ldap user as changed" do
- create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', external_email: true, email_provider: 'ldapmain')
+ create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain')
+ ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true)
expect(ldap_user.changed?).to be_falsey
end
end
@@ -141,12 +142,12 @@ describe Gitlab::LDAP::User do
expect(ldap_user.gl_user.email).to eq(info[:email])
end
- it "has external_email set to true" do
- expect(ldap_user.gl_user.external_email?).to be(true)
+ it "has user_synced_attributes_metadata email set to true" do
+ expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
- it "has email_provider set to provider" do
- expect(ldap_user.gl_user.email_provider).to eql 'ldapmain'
+ it "has synced_attribute_provider set to ldapmain" do
+ expect(ldap_user.gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain'
end
end
@@ -156,11 +157,11 @@ describe Gitlab::LDAP::User do
end
it "has a temp email" do
- expect(ldap_user.gl_user.temp_oauth_email?).to be(true)
+ expect(ldap_user.gl_user.temp_oauth_email?).to be_truthy
end
- it "has external_email set to false" do
- expect(ldap_user.gl_user.external_email?).to be(false)
+ it "has synced attribute email set to false" do
+ expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
end
end
end
@@ -168,7 +169,7 @@ describe Gitlab::LDAP::User do
describe 'blocking' do
def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config)
- .to receive(:block_auto_created_users).and_return(value)
+ .to receive(:block_auto_created_users).and_return(value)
end
context 'signup' do
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 2cf0f7516de..8aaf320cbf5 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -10,7 +10,11 @@ describe Gitlab::OAuth::User do
{
nickname: '-john+gitlab-ETC%.git@gmail.com',
name: 'John',
- email: 'john@mail.com'
+ email: 'john@mail.com',
+ address: {
+ locality: 'locality',
+ country: 'country'
+ }
}
end
let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
@@ -422,11 +426,12 @@ describe Gitlab::OAuth::User do
end
end
- describe 'updating email' do
+ describe 'ensure backwards compatibility with with sync email from provider option' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
before do
stub_omniauth_config(sync_email_from_provider: 'my-provider')
+ stub_omniauth_config(sync_profile_from_provider: ['my-provider'])
end
context "when provider sets an email" do
@@ -434,12 +439,12 @@ describe Gitlab::OAuth::User do
expect(gl_user.email).to eq(info_hash[:email])
end
- it "has external_email set to true" do
- expect(gl_user.external_email?).to be(true)
+ it "has external_attributes set to true" do
+ expect(gl_user.user_synced_attributes_metadata).not_to be_nil
end
- it "has email_provider set to provider" do
- expect(gl_user.email_provider).to eql 'my-provider'
+ it "has attributes_provider set to my-provider" do
+ expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
end
end
@@ -452,8 +457,9 @@ describe Gitlab::OAuth::User do
expect(gl_user.email).not_to eq(info_hash[:email])
end
- it "has external_email set to false" do
- expect(gl_user.external_email?).to be(false)
+ it "has user_synced_attributes_metadata set to nil" do
+ expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
end
end
end
@@ -487,4 +493,172 @@ describe Gitlab::OAuth::User do
end
end
end
+
+ describe 'updating email with sync profile' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ before do
+ stub_omniauth_config(sync_profile_from_provider: ['my-provider'])
+ stub_omniauth_config(sync_profile_attributes: true)
+ end
+
+ context "when provider sets an email" do
+ it "updates the user email" do
+ expect(gl_user.email).to eq(info_hash[:email])
+ end
+
+ it "has email_synced_attribute set to true" do
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true)
+ end
+
+ it "has my-provider as attributes_provider" do
+ expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
+ end
+ end
+
+ context "when provider doesn't set an email" do
+ before do
+ info_hash.delete(:email)
+ end
+
+ it "does not update the user email" do
+ expect(gl_user.email).not_to eq(info_hash[:email])
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false)
+ end
+ end
+ end
+
+ describe 'updating name' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ before do
+ stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+ end
+
+ context "when provider sets a name" do
+ it "updates the user name" do
+ expect(gl_user.name).to eq(info_hash[:name])
+ end
+ end
+
+ context "when provider doesn't set a name" do
+ before do
+ info_hash.delete(:name)
+ end
+
+ it "does not update the user name" do
+ expect(gl_user.name).not_to eq(info_hash[:name])
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be(false)
+ end
+ end
+ end
+
+ describe 'updating location' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ before do
+ stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+ end
+
+ context "when provider sets a location" do
+ it "updates the user location" do
+ expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country])
+ expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true)
+ end
+ end
+
+ context "when provider doesn't set a location" do
+ before do
+ info_hash[:address].delete(:country)
+ info_hash[:address].delete(:locality)
+ end
+
+ it "does not update the user location" do
+ expect(gl_user.location).to be_nil
+ expect(gl_user.user_synced_attributes_metadata.location_synced).to be(false)
+ end
+ end
+ end
+
+ describe 'updating user info' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ context "update all info" do
+ before do
+ stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
+ stub_omniauth_setting(sync_profile_attributes: true)
+ end
+
+ it "updates the user email" do
+ expect(gl_user.email).to eq(info_hash[:email])
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true)
+ end
+
+ it "updates the user name" do
+ expect(gl_user.name).to eq(info_hash[:name])
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true)
+ end
+
+ it "updates the user location" do
+ expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country])
+ expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true)
+ end
+
+ it "sets my-provider as the attributes provider" do
+ expect(gl_user.user_synced_attributes_metadata.provider).to eql('my-provider')
+ end
+ end
+
+ context "update only requested info" do
+ before do
+ stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
+ stub_omniauth_setting(sync_profile_attributes: %w(name location))
+ end
+
+ it "updates the user name" do
+ expect(gl_user.name).to eq(info_hash[:name])
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be(true)
+ end
+
+ it "updates the user location" do
+ expect(gl_user.location).to eq(info_hash[:address][:locality] + ', ' + info_hash[:address][:country])
+ expect(gl_user.user_synced_attributes_metadata.location_synced).to be(true)
+ end
+
+ it "does not update the user email" do
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false)
+ end
+ end
+
+ context "update default_scope" do
+ before do
+ stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
+ end
+
+ it "updates the user email" do
+ expect(gl_user.email).to eq(info_hash[:email])
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true)
+ end
+ end
+
+ context "update no info when profile sync is nil" do
+ it "does not have sync_attribute" do
+ expect(gl_user.user_synced_attributes_metadata).to be(nil)
+ end
+
+ it "does not update the user email" do
+ expect(gl_user.email).not_to eq(info_hash[:email])
+ end
+
+ it "does not update the user name" do
+ expect(gl_user.name).not_to eq(info_hash[:name])
+ end
+
+ it "does not update the user location" do
+ expect(gl_user.location).not_to eq(info_hash[:address][:country])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 9d7b2136dab..48d56628ed5 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -52,4 +52,124 @@ describe Gitlab::SQL::Pattern do
end
end
end
+
+ describe '.select_fuzzy_words' do
+ subject(:select_fuzzy_words) { Issue.select_fuzzy_words(query) }
+
+ context 'with a word equal to 3 chars' do
+ let(:query) { 'foo' }
+
+ it 'returns array cotaining a word' do
+ expect(select_fuzzy_words).to match_array(['foo'])
+ end
+ end
+
+ context 'with a word shorter than 3 chars' do
+ let(:query) { 'fo' }
+
+ it 'returns empty array' do
+ expect(select_fuzzy_words).to match_array([])
+ end
+ end
+
+ context 'with two words both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns array containing two words' do
+ expect(select_fuzzy_words).to match_array(%w[foo baz])
+ end
+ end
+
+ context 'with two words divided by two spaces both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns array containing two words' do
+ expect(select_fuzzy_words).to match_array(%w[foo baz])
+ end
+ end
+
+ context 'with two words equal to 3 chars and shorter than 3 chars' do
+ let(:query) { 'foo ba' }
+
+ it 'returns array containing a word' do
+ expect(select_fuzzy_words).to match_array(['foo'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote' do
+ let(:query) { '"really bar"' }
+
+ it 'returns array containing a multi-word' do
+ expect(select_fuzzy_words).to match_array(['really bar'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz' }
+
+ it 'returns array containing a multi-word and tow words' do
+ expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote missing a spece before the first double quote' do
+ let(:query) { 'foo"really bar"' }
+
+ it 'returns array containing two words with double quote' do
+ expect(select_fuzzy_words).to match_array(['foo"really', 'bar"'])
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote missing a spece after the second double quote' do
+ let(:query) { '"really bar"baz' }
+
+ it 'returns array containing two words with double quote' do
+ expect(select_fuzzy_words).to match_array(['"really', 'bar"baz'])
+ end
+ end
+
+ context 'with two multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz "awesome feature"' }
+
+ it 'returns array containing two multi-words and tow words' do
+ expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz', 'awesome feature'])
+ end
+ end
+ end
+
+ describe '.to_fuzzy_arel' do
+ subject(:to_fuzzy_arel) { Issue.to_fuzzy_arel(:title, query) }
+
+ context 'with a word equal to 3 chars' do
+ let(:query) { 'foo' }
+
+ it 'returns a single ILIKE condition' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.*I?LIKE '\%foo\%'/)
+ end
+ end
+
+ context 'with a word shorter than 3 chars' do
+ let(:query) { 'fo' }
+
+ it 'returns nil' do
+ expect(to_fuzzy_arel).to be_nil
+ end
+ end
+
+ context 'with two words both equal to 3 chars' do
+ let(:query) { 'foo baz' }
+
+ it 'returns a joining LIKE condition using a AND' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%'/)
+ end
+ end
+
+ context 'with a multi-word surrounded by double quote and two words' do
+ let(:query) { 'foo "really bar" baz' }
+
+ it 'returns a joining LIKE condition using a AND' do
+ expect(to_fuzzy_arel.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%' AND .*title.*I?LIKE '\%really bar\%'/)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index 6541326d1de..e2fa76522bc 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -30,6 +30,14 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
end
end
+ describe '.by_category' do
+ it 'returns sorted results' do
+ result = described_class.by_category('General')
+
+ expect(result).to eq(result.sort)
+ end
+ end
+
describe '#content' do
it 'loads the full file' do
gitignore = subject.new(Rails.root.join('vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml'))
@@ -38,4 +46,14 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
expect(gitignore.content).to start_with('#')
end
end
+
+ describe '#<=>' do
+ it 'sorts lexicographically' do
+ one = described_class.new('a.gitlab-ci.yml')
+ other = described_class.new('z.gitlab-ci.yml')
+
+ expect(one.<=>(other)).to be(-1)
+ expect([other, one].sort).to eq([one, other])
+ end
+ end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 308b1a128be..fdc3990132a 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -1,11 +1,7 @@
require 'spec_helper'
describe Gitlab::UrlSanitizer do
- let(:credentials) { { user: 'blah', password: 'password' } }
- let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: credentials)
- end
- let(:user) { double(:user, username: 'john.doe') }
+ using RSpec::Parameterized::TableSyntax
describe '.sanitize' do
def sanitize_url(url)
@@ -16,83 +12,166 @@ describe Gitlab::UrlSanitizer do
})
end
- it 'mask the credentials from HTTP URLs' do
- filtered_content = sanitize_url('http://user:pass@test.com/root/repoC.git/')
+ where(:input, :output) do
+ 'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/'
+ 'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/'
+ 'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git'
- expect(filtered_content).to include("http://*****:*****@test.com/root/repoC.git/")
- end
+ # git protocol does not support authentication but clean any details anyway
+ 'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git'
+ 'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git'
- it 'mask the credentials from HTTPS URLs' do
- filtered_content = sanitize_url('https://user:pass@test.com/root/repoA.git/')
+ # SCP-style URLs are left unmodified
+ 'user@server:project.git' | 'user@server:project.git'
+ 'user:pass@server:project.git' | 'user:pass@server:project.git'
- expect(filtered_content).to include("https://*****:*****@test.com/root/repoA.git/")
+ # return an empty string for invalid URLs
+ 'ssh://' | ''
end
- it 'mask credentials from SSH URLs' do
- filtered_content = sanitize_url('ssh://user@host.test/path/to/repo.git')
-
- expect(filtered_content).to include("ssh://*****@host.test/path/to/repo.git")
+ with_them do
+ it { expect(sanitize_url(input)).to include("repository '#{output}' not found") }
end
+ end
- it 'does not modify Git URLs' do
- # git protocol does not support authentication
- filtered_content = sanitize_url('git://host.test/path/to/repo.git')
+ describe '.valid?' do
+ where(:value, :url) do
+ false | nil
+ false | ''
+ false | '123://invalid:url'
+ true | 'valid@project:url.git'
+ true | 'ssh://example.com'
+ true | 'ssh://:@example.com'
+ true | 'ssh://foo@example.com'
+ true | 'ssh://foo:bar@example.com'
+ true | 'ssh://foo:bar@example.com/group/group/project.git'
+ true | 'git://example.com/group/group/project.git'
+ true | 'git://foo:bar@example.com/group/group/project.git'
+ true | 'http://foo:bar@example.com/group/group/project.git'
+ true | 'https://foo:bar@example.com/group/group/project.git'
+ end
- expect(filtered_content).to include("git://host.test/path/to/repo.git")
+ with_them do
+ it { expect(described_class.valid?(url)).to eq(value) }
end
+ end
+
+ describe '#sanitized_url' do
+ context 'credentials in hash' do
+ where(username: ['foo', '', nil], password: ['bar', '', nil])
- it 'does not modify scp-like URLs' do
- filtered_content = sanitize_url('user@server:project.git')
+ with_them do
+ let(:credentials) { { user: username, password: password } }
+ subject { described_class.new('http://example.com', credentials: credentials).sanitized_url }
- expect(filtered_content).to include("user@server:project.git")
+ it { is_expected.to eq('http://example.com') }
+ end
end
- it 'returns an empty string for invalid URLs' do
- filtered_content = sanitize_url('ssh://')
+ context 'credentials in URL' do
+ where(userinfo: %w[foo:bar@ foo@ :bar@ :@ @] + [nil])
- expect(filtered_content).to include("repository '' not found")
- end
- end
+ with_them do
+ subject { described_class.new("http://#{userinfo}example.com").sanitized_url }
- describe '.valid?' do
- it 'validates url strings' do
- expect(described_class.valid?(nil)).to be(false)
- expect(described_class.valid?('valid@project:url.git')).to be(true)
- expect(described_class.valid?('123://invalid:url')).to be(false)
+ it { is_expected.to eq('http://example.com') }
+ end
end
end
- describe '#sanitized_url' do
- it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") }
- end
-
describe '#credentials' do
- it { expect(url_sanitizer.credentials).to eq(credentials) }
+ context 'credentials in hash' do
+ where(:input, :output) do
+ { user: 'foo', password: 'bar' } | { user: 'foo', password: 'bar' }
+ { user: 'foo', password: '' } | { user: 'foo', password: nil }
+ { user: 'foo', password: nil } | { user: 'foo', password: nil }
+ { user: '', password: 'bar' } | { user: nil, password: 'bar' }
+ { user: '', password: '' } | { user: nil, password: nil }
+ { user: '', password: nil } | { user: nil, password: nil }
+ { user: nil, password: 'bar' } | { user: nil, password: 'bar' }
+ { user: nil, password: '' } | { user: nil, password: nil }
+ { user: nil, password: nil } | { user: nil, password: nil }
+ end
- context 'when user is given to #initialize' do
- let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
+ with_them do
+ subject { described_class.new('user@example.com:path.git', credentials: input).credentials }
+
+ it { is_expected.to eq(output) }
end
- it { expect(url_sanitizer.credentials).to eq({ user: 'john.doe' }) }
+ it 'overrides URL-provided credentials' do
+ sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' })
+
+ expect(sanitizer.credentials).to eq(user: 'c', password: 'd')
+ end
+ end
+
+ context 'credentials in URL' do
+ where(:url, :credentials) do
+ 'http://foo:bar@example.com' | { user: 'foo', password: 'bar' }
+ 'http://:bar@example.com' | { user: nil, password: 'bar' }
+ 'http://foo:@example.com' | { user: 'foo', password: nil }
+ 'http://foo@example.com' | { user: 'foo', password: nil }
+ 'http://:@example.com' | { user: nil, password: nil }
+ 'http://@example.com' | { user: nil, password: nil }
+ 'http://example.com' | { user: nil, password: nil }
+
+ # Credentials from SCP-style URLs are not supported at present
+ 'foo@example.com:path' | { user: nil, password: nil }
+ 'foo:bar@example.com:path' | { user: nil, password: nil }
+
+ # Other invalid URLs
+ nil | { user: nil, password: nil }
+ '' | { user: nil, password: nil }
+ 'no' | { user: nil, password: nil }
+ end
+
+ with_them do
+ subject { described_class.new(url).credentials }
+
+ it { is_expected.to eq(credentials) }
+ end
end
end
describe '#full_url' do
- it { expect(url_sanitizer.full_url).to eq("https://blah:password@github.com/me/project.git") }
+ context 'credentials in hash' do
+ where(:credentials, :userinfo) do
+ { user: 'foo', password: 'bar' } | 'foo:bar@'
+ { user: 'foo', password: '' } | 'foo@'
+ { user: 'foo', password: nil } | 'foo@'
+ { user: '', password: 'bar' } | ':bar@'
+ { user: '', password: '' } | nil
+ { user: '', password: nil } | nil
+ { user: nil, password: 'bar' } | ':bar@'
+ { user: nil, password: '' } | nil
+ { user: nil, password: nil } | nil
+ end
- it 'supports scp-like URLs' do
- sanitizer = described_class.new('user@server:project.git')
+ with_them do
+ subject { described_class.new('http://example.com', credentials: credentials).full_url }
- expect(sanitizer.full_url).to eq('user@server:project.git')
+ it { is_expected.to eq("http://#{userinfo}example.com") }
+ end
end
- context 'when user is given to #initialize' do
- let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
+ context 'credentials in URL' do
+ where(:input, :output) do
+ nil | ''
+ '' | :same
+ 'git@example.com' | :same
+ 'http://example.com' | :same
+ 'http://foo@example.com' | :same
+ 'http://foo:@example.com' | 'http://foo@example.com'
+ 'http://:bar@example.com' | :same
+ 'http://foo:bar@example.com' | :same
end
- it { expect(url_sanitizer.full_url).to eq("https://john.doe@github.com/me/project.git") }
+ with_them do
+ let(:expected) { output == :same ? input : output }
+
+ it { expect(described_class.new(input).full_url).to eq(expected) }
+ end
end
end
end
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
new file mode 100644
index 00000000000..7125bfcab59
--- /dev/null
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
+ let(:username) { '_this_user_will_not_exist_unless_it_is_stubbed' }
+ let(:base_dir) { Dir.mktmpdir }
+ let(:home_dir) { File.join(base_dir, "/var/lib/#{username}") }
+ let(:ssh_dir) { File.join(home_dir, '.ssh') }
+ let(:forbidden_file) { 'id_rsa' }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:user).and_return(username)
+ end
+
+ after do
+ FileUtils.rm_rf(base_dir)
+ end
+
+ it 'only whitelists safe files' do
+ expect(described_class::WHITELIST).to contain_exactly('authorized_keys', 'authorized_keys2', 'known_hosts')
+ end
+
+ describe '#skip?' do
+ subject { described_class.new.skip? }
+
+ where(user_exists: [true, false], home_dir_exists: [true, false])
+
+ with_them do
+ let(:expected_result) { !user_exists || !home_dir_exists }
+
+ before do
+ stub_user if user_exists
+ stub_home_dir if home_dir_exists
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#check?' do
+ subject { described_class.new.check? }
+
+ before do
+ stub_user
+ end
+
+ it 'fails if a forbidden file exists' do
+ stub_ssh_file(forbidden_file)
+
+ is_expected.to be_falsy
+ end
+
+ it "succeeds if the SSH directory doesn't exist" do
+ FileUtils.rm_rf(ssh_dir)
+
+ is_expected.to be_truthy
+ end
+
+ it 'succeeds if all the whitelisted files exist' do
+ described_class::WHITELIST.each do |filename|
+ stub_ssh_file(filename)
+ end
+
+ is_expected.to be_truthy
+ end
+ end
+
+ def stub_user
+ allow(File).to receive(:expand_path).with("~#{username}").and_return(home_dir)
+ end
+
+ def stub_home_dir
+ FileUtils.mkdir_p(home_dir)
+ end
+
+ def stub_ssh_file(filename)
+ FileUtils.mkdir_p(ssh_dir)
+ FileUtils.touch(File.join(ssh_dir, filename))
+ end
+end
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 4de5da984ba..9da3648400e 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -35,6 +35,20 @@ describe SystemCheck::SimpleExecutor do
end
end
+ class DynamicSkipCheck < SystemCheck::BaseCheck
+ set_name 'dynamic skip check'
+ set_skip_reason 'this is a skip reason'
+
+ def skip?
+ self.skip_reason = 'this is a dynamic skip reason'
+ true
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
class MultiCheck < SystemCheck::BaseCheck
set_name 'multi check'
@@ -127,6 +141,10 @@ describe SystemCheck::SimpleExecutor do
expect(subject.checks.size).to eq(1)
end
+
+ it 'errors out when passing multiple items' do
+ expect { subject << [SimpleCheck, OtherCheck] }.to raise_error(ArgumentError)
+ end
end
subject { described_class.new('Test') }
@@ -205,10 +223,14 @@ describe SystemCheck::SimpleExecutor do
subject.run_check(SkipCheck)
end
- it 'displays #skip_reason' do
+ it 'displays .skip_reason' do
expect { subject.run_check(SkipCheck) }.to output(/this is a skip reason/).to_stdout
end
+ it 'displays #skip_reason' do
+ expect { subject.run_check(DynamicSkipCheck) }.to output(/this is a dynamic skip reason/).to_stdout
+ end
+
it 'does not execute #check when #skip? is true' do
expect_any_instance_of(SkipCheck).not_to receive(:check?)
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
new file mode 100644
index 00000000000..cfd4021fbac
--- /dev/null
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170825104051_migrate_issues_to_ghost_user.rb')
+
+describe MigrateIssuesToGhostUser, :migration do
+ describe '#up' do
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:users) { table(:users) }
+
+ before do
+ projects.create!(name: 'gitlab')
+ user = users.create(email: 'test@example.com')
+ issues.create(title: 'Issue 1', author_id: nil, project_id: 1)
+ issues.create(title: 'Issue 2', author_id: user.id, project_id: 1)
+ end
+
+ context 'when ghost user exists' do
+ let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') }
+
+ it 'does not create a new user' do
+ expect { schema_migrate_up! }.not_to change { User.count }
+ end
+
+ it 'migrates issues where author = nil to the ghost user' do
+ schema_migrate_up!
+
+ expect(issues.first.reload.author_id).to eq(ghost.id)
+ end
+
+ it 'does not change issues authored by an existing user' do
+ expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ end
+ end
+
+ context 'when ghost user does not exist' do
+ it 'creates a new user' do
+ expect { schema_migrate_up! }.to change { User.count }.by(1)
+ end
+
+ it 'migrates issues where author = nil to the ghost user' do
+ schema_migrate_up!
+
+ expect(issues.first.reload.author_id).to eq(User.ghost.id)
+ end
+
+ it 'does not change issues authored by an existing user' do
+ expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 0c35ad3c9d8..c2c9f1c12d1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -43,6 +43,32 @@ describe Ci::Build do
it { is_expected.not_to include(manual_but_created) }
end
+ describe '.ref_protected' do
+ subject { described_class.ref_protected }
+
+ context 'when protected is true' do
+ let!(:job) { create(:ci_build, :protected) }
+
+ it { is_expected.to include(job) }
+ end
+
+ context 'when protected is false' do
+ let!(:job) { create(:ci_build) }
+
+ it { is_expected.not_to include(job) }
+ end
+
+ context 'when protected is nil' do
+ let!(:job) { create(:ci_build) }
+
+ before do
+ job.update_attribute(:protected, nil)
+ end
+
+ it { is_expected.not_to include(job) }
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -1466,10 +1492,12 @@ describe Ci::Build do
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
- let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
+ let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
+
let(:user_trigger_variable) do
- { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
+ { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false }
end
+
let(:predefined_trigger_variable) do
{ key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
end
@@ -1478,8 +1506,26 @@ describe Ci::Build do
build.trigger_request = trigger_request
end
- it { is_expected.to include(user_trigger_variable) }
- it { is_expected.to include(predefined_trigger_variable) }
+ shared_examples 'returns variables for triggers' do
+ it { is_expected.to include(user_trigger_variable) }
+ it { is_expected.to include(predefined_trigger_variable) }
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+ end
+
+ it_behaves_like 'returns variables for triggers'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+ end
+
+ it_behaves_like 'returns variables for triggers'
+ end
end
context 'when pipeline has a variable' do
@@ -1642,6 +1688,30 @@ describe Ci::Build do
{ key: 'secret', value: 'value', public: false }])
end
end
+
+ context 'when using auto devops' do
+ context 'and is enabled' do
+ before do
+ project.create_auto_devops!(enabled: true, domain: 'example.com')
+ end
+
+ it "includes AUTO_DEVOPS_DOMAIN" do
+ is_expected.to include(
+ { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
+ end
+ end
+
+ context 'and is disabled' do
+ before do
+ project.create_auto_devops!(enabled: false, domain: 'example.com')
+ end
+
+ it "includes AUTO_DEVOPS_DOMAIN" do
+ is_expected.not_to include(
+ { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
+ end
+ end
+ end
end
describe 'state transition: any => [:pending]' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 80cf872a5fd..95da97b7bc5 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -546,6 +546,22 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#has_kubernetes_active?' do
+ context 'when kubernetes is active' do
+ let(:project) { create(:kubernetes_project) }
+
+ it 'returns true' do
+ expect(pipeline).to have_kubernetes_active
+ end
+ end
+
+ context 'when kubernetes is not active' do
+ it 'returns false' do
+ expect(pipeline).not_to have_kubernetes_active
+ end
+ end
+ end
+
describe '#has_stage_seeds?' do
context 'when pipeline has stage seeds' do
subject { build(:ci_pipeline_with_one_job) }
@@ -783,10 +799,77 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#set_config_source' do
+ context 'on object initialisation' do
+ context 'when pipelines does not contain needed data' do
+ let(:pipeline) do
+ Ci::Pipeline.new
+ end
+
+ it 'defines source to be unknown' do
+ expect(pipeline).to be_unknown_source
+ end
+ end
+
+ context 'when pipeline contains all needed data' do
+ let(:pipeline) do
+ Ci::Pipeline.new(
+ project: project,
+ sha: '1234',
+ ref: 'master',
+ source: :push)
+ end
+
+ context 'when the repository has a config file' do
+ before do
+ allow(project.repository).to receive(:gitlab_ci_yml_for)
+ .and_return('config')
+ end
+
+ it 'defines source to be from repository' do
+ expect(pipeline).to be_repository_source
+ end
+
+ context 'when loading an object' do
+ let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }
+
+ it 'does not redefine the source' do
+ # force to overwrite the source
+ pipeline.unknown_source!
+
+ expect(new_pipeline).to be_unknown_source
+ end
+ end
+ end
+
+ context 'when the repository does not have a config file' do
+ let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
+
+ context 'auto devops enabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ allow(project).to receive(:ci_config_path) { 'custom' }
+ end
+
+ it 'defines source to be auto devops' do
+ subject
+
+ expect(pipeline).to be_auto_devops_source
+ end
+ end
+ end
+ end
+ end
+ end
+
describe '#ci_yaml_file' do
let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
- context 'when AutoDevops is enabled' do
+ context 'the source is unknown' do
+ before do
+ pipeline.unknown_source!
+ end
+
it 'returns the configuration if found' do
allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
.and_return('config')
@@ -794,49 +877,39 @@ describe Ci::Pipeline, :mailer do
expect(pipeline.ci_yaml_file).to be_a(String)
expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
expect(pipeline.yaml_errors).to be_nil
- expect(pipeline.repository?).to be(true)
end
- context 'when the implied configuration will be used' do
- before do
- allow_any_instance_of(ApplicationSetting)
- .to receive(:auto_devops_enabled?) { true }
- end
-
- it 'returns the implied configuration when its not found' do
- allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
+ it 'sets yaml errors if not found' do
+ expect(pipeline.ci_yaml_file).to be_nil
+ expect(pipeline.yaml_errors)
+ .to start_with('Failed to load CI/CD config file')
+ end
+ end
- expect(pipeline.ci_yaml_file).to eq(implied_yml)
- end
+ context 'the source is the repository' do
+ before do
+ pipeline.repository_source!
+ end
- it 'sets the config source' do
- allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
+ it 'returns the configuration if found' do
+ allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
+ .and_return('config')
- expect(pipeline.ci_yaml_file).to eq(implied_yml)
- expect(pipeline.auto_devops?).to be(true)
- end
+ expect(pipeline.ci_yaml_file).to be_a(String)
+ expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
+ expect(pipeline.yaml_errors).to be_nil
end
end
- context 'when AudoDevOps is disabled' do
- context 'when an invalid path is given' do
- it 'sets the yaml errors' do
- allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
-
- expect(pipeline.ci_yaml_file).to be_nil
- expect(pipeline.yaml_errors)
- .to start_with('Failed to load CI/CD config file')
- end
+ context 'when the source is auto_devops_source' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ pipeline.auto_devops_source!
end
- context 'when the config file can be found' do
- it 'has no yaml_errors' do
- allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
- .and_return('config')
-
- expect(pipeline.ci_yaml_file).to eq('config')
- expect(pipeline.yaml_errors).to be_nil
- end
+ it 'finds the implied config' do
+ expect(pipeline.ci_yaml_file).to eq(implied_yml)
+ expect(pipeline.yaml_errors).to be_nil
end
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 48f878bbee6..2e686e515c5 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
describe Ci::Runner do
describe 'validation' do
+ it { is_expected.to validate_presence_of(:access_level) }
+
context 'when runner is not allowed to pick untagged jobs' do
context 'when runner does not have tags' do
it 'is not valid' do
@@ -19,6 +21,34 @@ describe Ci::Runner do
end
end
+ describe '#access_level' do
+ context 'when creating new runner and access_level is nil' do
+ let(:runner) do
+ build(:ci_runner, access_level: nil)
+ end
+
+ it "object is invalid" do
+ expect(runner).not_to be_valid
+ end
+ end
+
+ context 'when creating new runner and access_level is defined in enum' do
+ let(:runner) do
+ build(:ci_runner, access_level: :not_protected)
+ end
+
+ it "object is valid" do
+ expect(runner).to be_valid
+ end
+ end
+
+ context 'when creating new runner and access_level is not defined in enum' do
+ it "raises an error" do
+ expect { build(:ci_runner, access_level: :this_is_not_defined) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
describe '#display_name' do
it 'returns the description if it has a value' do
runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
@@ -95,6 +125,8 @@ describe Ci::Runner do
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner) { create(:ci_runner) }
+ subject { runner.can_pick?(build) }
+
before do
build.project.runners << runner
end
@@ -222,6 +254,50 @@ describe Ci::Runner do
end
end
end
+
+ context 'when access_level of runner is not_protected' do
+ before do
+ runner.not_protected!
+ end
+
+ context 'when build is protected' do
+ before do
+ build.protected = true
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when build is unprotected' do
+ before do
+ build.protected = false
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when access_level of runner is ref_protected' do
+ before do
+ runner.ref_protected!
+ end
+
+ context 'when build is protected' do
+ before do
+ build.protected = true
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when build is unprotected' do
+ before do
+ build.protected = false
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
describe '#status' do
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
new file mode 100644
index 00000000000..7dcf3528f73
--- /dev/null
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Ci::TriggerRequest do
+ describe 'validation' do
+ it 'be invalid if saving a variable' do
+ trigger = build(:ci_trigger_request, variables: { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
+
+ expect(trigger).not_to be_valid
+ end
+
+ it 'be valid if not saving a variable' do
+ trigger = build(:ci_trigger_request)
+
+ expect(trigger).to be_valid
+ end
+ end
+end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index f7583645e69..858ec831200 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -443,4 +443,25 @@ describe CommitStatus do
end
end
end
+
+ describe 'set failure_reason when drop' do
+ let(:commit_status) { create(:commit_status, :created) }
+
+ subject do
+ commit_status.drop!(reason)
+ commit_status
+ end
+
+ context 'when failure_reason is nil' do
+ let(:reason) { }
+
+ it { is_expected.to be_unknown_failure }
+ end
+
+ context 'when failure_reason is script_failure' do
+ let(:reason) { :script_failure }
+
+ it { is_expected.to be_script_failure }
+ end
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index dfbe1a7c192..fb5fb7daaab 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -66,56 +66,76 @@ describe Issuable do
end
describe ".search" do
- let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
+ let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
- it 'returns notes with a matching title' do
+ it 'returns issues with a matching title' do
expect(issuable_class.search(searchable_issue.title))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching title' do
+ it 'returns issues with a partially matching title' do
expect(issuable_class.search('able')).to eq([searchable_issue])
end
- it 'returns notes with a matching title regardless of the casing' do
+ it 'returns issues with a matching title regardless of the casing' do
expect(issuable_class.search(searchable_issue.title.upcase))
.to eq([searchable_issue])
end
+
+ it 'returns issues with a fuzzy matching title' do
+ expect(issuable_class.search('searchable issue')).to eq([searchable_issue])
+ end
+
+ it 'returns all issues with a query shorter than 3 chars' do
+ expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ end
end
describe ".full_search" do
let!(:searchable_issue) do
- create(:issue, title: "Searchable issue", description: 'kittens')
+ create(:issue, title: "Searchable awesome issue", description: 'Many cute kittens')
end
- it 'returns notes with a matching title' do
+ it 'returns issues with a matching title' do
expect(issuable_class.full_search(searchable_issue.title))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching title' do
+ it 'returns issues with a partially matching title' do
expect(issuable_class.full_search('able')).to eq([searchable_issue])
end
- it 'returns notes with a matching title regardless of the casing' do
+ it 'returns issues with a matching title regardless of the casing' do
expect(issuable_class.full_search(searchable_issue.title.upcase))
.to eq([searchable_issue])
end
- it 'returns notes with a matching description' do
+ it 'returns issues with a fuzzy matching title' do
+ expect(issuable_class.full_search('searchable issue')).to eq([searchable_issue])
+ end
+
+ it 'returns issues with a matching description' do
expect(issuable_class.full_search(searchable_issue.description))
.to eq([searchable_issue])
end
- it 'returns notes with a partially matching description' do
+ it 'returns issues with a partially matching description' do
expect(issuable_class.full_search(searchable_issue.description))
.to eq([searchable_issue])
end
- it 'returns notes with a matching description regardless of the casing' do
+ it 'returns issues with a matching description regardless of the casing' do
expect(issuable_class.full_search(searchable_issue.description.upcase))
.to eq([searchable_issue])
end
+
+ it 'returns issues with a fuzzy matching description' do
+ expect(issuable_class.full_search('many kittens')).to eq([searchable_issue])
+ end
+
+ it 'returns all issues with a query shorter than 3 chars' do
+ expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ end
end
describe '.to_ability_name' do
@@ -460,4 +480,71 @@ describe Issuable do
end
end
end
+
+ describe '#first_contribution?' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:other_project) { create(:project) }
+ let(:owner) { create(:owner) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:contributor) { create(:user) }
+ let(:first_time_contributor) { create(:user) }
+
+ before do
+ group.add_owner(owner)
+ project.add_master(master)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ project.add_guest(contributor)
+ project.add_guest(first_time_contributor)
+ end
+
+ let(:merged_mr) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) }
+ let(:open_mr) { create(:merge_request, author: first_time_contributor, target_project: project, source_project: project) }
+ let(:merged_mr_other_project) { create(:merge_request, :merged, author: first_time_contributor, target_project: other_project, source_project: other_project) }
+
+ context "for merge requests" do
+ it "is false for MASTER" do
+ mr = create(:merge_request, author: master, target_project: project, source_project: project)
+
+ expect(mr).not_to be_first_contribution
+ end
+
+ it "is false for OWNER" do
+ mr = create(:merge_request, author: owner, target_project: project, source_project: project)
+
+ expect(mr).not_to be_first_contribution
+ end
+
+ it "is false for REPORTER" do
+ mr = create(:merge_request, author: reporter, target_project: project, source_project: project)
+
+ expect(mr).not_to be_first_contribution
+ end
+
+ it "is true when you don't have any merged MR" do
+ expect(open_mr).to be_first_contribution
+ expect(merged_mr).not_to be_first_contribution
+ end
+
+ it "handles multiple projects separately" do
+ expect(open_mr).to be_first_contribution
+ expect(merged_mr_other_project).not_to be_first_contribution
+ end
+ end
+
+ context "for issues" do
+ let(:contributor_issue) { create(:issue, author: contributor, project: project) }
+ let(:first_time_contributor_issue) { create(:issue, author: first_time_contributor, project: project) }
+
+ it "is false even without merged MR" do
+ expect(merged_mr).to be
+ expect(first_time_contributor_issue).not_to be_first_contribution
+ expect(contributor_issue).not_to be_first_contribution
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index d00faa4f8be..91591017587 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -189,8 +189,8 @@ describe Note, ResolvableNote do
allow(subject).to receive(:resolvable?).and_return(false)
end
- it "returns nil" do
- expect(subject.resolve!(current_user)).to be_nil
+ it "returns false" do
+ expect(subject.resolve!(current_user)).to be_falsey
end
it "doesn't set resolved_at" do
@@ -224,8 +224,8 @@ describe Note, ResolvableNote do
subject.resolve!(user)
end
- it "returns nil" do
- expect(subject.resolve!(current_user)).to be_nil
+ it "returns false" do
+ expect(subject.resolve!(current_user)).to be_falsey
end
it "doesn't change resolved_at" do
@@ -279,8 +279,8 @@ describe Note, ResolvableNote do
allow(subject).to receive(:resolvable?).and_return(false)
end
- it "returns nil" do
- expect(subject.unresolve!).to be_nil
+ it "returns false" do
+ expect(subject.unresolve!).to be_falsey
end
end
@@ -320,8 +320,8 @@ describe Note, ResolvableNote do
end
context "when not resolved" do
- it "returns nil" do
- expect(subject.unresolve!).to be_nil
+ it "returns false" do
+ expect(subject.unresolve!).to be_falsey
end
end
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index e48f20bf53b..9c99c3e5c08 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -99,14 +99,14 @@ describe GpgKey do
end
describe '#verified?' do
- it 'returns true one of the email addresses in the key belongs to the user' do
+ it 'returns true if one of the email addresses in the key belongs to the user' do
user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_truthy
end
- it 'returns false if one of the email addresses in the key does not belong to the user' do
+ it 'returns false if none of the email addresses in the key does not belong to the user' do
user = create :user, email: 'someone.else@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
@@ -114,6 +114,32 @@ describe GpgKey do
end
end
+ describe 'verified_and_belongs_to_email?' do
+ it 'returns false if none of the email addresses in the key does not belong to the user' do
+ user = create :user, email: 'someone.else@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_falsey
+ expect(gpg_key.verified_and_belongs_to_email?('someone.else@example.com')).to be_falsey
+ end
+
+ it 'returns false if one of the email addresses in the key belongs to the user and does not match the provided email' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_truthy
+ expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.net')).to be_falsey
+ end
+
+ it 'returns true if one of the email addresses in the key belongs to the user and matches the provided email' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_truthy
+ expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.com')).to be_truthy
+ end
+ end
+
describe 'notification', :mailer do
let(:user) { create(:user) }
@@ -129,15 +155,15 @@ describe GpgKey do
describe '#revoke' do
it 'invalidates all associated gpg signatures and destroys the key' do
gpg_key = create :gpg_key
- gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: gpg_key
+ gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: gpg_key
unrelated_gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key
- unrelated_gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: unrelated_gpg_key
+ unrelated_gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: unrelated_gpg_key
gpg_key.revoke
expect(gpg_signature.reload).to have_attributes(
- valid_signature: false,
+ verification_status: 'unknown_key',
gpg_key: nil
)
@@ -145,7 +171,7 @@ describe GpgKey do
# unrelated signature is left untouched
expect(unrelated_gpg_signature.reload).to have_attributes(
- valid_signature: true,
+ verification_status: 'verified',
gpg_key: unrelated_gpg_key
)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index f9cd12c0ff3..f36d6eeb327 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -9,6 +9,7 @@ describe Group do
it { is_expected.to have_many(:users).through(:group_members) }
it { is_expected.to have_many(:owners).through(:group_members) }
it { is_expected.to have_many(:requesters).dependent(:destroy) }
+ it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
@@ -25,22 +26,8 @@ describe Group do
group.add_developer(developer)
end
- describe '#members' do
- it 'includes members and exclude requesters' do
- member_user_ids = group.members.pluck(:user_id)
-
- expect(member_user_ids).to include(developer.id)
- expect(member_user_ids).not_to include(requester.id)
- end
- end
-
- describe '#requesters' do
- it 'does not include requesters' do
- requester_user_ids = group.requesters.pluck(:user_id)
-
- expect(requester_user_ids).to include(requester.id)
- expect(requester_user_ids).not_to include(developer.id)
- end
+ it_behaves_like 'members and requesters associations' do
+ let(:namespace) { group }
end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 87513e18b25..a07ce05a865 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -409,6 +409,15 @@ describe Member do
expect(members).to be_a Array
expect(members).to be_empty
end
+
+ it 'supports differents formats' do
+ list = ['joe@local.test', admin, user1.id, user2.id.to_s]
+
+ members = described_class.add_users(source, list, :master)
+
+ expect(members.size).to eq(4)
+ expect(members.first).to be_invite
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f5d079c27c4..d80d5657c42 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1262,7 +1262,6 @@ describe MergeRequest do
describe "#reload_diff" do
let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
-
let(:commit) { subject.project.commit(sample_commit.id) }
it "does not change existing merge request diff" do
@@ -1280,9 +1279,19 @@ describe MergeRequest do
subject.reload_diff
end
- it "updates diff discussion positions" do
- old_diff_refs = subject.diff_refs
+ it "calls update_diff_discussion_positions" do
+ expect(subject).to receive(:update_diff_discussion_positions)
+
+ subject.reload_diff
+ end
+ end
+ describe '#update_diff_discussion_positions' do
+ let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
+ let(:commit) { subject.project.commit(sample_commit.id) }
+ let(:old_diff_refs) { subject.diff_refs }
+
+ before do
# Update merge_request_diff so that #diff_refs will return commit.diff_refs
allow(subject).to receive(:create_merge_request_diff) do
subject.merge_request_diffs.create(
@@ -1293,7 +1302,9 @@ describe MergeRequest do
subject.merge_request_diff(true)
end
+ end
+ it "updates diff discussion positions" do
expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
subject.project,
subject.author,
@@ -1305,7 +1316,26 @@ describe MergeRequest do
expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
expect_any_instance_of(DiffNote).to receive(:save).once
- subject.reload_diff(subject.author)
+ subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
+ new_diff_refs: commit.diff_refs,
+ current_user: subject.author)
+ end
+
+ context 'when resolve_outdated_diff_discussions is set' do
+ before do
+ discussion
+
+ subject.project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'calls MergeRequests::ResolvedDiscussionNotificationService' do
+ expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService)
+ .to receive(:execute).with(subject)
+
+ subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
+ new_diff_refs: commit.diff_refs,
+ current_user: subject.author)
+ end
end
end
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 08cb7c4c1b1..ca13af4d73e 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -1,14 +1,23 @@
require 'spec_helper'
describe ProjectAutoDevops do
- subject { build_stubbed(:project_auto_devops) }
+ set(:project) { build(:project) }
it { is_expected.to belong_to(:project) }
- it { is_expected.to validate_presence_of(:domain) }
-
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
- it { is_expected.to be_enabled }
+ describe 'variables' do
+ let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
+
+ context 'when domain is defined' do
+ let(:domain) { 'example.com' }
+
+ it 'returns AUTO_DEVOPS_DOMAIN' do
+ expect(auto_devops.variables).to include(
+ { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index b1743cd608e..537cdadd528 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -203,18 +203,13 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
describe '#predefined_variables' do
let(:kubeconfig) do
- config =
- YAML.load(File.read(expand_fixture_path('config/kubeconfig.yml')))
-
- config.dig('users', 0, 'user')['token'] =
- 'token'
-
+ config_file = expand_fixture_path('config/kubeconfig.yml')
+ config = YAML.load(File.read(config_file))
+ config.dig('users', 0, 'user')['token'] = 'token'
+ config.dig('contexts', 0, 'context')['namespace'] = namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.encode64('CA PEM DATA')
- config.dig('contexts', 0, 'context')['namespace'] =
- namespace
-
YAML.dump(config)
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d41d7150922..75c99b62150 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -75,6 +75,7 @@ describe Project do
it { is_expected.to have_many(:forks).through(:forked_project_links) }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_many(:pipeline_schedules) }
+ it { is_expected.to have_many(:members_and_requesters) }
context 'after initialized' do
it "has a project_feature" do
@@ -91,22 +92,8 @@ describe Project do
project.team << [developer, :developer]
end
- describe '#members' do
- it 'includes members and exclude requesters' do
- member_user_ids = project.members.pluck(:user_id)
-
- expect(member_user_ids).to include(developer.id)
- expect(member_user_ids).not_to include(requester.id)
- end
- end
-
- describe '#requesters' do
- it 'does not include requesters' do
- requester_user_ids = project.requesters.pluck(:user_id)
-
- expect(requester_user_ids).to include(requester.id)
- expect(requester_user_ids).not_to include(developer.id)
- end
+ it_behaves_like 'members and requesters associations' do
+ let(:namespace) { project }
end
end
@@ -2522,4 +2509,133 @@ describe Project do
end
end
end
+
+ describe '#has_ci?' do
+ set(:project) { create(:project) }
+ let(:repository) { double }
+
+ before do
+ expect(project).to receive(:repository) { repository }
+ end
+
+ context 'when has .gitlab-ci.yml' do
+ before do
+ expect(repository).to receive(:gitlab_ci_yml) { 'content' }
+ end
+
+ it "CI is available" do
+ expect(project).to have_ci
+ end
+ end
+
+ context 'when there is no .gitlab-ci.yml' do
+ before do
+ expect(repository).to receive(:gitlab_ci_yml) { nil }
+ end
+
+ it "CI is not available" do
+ expect(project).not_to have_ci
+ end
+
+ context 'when auto devops is enabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it "CI is available" do
+ expect(project).to have_ci
+ end
+ end
+ end
+ end
+
+ describe '#auto_devops_enabled?' do
+ set(:project) { create(:project) }
+
+ subject { project.auto_devops_enabled? }
+
+ context 'when enabled in settings' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it 'auto devops is implicitly enabled' do
+ expect(project.auto_devops).to be_nil
+ expect(project).to be_auto_devops_enabled
+ end
+
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project)
+ end
+
+ it "auto devops is enabled" do
+ expect(project).to be_auto_devops_enabled
+ end
+ end
+
+ context 'when explicitly disabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: false)
+ end
+
+ it "auto devops is disabled" do
+ expect(project).not_to be_auto_devops_enabled
+ end
+ end
+ end
+
+ context 'when disabled in settings' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it 'auto devops is implicitly disabled' do
+ expect(project.auto_devops).to be_nil
+ expect(project).not_to be_auto_devops_enabled
+ end
+
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project)
+ end
+
+ it "auto devops is enabled" do
+ expect(project).to be_auto_devops_enabled
+ end
+ end
+ end
+ end
+
+ context '#auto_devops_variables' do
+ set(:project) { create(:project) }
+
+ subject { project.auto_devops_variables }
+
+ context 'when enabled in settings' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ context 'when domain is empty' do
+ before do
+ create(:project_auto_devops, project: project, domain: nil)
+ end
+
+ it 'variables are empty' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when domain is configured' do
+ before do
+ create(:project_auto_devops, project: project, domain: 'example.com')
+ end
+
+ it "variables are not empty" do
+ is_expected.not_to be_empty
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 40875c8fb7e..7065d467ec0 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -886,7 +886,7 @@ describe Repository, models: true do
context 'when pre hooks were successful' do
it 'runs without errors' do
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
- .with(committer, repository, old_rev, blank_sha, 'refs/heads/feature')
+ .with(committer, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
end
@@ -932,20 +932,20 @@ describe Repository, models: true do
service = Gitlab::Git::HooksService.new
expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
- .with(committer, target_repository, old_rev, new_rev, updating_ref)
+ .with(committer, target_repository.raw_repository, old_rev, new_rev, updating_ref)
.and_yield(service).and_return(true)
end
it 'runs without errors' do
expect do
- GitOperationService.new(committer, repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
end.not_to raise_error
end
it 'ensures the autocrlf Git option is set to :input' do
- service = GitOperationService.new(committer, repository)
+ service = Gitlab::Git::OperationService.new(committer, repository.raw_repository)
expect(service).to receive(:update_autocrlf_option)
@@ -956,7 +956,7 @@ describe Repository, models: true do
it 'updates the head' do
expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
- GitOperationService.new(committer, repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
@@ -971,13 +971,13 @@ describe Repository, models: true do
let(:updating_ref) { 'refs/heads/master' }
it 'fetch_ref and create the branch' do
- expect(target_project.repository).to receive(:fetch_ref)
+ expect(target_project.repository.raw_repository).to receive(:fetch_ref)
.and_call_original
- GitOperationService.new(committer, target_repository)
+ Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
.with_branch(
'master',
- start_project: project,
+ start_repository: project.repository.raw_repository,
start_branch_name: 'feature') { new_rev }
expect(target_repository.branch_names).to contain_exactly('master')
@@ -990,8 +990,8 @@ describe Repository, models: true do
it 'does not fetch_ref and just pass the commit' do
expect(target_repository).not_to receive(:fetch_ref)
- GitOperationService.new(committer, target_repository)
- .with_branch('feature', start_project: project) { new_rev }
+ Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
+ .with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
end
end
end
@@ -1000,7 +1000,7 @@ describe Repository, models: true do
let(:target_project) { create(:project, :empty_repo) }
before do
- expect(target_project.repository).to receive(:run_git)
+ expect(target_project.repository.raw_repository).to receive(:run_git)
end
it 'raises Rugged::ReferenceError' do
@@ -1009,9 +1009,9 @@ describe Repository, models: true do
end
expect do
- GitOperationService.new(committer, target_project.repository)
+ Gitlab::Git::OperationService.new(committer, target_project.repository.raw_repository)
.with_branch('feature',
- start_project: project,
+ start_repository: project.repository.raw_repository,
&:itself)
end.to raise_reference_error
end
@@ -1031,7 +1031,7 @@ describe Repository, models: true do
repository.add_branch(user, branch, old_rev)
expect do
- GitOperationService.new(committer, repository).with_branch(branch) do
+ Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
new_rev
end
end.not_to raise_error
@@ -1049,10 +1049,10 @@ describe Repository, models: true do
# Updating 'master' to new_rev would lose the commits on 'master' that
# are not contained in new_rev. This should not be allowed.
expect do
- GitOperationService.new(committer, repository).with_branch(branch) do
+ Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
new_rev
end
- end.to raise_error(Repository::CommitError)
+ end.to raise_error(Gitlab::Git::CommitError)
end
end
@@ -1061,7 +1061,7 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
- GitOperationService.new(committer, repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
@@ -1079,10 +1079,9 @@ describe Repository, models: true do
expect(repository).not_to receive(:expire_emptiness_caches)
expect(repository).to receive(:expire_branches_cache)
- GitOperationService.new(committer, repository)
- .with_branch('new-feature') do
- new_rev
- end
+ repository.with_branch(user, 'new-feature') do
+ new_rev
+ end
end
end
@@ -1139,7 +1138,7 @@ describe Repository, models: true do
describe 'when there are no branches' do
before do
- allow(repository).to receive(:branch_count).and_return(0)
+ allow(repository.raw_repository).to receive(:branch_count).and_return(0)
end
it { is_expected.to eq(false) }
@@ -1147,7 +1146,7 @@ describe Repository, models: true do
describe 'when there are branches' do
it 'returns true' do
- expect(repository).to receive(:branch_count).and_return(3)
+ expect(repository.raw_repository).to receive(:branch_count).and_return(3)
expect(subject).to eq(true)
end
@@ -1161,7 +1160,7 @@ describe Repository, models: true do
end
it 'sets autocrlf to :input' do
- GitOperationService.new(nil, repository).send(:update_autocrlf_option)
+ Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
expect(repository.raw_repository.autocrlf).to eq(:input)
end
@@ -1176,7 +1175,7 @@ describe Repository, models: true do
expect(repository.raw_repository).not_to receive(:autocrlf=)
.with(:input)
- GitOperationService.new(nil, repository).send(:update_autocrlf_option)
+ Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
end
end
end
@@ -1762,15 +1761,15 @@ describe Repository, models: true do
describe '#update_ref' do
it 'can create a ref' do
- GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+ Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
expect(repository.find_branch('foobar')).not_to be_nil
end
it 'raises CommitError when the ref update fails' do
expect do
- GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
- end.to raise_error(Repository::CommitError)
+ Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+ end.to raise_error(Gitlab::Git::CommitError)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b70ab5581ac..abf732e60bf 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2102,4 +2102,84 @@ describe User do
end
end
end
+
+ describe '#verified_email?' do
+ it 'returns true when the email is the primary email' do
+ user = build :user, email: 'email@example.com'
+
+ expect(user.verified_email?('email@example.com')).to be true
+ end
+
+ it 'returns false when the email is not the primary email' do
+ user = build :user, email: 'email@example.com'
+
+ expect(user.verified_email?('other_email@example.com')).to be false
+ end
+ end
+
+ describe '#sync_attribute?' do
+ let(:user) { described_class.new }
+
+ context 'oauth user' do
+ it 'returns true if name can be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(name location))
+ expect(user.sync_attribute?(:name)).to be_truthy
+ end
+
+ it 'returns true if email can be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(name email))
+ expect(user.sync_attribute?(:email)).to be_truthy
+ end
+
+ it 'returns true if location can be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(location email))
+ expect(user.sync_attribute?(:email)).to be_truthy
+ end
+
+ it 'returns false if name can not be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(location email))
+ expect(user.sync_attribute?(:name)).to be_falsey
+ end
+
+ it 'returns false if email can not be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(location email))
+ expect(user.sync_attribute?(:name)).to be_falsey
+ end
+
+ it 'returns false if location can not be synced' do
+ stub_omniauth_setting(sync_profile_attributes: %w(location email))
+ expect(user.sync_attribute?(:name)).to be_falsey
+ end
+
+ it 'returns true for all syncable attributes if all syncable attributes can be synced' do
+ stub_omniauth_setting(sync_profile_attributes: true)
+ expect(user.sync_attribute?(:name)).to be_truthy
+ expect(user.sync_attribute?(:email)).to be_truthy
+ expect(user.sync_attribute?(:location)).to be_truthy
+ end
+
+ it 'returns false for all syncable attributes but email if no syncable attributes are declared' do
+ expect(user.sync_attribute?(:name)).to be_falsey
+ expect(user.sync_attribute?(:email)).to be_truthy
+ expect(user.sync_attribute?(:location)).to be_falsey
+ end
+ end
+
+ context 'ldap user' do
+ it 'returns true for email if ldap user' do
+ allow(user).to receive(:ldap_user?).and_return(true)
+ expect(user.sync_attribute?(:name)).to be_falsey
+ expect(user.sync_attribute?(:email)).to be_truthy
+ expect(user.sync_attribute?(:location)).to be_falsey
+ end
+
+ it 'returns true for email and location if ldap user and location declared as syncable' do
+ allow(user).to receive(:ldap_user?).and_return(true)
+ stub_omniauth_setting(sync_profile_attributes: %w(location))
+ expect(user.sync_attribute?(:name)).to be_falsey
+ expect(user.sync_attribute?(:email)).to be_truthy
+ expect(user.sync_attribute?(:location)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index a7a34ecac72..1a8001be6ab 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -100,4 +100,38 @@ describe Ci::BuildPresenter do
end
end
end
+
+ describe '#trigger_variables' do
+ let(:build) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
+
+ context 'when variable is stored in ci_pipeline_variables' do
+ let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
+
+ context 'when pipeline is triggered by trigger API' do
+ it 'returns variables' do
+ expect(presenter.trigger_variables).to eq([pipeline_variable.to_runner_variable])
+ end
+ end
+
+ context 'when pipeline is not triggered by trigger API' do
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not return variables' do
+ expect(presenter.trigger_variables).to eq([])
+ end
+ end
+ end
+
+ context 'when variable is stored in ci_trigger_requests.variables' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+ end
+
+ it 'returns variables' do
+ expect(presenter.trigger_variables).to eq(trigger_request.user_variables)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index b1e011de604..cc794fad3a7 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -75,6 +75,22 @@ describe API::Branches do
let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}" }
shared_examples_for 'repository branch' do
+ context 'HEAD request' do
+ it 'returns 204 No Content' do
+ head api(route, user)
+
+ expect(response).to have_gitlab_http_status(204)
+ expect(response.body).to be_empty
+ end
+
+ it 'returns 404 Not Found' do
+ head api("/projects/#{project_id}/repository/branches/unknown", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_empty
+ end
+ end
+
it 'returns the repository branch' do
get api(route, current_user)
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 3c02e6302b4..e4c73583545 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -16,8 +16,8 @@ describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master') }
- let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop') }
+ let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master', protected: false) }
+ let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop', protected: false) }
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
@@ -142,6 +142,9 @@ describe API::CommitStatuses do
expect(json_response['ref']).not_to be_empty
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
+ if status == 'failed'
+ expect(CommitStatus.find(json_response['id'])).to be_api_failure
+ end
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index d3b48f948f6..f663719d28c 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -565,7 +565,7 @@ describe API::Commits do
end
context 'when the ref has a pipeline' do
- let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha) }
+ let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha, protected: false) }
it 'includes a "created" status' do
get api(route, current_user)
@@ -673,6 +673,12 @@ describe API::Commits do
it_behaves_like 'ref diff'
end
end
+
+ context 'when binary diff are treated as text' do
+ let(:commit_id) { TestEnv::BRANCH_SHA['add-pdf-text-binary'] }
+
+ it_behaves_like 'ref diff'
+ end
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 971eaf837cb..114019441a3 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -224,7 +224,7 @@ describe API::Files do
it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:create_file)
- .and_raise(Repository::CommitError, 'Cannot create file')
+ .and_raise(Gitlab::Git::CommitError, 'Cannot create file')
post api(route("any%2Etxt"), user), valid_params
@@ -339,7 +339,7 @@ describe API::Files do
end
it "returns a 400 if fails to delete file" do
- allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Repository::CommitError, 'Cannot delete file')
+ allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file')
delete api(route(file_path), user), valid_params
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index a6c804fb2b3..1274e66bb4c 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,13 +5,26 @@ describe API::Internal do
let(:key) { create(:key, user: user) }
let(:project) { create(:project, :repository) }
let(:secret_token) { Gitlab::Shell.secret_token }
+ let(:gl_repository) { "project-#{project.id}" }
+ let(:reference_counter) { double('ReferenceCounter') }
describe "GET /internal/check" do
it do
+ expect_any_instance_of(Redis).to receive(:ping).and_return('PONG')
+
get api("/internal/check"), secret_token: secret_token
expect(response).to have_http_status(200)
expect(json_response['api_version']).to eq(API::API.version)
+ expect(json_response['redis']).to be(true)
+ end
+
+ it 'returns false for field `redis` when redis is unavailable' do
+ expect_any_instance_of(Redis).to receive(:ping).and_raise(Errno::ENOENT)
+
+ get api("/internal/check"), secret_token: secret_token
+
+ expect(json_response['redis']).to be(false)
end
end
@@ -661,9 +674,7 @@ describe API::Internal do
# end
describe 'POST /internal/post_receive' do
- let(:gl_repository) { "project-#{project.id}" }
let(:identifier) { 'key-123' }
- let(:reference_counter) { double('ReferenceCounter') }
let(:valid_params) do
{
@@ -749,6 +760,22 @@ describe API::Internal do
end
end
+ describe 'POST /internal/pre_receive' do
+ let(:valid_params) do
+ { gl_repository: gl_repository, secret_token: secret_token }
+ end
+
+ it 'decreases the reference counter and returns the result' do
+ expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository)
+ .and_return(reference_counter)
+ expect(reference_counter).to receive(:increase).and_return(true)
+
+ post api("/internal/pre_receive"), valid_params
+
+ expect(json_response['reference_counter_increased']).to be(true)
+ end
+ end
+
def project_with_repo_path(path)
double().tap do |fake_project|
allow(fake_project).to receive_message_chain('repository.path_to_repo' => path)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index dee75c96b86..1583d1c2435 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -138,6 +138,16 @@ describe API::Issues, :mailer do
expect(first_issue['id']).to eq(issue2.id)
end
+ it 'returns issues reacted by the authenticated user by the given emoji' do
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
+ award_emoji = create(:award_emoji, awardable: issue2, user: user2, name: 'star')
+
+ get api('/issues', user2), my_reaction_emoji: award_emoji.name, scope: 'all'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
it 'returns issues matching given search string for title' do
get api("/issues", user), search: issue.title
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index f56baf9663d..2d7cc1a1798 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe API::Jobs do
- let!(:project) do
+ set(:project) do
create(:project, :repository, public_builds: false)
end
- let!(:pipeline) do
+ set(:pipeline) do
create(:ci_empty_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch)
@@ -188,6 +188,84 @@ describe API::Jobs do
end
end
+ describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ let(:artifact) do
+ 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ context 'when project is public' do
+ it 'allows to access artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when project is public with builds access disabled' do
+ it 'rejects access to artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, false)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when project is private' do
+ it 'rejects access and hides existence of artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PRIVATE)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when user is authorized' do
+ it 'returns a specific artifact file for a valid path' do
+ expect(Gitlab::Workhorse)
+ .to receive(:send_artifacts_entry)
+ .and_call_original
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_http_status(200)
+ expect(response.headers)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ it 'does not return job artifact file' do
+ get_artifact_file('some/artifact')
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ def get_artifact_file(artifact_path)
+ get api("/projects/#{project.id}/jobs/#{job.id}/" \
+ "artifacts/#{artifact_path}", api_user)
+ end
+ end
+
describe 'GET /projects/:id/jobs/:job_id/artifacts' do
before do
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
@@ -209,11 +287,12 @@ describe API::Jobs do
end
end
- context 'unauthorized user' do
+ context 'when anonymous user is accessing private artifacts' do
let(:api_user) { nil }
- it 'does not return specific job artifacts' do
- expect(response).to have_http_status(401)
+ it 'hides artifacts and rejects request' do
+ expect(project).to be_private
+ expect(response).to have_http_status(404)
end
end
end
@@ -242,8 +321,9 @@ describe API::Jobs do
get_for_ref
end
- it 'gives 401' do
- expect(response).to have_http_status(401)
+ it 'does not find a resource in a private project' do
+ expect(project).to be_private
+ expect(response).to have_http_status(404)
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 9027090aabd..21d2c9644fb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -117,6 +117,18 @@ describe API::MergeRequests do
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
end
+
+ it 'returns merge requests reacted by the authenticated user by the given emoji' do
+ merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star')
+
+ get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request3.id)
+ end
end
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index b6a5a7ffbb5..f650df57383 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::PipelineSchedules do
set(:developer) { create(:user) }
set(:user) { create(:user) }
- set(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository, public_builds: false) }
before do
project.add_developer(developer)
@@ -110,6 +110,18 @@ describe API::PipelineSchedules do
end
end
+ context 'authenticated user with insufficient permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
@@ -299,4 +311,150 @@ describe API::PipelineSchedules do
end
end
end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ let(:params) { attributes_for(:ci_pipeline_schedule_variable) }
+
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule_variable' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params
+ end.to change { pipeline_schedule.variables.count }.by(1)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['key']).to eq(params[:key])
+ expect(json_response['value']).to eq(params[:value])
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+
+ context 'when key has validation error' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
+ params.merge('key' => '!?!?')
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('key')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule_variable' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
+ value: 'updated_value'
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ expect(json_response['value']).to eq('updated_value')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule_variable' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ let(:master) { create(:user) }
+
+ set(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable, pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ project.add_master(master)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule_variable' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", master)
+ end.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+
+ expect(response).to have_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule_variable')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/____", master)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master) }
+
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule_variable' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4490e50702b..f771e4fa4ff 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -414,6 +414,7 @@ describe API::Projects do
jobs_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
+ resolve_outdated_diff_discussions: false,
only_allow_merge_if_pipeline_succeeds: false,
request_access_enabled: true,
only_allow_merge_if_all_discussions_are_resolved: false,
@@ -477,20 +478,40 @@ describe API::Projects do
expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
end
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: false)
+
+ post api('/projects', user), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
+ end
+
+ it 'sets a project as allowing outdated diff discussions to automatically resolve if resolve_outdated_diff_discussions' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: true)
+
+ post api('/projects', user), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
+ end
+
it 'sets a project as allowing merge even if build fails' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
+
post api('/projects', user), project
+
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
+
post api('/projects', user), project
+
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
post api('/projects', user), project
@@ -506,7 +527,7 @@ describe API::Projects do
end
it 'sets a project as allowing merge only if all discussions are resolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
post api('/projects', user), project
@@ -514,7 +535,7 @@ describe API::Projects do
end
it 'ignores import_url when it is nil' do
- project = attributes_for(:project, { import_url: nil })
+ project = attributes_for(:project, import_url: nil)
post api('/projects', user), project
@@ -642,20 +663,36 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: false)
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
+ end
+
+ it 'sets a project as allowing outdated diff discussions to automatically resolve' do
+ project = attributes_for(:project, resolve_outdated_diff_discussions: true)
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
+ end
+
it 'sets a project as allowing merge even if build fails' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
- it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
- project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
+ it 'sets a project as allowing merge only if pipeline succeeds' do
+ project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
post api("/projects/user/#{user.id}", admin), project
@@ -663,7 +700,7 @@ describe API::Projects do
end
it 'sets a project as allowing merge only if all discussions are resolved' do
- project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
+ project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
post api("/projects/user/#{user.id}", admin), project
@@ -732,6 +769,7 @@ describe API::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 993164aa8fe..12720355a6d 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -557,17 +557,36 @@ describe API::Runner do
{ 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
end
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
+
before do
- trigger = create(:ci_trigger, project: project)
- create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [job], trigger: trigger)
project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
- it 'returns variables for triggers' do
- request_job
+ shared_examples 'expected variables behavior' do
+ it 'returns variables for triggers' do
+ request_job
- expect(response).to have_http_status(201)
- expect(json_response['variables']).to include(*expected_variables)
+ expect(response).to have_http_status(201)
+ expect(json_response['variables']).to include(*expected_variables)
+ end
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
+ end
+
+ it_behaves_like 'expected variables behavior'
+ end
+
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
+ end
+
+ it_behaves_like 'expected variables behavior'
end
end
@@ -626,13 +645,34 @@ describe API::Runner do
it 'mark job as succeeded' do
update_job(state: 'success')
- expect(job.reload.status).to eq 'success'
+ job.reload
+ expect(job).to be_success
end
it 'mark job as failed' do
update_job(state: 'failed')
- expect(job.reload.status).to eq 'failed'
+ job.reload
+ expect(job).to be_failed
+ expect(job).to be_unknown_failure
+ end
+
+ context 'when failure_reason is script_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'script_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_script_failure }
+ end
+
+ context 'when failure_reason is runner_system_failure' do
+ before do
+ update_job(state: 'failed', failure_reason: 'runner_system_failure')
+ job.reload
+ end
+
+ it { expect(job).to be_runner_system_failure }
end
end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 244895a417e..67907579225 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -191,7 +191,8 @@ describe API::Runners do
active: !active,
tag_list: ['ruby2.1', 'pgsql', 'mysql'],
run_untagged: 'false',
- locked: 'true')
+ locked: 'true',
+ access_level: 'ref_protected')
shared_runner.reload
expect(response).to have_http_status(200)
@@ -200,6 +201,7 @@ describe API::Runners do
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
expect(shared_runner.run_untagged?).to be(false)
expect(shared_runner.locked?).to be(true)
+ expect(shared_runner.ref_protected?).to be_truthy
expect(shared_runner.ensure_runner_queue_value)
.not_to eq(runner_queue_value)
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5fef4437997..37cb95a16e3 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -4,6 +4,7 @@ describe API::Users do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) }
+ let(:gpg_key) { create(:gpg_key, user: user) }
let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
@@ -753,6 +754,164 @@ describe API::Users do
end
end
+ describe 'POST /users/:id/keys' do
+ before do
+ admin
+ end
+
+ it 'does not create invalid GPG key' do
+ post api("/users/#{user.id}/gpg_keys", admin)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('key is missing')
+ end
+
+ it 'creates GPG key' do
+ key_attrs = attributes_for :gpg_key
+ expect do
+ post api("/users/#{user.id}/gpg_keys", admin), key_attrs
+
+ expect(response).to have_http_status(201)
+ end.to change { user.gpg_keys.count }.by(1)
+ end
+
+ it 'returns 400 for invalid ID' do
+ post api('/users/999999/gpg_keys', admin)
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ describe 'GET /user/:id/gpg_keys' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api("/users/#{user.id}/gpg_keys")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get api('/users/999999/gpg_keys', admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ delete api("/users/#{user.id}/gpg_keys/42", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns array of GPG keys' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/users/#{user.id}/gpg_keys", admin)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['key']).to eq(gpg_key.key)
+ end
+ end
+ end
+
+ describe 'DELETE /user/:id/gpg_keys/:key_id' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete api("/users/#{user.id}/keys/42")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'deletes existing key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(204)
+ end.to change { user.gpg_keys.count }.by(-1)
+ end
+
+ it 'returns 404 error if user not found' do
+ user.keys << key
+ user.save
+
+ delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ delete api("/users/#{user.id}/gpg_keys/42", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+ end
+ end
+
+ describe 'POST /user/:id/gpg_keys/:key_id/revoke' do
+ before do
+ admin
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ post api("/users/#{user.id}/gpg_keys/42/revoke")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'revokes existing key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin)
+
+ expect(response).to have_http_status(:accepted)
+ end.to change { user.gpg_keys.count }.by(-1)
+ end
+
+ it 'returns 404 error if user not found' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not foud' do
+ post api("/users/#{user.id}/gpg_keys/42/revoke", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+ end
+ end
+
describe "POST /users/:id/emails" do
before do
admin
@@ -1153,6 +1312,173 @@ describe API::Users do
end
end
+ describe 'GET /user/gpg_keys' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/user/gpg_keys')
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns array of GPG keys' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api('/user/gpg_keys', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['key']).to eq(gpg_key.key)
+ end
+
+ context 'scopes' do
+ let(:path) { '/user/gpg_keys' }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
+ end
+ end
+
+ describe 'GET /user/gpg_keys/:key_id' do
+ it 'returns a single key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/user/gpg_keys/#{gpg_key.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['key']).to eq(gpg_key.key)
+ end
+
+ it 'returns 404 Not Found within invalid ID' do
+ get api('/user/gpg_keys/42', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it "returns 404 error if admin accesses user's GPG key" do
+ user.gpg_keys << gpg_key
+ user.save
+
+ get api("/user/gpg_keys/#{gpg_key.id}", admin)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 404 for invalid ID' do
+ get api('/users/gpg_keys/ASDF', admin)
+
+ expect(response).to have_http_status(404)
+ end
+
+ context 'scopes' do
+ let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
+ end
+
+ describe 'POST /user/gpg_keys' do
+ it 'creates a GPG key' do
+ key_attrs = attributes_for :gpg_key
+ expect do
+ post api('/user/gpg_keys', user), key_attrs
+
+ expect(response).to have_http_status(201)
+ end.to change { user.gpg_keys.count }.by(1)
+ end
+
+ it 'returns a 401 error if unauthorized' do
+ post api('/user/gpg_keys'), key: 'some key'
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'does not create GPG key without key' do
+ post api('/user/gpg_keys', user)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('key is missing')
+ end
+ end
+
+ describe 'POST /user/gpg_keys/:key_id/revoke' do
+ it 'revokes existing GPG key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ post api("/user/gpg_keys/#{gpg_key.id}/revoke", user)
+
+ expect(response).to have_http_status(:accepted)
+ end.to change { user.gpg_keys.count}.by(-1)
+ end
+
+ it 'returns 404 if key ID not found' do
+ post api('/user/gpg_keys/42/revoke', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 401 error if unauthorized' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ post api("/user/gpg_keys/#{gpg_key.id}/revoke")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 404 for invalid ID' do
+ post api('/users/gpg_keys/ASDF/revoke', admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'DELETE /user/gpg_keys/:key_id' do
+ it 'deletes existing GPG key' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ expect do
+ delete api("/user/gpg_keys/#{gpg_key.id}", user)
+
+ expect(response).to have_http_status(204)
+ end.to change { user.gpg_keys.count}.by(-1)
+ end
+
+ it 'returns 404 if key ID not found' do
+ delete api('/user/gpg_keys/42', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns 401 error if unauthorized' do
+ user.gpg_keys << gpg_key
+ user.save
+
+ delete api("/user/gpg_keys/#{gpg_key.id}")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 404 for invalid ID' do
+ delete api('/users/gpg_keys/ASDF', admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
describe "GET /user/emails" do
context "when unauthenticated" do
it "returns authentication error" do
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 8fb96b3c7c5..6d0ca33a6fa 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -386,7 +386,7 @@ describe API::V3::Commits do
end
it "returns status for CI" do
- pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
+ pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false)
pipeline.update(status: 'success')
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -396,7 +396,7 @@ describe API::V3::Commits do
end
it "returns status for CI when pipeline is created" do
- project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
+ project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false)
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 4ffa5d1784e..dc7f0eefd16 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -127,7 +127,7 @@ describe API::V3::Files do
it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:create_file)
- .and_raise(Repository::CommitError, 'Cannot create file')
+ .and_raise(Gitlab::Git::CommitError, 'Cannot create file')
post v3_api("/projects/#{project.id}/repository/files", user), valid_params
@@ -228,7 +228,7 @@ describe API::V3::Files do
end
it "returns a 400 if fails to delete file" do
- allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Repository::CommitError, 'Cannot delete file')
+ allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file')
delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index a514166274a..cae2c3118da 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -687,6 +687,7 @@ describe API::V3::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['builds_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index d4648136841..7ccf387f2dc 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -37,7 +37,7 @@ describe API::V3::Triggers do
it 'returns unauthorized if token is for different project' do
post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(404)
end
end
@@ -80,7 +80,8 @@ describe API::V3::Triggers do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
- expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
+ expect(pipeline.variables.map { |v| { v.key => v.value } }.first).to eq(variables)
+ expect(json_response['variables']).to eq(variables)
end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 4ba3dada37c..4775ba4c2d8 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::CreatePipelineService do
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
let(:user) { create(:admin) }
let(:ref_name) { 'refs/heads/master' }
@@ -391,12 +391,15 @@ describe Ci::CreatePipelineService do
end
context 'when user is master' do
+ let(:pipeline) { execute_service }
+
before do
project.add_master(user)
end
- it 'creates a pipeline' do
- expect(execute_service).to be_persisted
+ it 'creates a protected pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_protected
expect(Ci::Pipeline.count).to eq(1)
end
end
@@ -468,10 +471,11 @@ describe Ci::CreatePipelineService do
let(:user) {}
let(:trigger) { create(:ci_trigger, owner: nil) }
let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
+ let(:pipeline) { execute_service(trigger_request: trigger_request) }
- it 'creates a pipeline' do
- expect(execute_service(trigger_request: trigger_request))
- .to be_persisted
+ it 'creates an unprotected pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).not_to be_protected
expect(Ci::Pipeline.count).to eq(1)
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
deleted file mode 100644
index 8295813a1ca..00000000000
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateTriggerRequestService do
- let(:service) { described_class }
- let(:project) { create(:project, :repository) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
- let(:owner) { create(:user) }
-
- before do
- stub_ci_pipeline_to_return_yaml_file
-
- project.add_developer(owner)
- end
-
- describe '#execute' do
- context 'valid params' do
- subject { service.execute(project, trigger, 'master') }
-
- context 'without owner' do
- it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(subject.pipeline).to be_trigger }
- end
-
- context 'with owner' do
- it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.trigger_request.builds.first.user).to eq(owner) }
- it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(subject.pipeline).to be_trigger }
- it { expect(subject.pipeline.user).to eq(owner) }
- end
- end
-
- context 'no commit for ref' do
- subject { service.execute(project, trigger, 'other-branch') }
-
- it { expect(subject.pipeline).not_to be_persisted }
- end
-
- context 'no builds created' do
- subject { service.execute(project, trigger, 'master') }
-
- before do
- stub_ci_pipeline_yaml_file('script: { only: [develop], script: hello World }')
- end
-
- it { expect(subject.pipeline).not_to be_persisted }
- end
- end
-end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 8eb0d2d10a4..5ac30111ec9 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -4,7 +4,7 @@ module Ci
describe RegisterJobService do
let!(:project) { FactoryGirl.create :project, shared_runners_enabled: false }
let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
- let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline }
+ let!(:pending_job) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
@@ -15,32 +15,32 @@ module Ci
describe '#execute' do
context 'runner follow tag list' do
it "picks build with the same tag" do
- pending_build.tag_list = ["linux"]
- pending_build.save
+ pending_job.tag_list = ["linux"]
+ pending_job.save
specific_runner.tag_list = ["linux"]
- expect(execute(specific_runner)).to eq(pending_build)
+ expect(execute(specific_runner)).to eq(pending_job)
end
it "does not pick build with different tag" do
- pending_build.tag_list = ["linux"]
- pending_build.save
+ pending_job.tag_list = ["linux"]
+ pending_job.save
specific_runner.tag_list = ["win32"]
expect(execute(specific_runner)).to be_falsey
end
it "picks build without tag" do
- expect(execute(specific_runner)).to eq(pending_build)
+ expect(execute(specific_runner)).to eq(pending_job)
end
it "does not pick build with tag" do
- pending_build.tag_list = ["linux"]
- pending_build.save
+ pending_job.tag_list = ["linux"]
+ pending_job.save
expect(execute(specific_runner)).to be_falsey
end
it "pick build without tag" do
specific_runner.tag_list = ["win32"]
- expect(execute(specific_runner)).to eq(pending_build)
+ expect(execute(specific_runner)).to eq(pending_job)
end
end
@@ -76,7 +76,7 @@ module Ci
let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :project, shared_runners_enabled: true }
let!(:pipeline3) { create :ci_pipeline, project: project3 }
- let!(:build1_project1) { pending_build }
+ let!(:build1_project1) { pending_job }
let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
@@ -172,7 +172,7 @@ module Ci
context 'when first build is stalled' do
before do
- pending_build.lock_version = 10
+ pending_job.lock_version = 10
end
subject { described_class.new(specific_runner).execute }
@@ -182,7 +182,7 @@ module Ci
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
- .and_return([pending_build, other_build])
+ .and_return([pending_job, other_build])
end
it "receives second build from the queue" do
@@ -194,7 +194,7 @@ module Ci
context 'when single build is in queue' do
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
- .and_return([pending_build])
+ .and_return([pending_job])
end
it "does not receive any valid result" do
@@ -215,6 +215,70 @@ module Ci
end
end
+ context 'when access_level of runner is not_protected' do
+ let!(:specific_runner) { create(:ci_runner, :specific) }
+
+ context 'when a job is protected' do
+ let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
+
+ it 'picks the job' do
+ expect(execute(specific_runner)).to eq(pending_job)
+ end
+ end
+
+ context 'when a job is unprotected' do
+ let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'picks the job' do
+ expect(execute(specific_runner)).to eq(pending_job)
+ end
+ end
+
+ context 'when protected attribute of a job is nil' do
+ let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+
+ before do
+ pending_job.update_attribute(:protected, nil)
+ end
+
+ it 'picks the job' do
+ expect(execute(specific_runner)).to eq(pending_job)
+ end
+ end
+ end
+
+ context 'when access_level of runner is ref_protected' do
+ let!(:specific_runner) { create(:ci_runner, :ref_protected, :specific) }
+
+ context 'when a job is protected' do
+ let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
+
+ it 'picks the job' do
+ expect(execute(specific_runner)).to eq(pending_job)
+ end
+ end
+
+ context 'when a job is unprotected' do
+ let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not pick the job' do
+ expect(execute(specific_runner)).to be_nil
+ end
+ end
+
+ context 'when protected attribute of a job is nil' do
+ let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+
+ before do
+ pending_job.update_attribute(:protected, nil)
+ end
+
+ it 'does not pick the job' do
+ expect(execute(specific_runner)).to be_nil
+ end
+ end
+ end
+
def execute(runner)
described_class.new(runner).execute.build
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index cec667071cc..bbc3a8c79f5 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -22,7 +22,7 @@ describe Ci::RetryBuildService do
%i[type lock_version target_url base_tags
commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id
- user_id auto_canceled_by_id retried].freeze
+ user_id auto_canceled_by_id retried failure_reason].freeze
shared_examples 'build duplication' do
let(:stage) do
@@ -48,10 +48,21 @@ describe Ci::RetryBuildService do
describe 'clone accessors' do
CLONE_ACCESSORS.each do |attribute|
it "clones #{attribute} build attribute" do
- expect(new_build.send(attribute)).to be_present
+ expect(new_build.send(attribute)).not_to be_nil
expect(new_build.send(attribute)).to eq build.send(attribute)
end
end
+
+ context 'when job has nullified protected' do
+ before do
+ build.update_attribute(:protected, nil)
+ end
+
+ it "clones protected build attribute" do
+ expect(new_build.protected).to be_nil
+ expect(new_build.protected).to eq build.protected
+ end
+ end
end
describe 'reject acessors' do
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index c239494298b..82b156f5ebe 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -150,21 +150,7 @@ describe Discussions::UpdateDiffPositionService do
)
end
- context "when the diff line is the same" do
- let(:line) { 16 }
-
- it "updates the position" do
- subject.execute(discussion)
-
- expect(discussion.original_position).to eq(old_position)
- expect(discussion.position).not_to eq(old_position)
- expect(discussion.position.new_line).to eq(22)
- end
- end
-
- context "when the diff line has changed" do
- let(:line) { 9 }
-
+ shared_examples 'outdated diff note' do
it "doesn't update the position" do
subject.execute(discussion)
@@ -189,5 +175,51 @@ describe Discussions::UpdateDiffPositionService do
subject.execute(discussion)
end
end
+
+ context "when the diff line is the same" do
+ let(:line) { 16 }
+
+ it "updates the position" do
+ subject.execute(discussion)
+
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).not_to eq(old_position)
+ expect(discussion.position.new_line).to eq(22)
+ end
+
+ context 'when the resolve_outdated_diff_discussions setting is set' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'does not resolve the discussion' do
+ subject.execute(discussion)
+
+ expect(discussion).not_to be_resolved
+ expect(discussion).not_to be_resolved_by_push
+ end
+ end
+ end
+
+ context "when the diff line has changed" do
+ let(:line) { 9 }
+
+ include_examples 'outdated diff note'
+
+ context 'when the resolve_outdated_diff_discussions setting is set' do
+ before do
+ project.update!(resolve_outdated_diff_discussions: true)
+ end
+
+ it 'sets resolves the discussion and sets resolved_by_push' do
+ subject.execute(discussion)
+
+ expect(discussion).to be_resolved
+ expect(discussion).to be_resolved_by_push
+ end
+
+ include_examples 'outdated diff note'
+ end
+ end
end
end
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index aa6ad6340f5..031366d1825 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -116,6 +116,7 @@ describe Projects::UpdatePagesService do
expect(deploy_status.description)
.to match(/artifacts for pages are too large/)
+ expect(deploy_status).to be_script_failure
end
end
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 39586d37e93..934b4557ba2 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -80,7 +80,8 @@ module CycleAnalyticsHelpers
sha: project.repository.commit('master').sha,
ref: 'master',
source: :push,
- project: project)
+ project: project,
+ protected: false)
end
def new_dummy_job(environment)
@@ -93,7 +94,8 @@ module CycleAnalyticsHelpers
ref: 'master',
tag: false,
name: 'dummy',
- pipeline: dummy_pipeline)
+ pipeline: dummy_pipeline,
+ protected: false)
end
end
diff --git a/spec/support/group_members_shared_example.rb b/spec/support/group_members_shared_example.rb
new file mode 100644
index 00000000000..547c83c7955
--- /dev/null
+++ b/spec/support/group_members_shared_example.rb
@@ -0,0 +1,27 @@
+RSpec.shared_examples 'members and requesters associations' do
+ describe '#members_and_requesters' do
+ it 'includes members and requesters' do
+ member_and_requester_user_ids = namespace.members_and_requesters.pluck(:user_id)
+
+ expect(member_and_requester_user_ids).to include(requester.id, developer.id)
+ end
+ end
+
+ describe '#members' do
+ it 'includes members and exclude requesters' do
+ member_user_ids = namespace.members.pluck(:user_id)
+
+ expect(member_user_ids).to include(developer.id)
+ expect(member_user_ids).not_to include(requester.id)
+ end
+ end
+
+ describe '#requesters' do
+ it 'does not include requesters' do
+ requester_user_ids = namespace.requesters.pluck(:user_id)
+
+ expect(requester_user_ids).to include(requester.id)
+ expect(requester_user_ids).not_to include(developer.id)
+ end
+ end
+end
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 8731847592b..11ef1fc477f 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -41,7 +41,7 @@ module SeedHelper
end
def create_mutable_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
chdir: SEED_STORAGE_PATH,
out: '/dev/null',
err: '/dev/null')
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 1e39f80699c..71b9deeabc3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,7 +5,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'signed-commits' => '5d4a1cb',
+ 'signed-commits' => '2d1096e',
'not-merged-branch' => 'b83d6e3',
'branch-merged' => '498214d',
'empty-branch' => '7efb185',
@@ -176,6 +176,24 @@ module TestEnv
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
@gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i }
+ wait_gitaly
+ end
+
+ def wait_gitaly
+ sleep_time = 10
+ sleep_interval = 0.1
+ socket = Gitlab::GitalyClient.address('default').sub('unix:', '')
+
+ Integer(sleep_time / sleep_interval).times do
+ begin
+ Socket.unix(socket)
+ return
+ rescue
+ sleep sleep_interval
+ end
+ end
+
+ raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds"
end
def stop_gitaly
diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb
index 3390ae247ff..f2c19c7642a 100644
--- a/spec/views/ci/lints/show.html.haml_spec.rb
+++ b/spec/views/ci/lints/show.html.haml_spec.rb
@@ -73,8 +73,8 @@ describe 'ci/lints/show' do
render
expect(rendered).to have_content('Tag list: dotnet')
- expect(rendered).to have_content('Refs only: test@dude/repo')
- expect(rendered).to have_content('Refs except: deploy')
+ expect(rendered).to have_content('Only policy: refs, test@dude/repo')
+ expect(rendered).to have_content('Except policy: refs, deploy')
expect(rendered).to have_content('Environment: testing')
expect(rendered).to have_content('When: on_success')
end
diff --git a/spec/views/layouts/nav/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index faea2505e40..b17bc6692f3 100644
--- a/spec/views/layouts/nav/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -1,11 +1,13 @@
require 'spec_helper'
-describe 'layouts/nav/_project' do
+describe 'layouts/nav/sidebar/_project' do
describe 'container registry tab' do
before do
+ project = create(:project, :repository)
stub_container_registry_config(enabled: true)
- assign(:project, create(:project, :repository))
+ assign(:project, project)
+ assign(:repository, project.repository)
allow(view).to receive(:current_ref).and_return('master')
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 117f48450e2..d4279626e75 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -195,20 +195,4 @@ describe 'projects/jobs/show' do
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
end
end
-
- describe 'shows trigger variables in sidebar' do
- let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
-
- before do
- build.trigger_request = trigger_request
- render
- end
-
- it 'shows trigger variables in separate lines' do
- expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_1')
- expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_2')
- expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_1')
- expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
- end
- end
end
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
index 54978baca88..aa6c347d738 100644
--- a/spec/workers/create_gpg_signature_worker_spec.rb
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -7,9 +7,14 @@ describe CreateGpgSignatureWorker do
let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
it 'calls Gitlab::Gpg::Commit#signature' do
- expect(Gitlab::Gpg::Commit).to receive(:new).with(project, commit_sha).and_call_original
+ commit = instance_double(Commit)
+ gpg_commit = instance_double(Gitlab::Gpg::Commit)
- expect_any_instance_of(Gitlab::Gpg::Commit).to receive(:signature)
+ allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
+ allow(project).to receive(:commit).with(commit_sha).and_return(commit)
+
+ expect(Gitlab::Gpg::Commit).to receive(:new).with(commit).and_return(gpg_commit)
+ expect(gpg_commit).to receive(:signature)
described_class.new.perform(commit_sha, project.id)
end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 05f971dfd13..c4979792194 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -23,8 +23,8 @@ describe GitGarbageCollectWorker do
expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
- expect_any_instance_of(Repository).to receive(:branch_count).and_call_original
- expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
+ expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original
+ expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
subject.perform(project.id)
end
@@ -143,7 +143,7 @@ describe GitGarbageCollectWorker do
tree: old_commit.tree,
parents: [old_commit]
)
- GitOperationService.new(nil, project.repository).send(
+ Gitlab::Git::OperationService.new(nil, project.repository.raw_repository).send(
:update_ref,
"refs/heads/#{SecureRandom.hex(6)}",
new_commit_sha,
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 549635f7f33..ac6f4fefb4e 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -6,27 +6,31 @@ describe StuckCiJobsWorker do
let(:worker) { described_class.new }
let(:exclusive_lease_uuid) { SecureRandom.uuid }
- subject do
- job.reload
- job.status
- end
-
before do
job.update!(status: status, updated_at: updated_at)
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid)
end
shared_examples 'job is dropped' do
- it 'changes status' do
+ before do
worker.perform
- is_expected.to eq('failed')
+ job.reload
+ end
+
+ it "changes status" do
+ expect(job).to be_failed
+ expect(job).to be_stuck_or_timeout_failure
end
end
shared_examples 'job is unchanged' do
- it "doesn't change status" do
+ before do
worker.perform
- is_expected.to eq(status)
+ job.reload
+ end
+
+ it "doesn't change status" do
+ expect(job.status).to eq(status)
end
end