qualtrics_api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +141 -0
- data/Rakefile +7 -0
- data/fixtures/vcr_cassettes/response_export_start_success.yml +32 -0
- data/fixtures/vcr_cassettes/response_export_update_success.yml +61 -0
- data/fixtures/vcr_cassettes/survey_collection_fetch_fail.yml +33 -0
- data/fixtures/vcr_cassettes/survey_collection_fetch_sucess.yml +33 -0
- data/fixtures/vcr_cassettes/survey_collection_fetch_with_scopeId_success.yml +33 -0
- data/lib/qualtrics_api.rb +23 -0
- data/lib/qualtrics_api/client.rb +32 -0
- data/lib/qualtrics_api/request_error_handler.rb +37 -0
- data/lib/qualtrics_api/response_export.rb +40 -0
- data/lib/qualtrics_api/response_export_collection.rb +24 -0
- data/lib/qualtrics_api/services/response_export_service.rb +90 -0
- data/lib/qualtrics_api/survey.rb +29 -0
- data/lib/qualtrics_api/survey_collection.rb +64 -0
- data/lib/qualtrics_api/url.rb +3 -0
- data/lib/qualtrics_api/version.rb +3 -0
- data/qualtrics_api.gemspec +30 -0
- data/spec/lib/client_spec.rb +47 -0
- data/spec/lib/response_export_collection_spec.rb +33 -0
- data/spec/lib/response_export_spec.rb +94 -0
- data/spec/lib/services/response_export_service_spec.rb +110 -0
- data/spec/lib/survey_collection_spec.rb +106 -0
- data/spec/lib/survey_spec.rb +60 -0
- data/spec/qualtrics_api_spec.rb +14 -0
- data/spec/spec_helper.rb +9 -0
- metadata +167 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware"
|
3
|
+
|
4
|
+
require "qualtrics_api/version"
|
5
|
+
require "qualtrics_api/url"
|
6
|
+
|
7
|
+
require "qualtrics_api/request_error_handler"
|
8
|
+
|
9
|
+
require "qualtrics_api/client"
|
10
|
+
require "qualtrics_api/survey"
|
11
|
+
require "qualtrics_api/survey_collection"
|
12
|
+
require "qualtrics_api/response_export"
|
13
|
+
require "qualtrics_api/response_export_collection"
|
14
|
+
|
15
|
+
require "qualtrics_api/services/response_export_service"
|
16
|
+
|
17
|
+
module QualtricsAPI
|
18
|
+
|
19
|
+
def self.new(token)
|
20
|
+
Client.new(api_token: token)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
|
3
|
+
class Client
|
4
|
+
attr_reader :api_token
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@api_token = options[:api_token]
|
8
|
+
end
|
9
|
+
|
10
|
+
def surveys(options = {})
|
11
|
+
@surveys ||= QualtricsAPI::SurveyCollection.new options.merge({ connection: connection })
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_exports(options = {})
|
15
|
+
@response_exports ||= QualtricsAPI::ResponseExportCollection.new options.merge({ connection: connection })
|
16
|
+
end
|
17
|
+
|
18
|
+
def connection
|
19
|
+
@conn ||= Faraday.new(url: QualtricsAPI::URL,
|
20
|
+
params: { apiToken: @api_token }) do |faraday|
|
21
|
+
faraday.request :json
|
22
|
+
faraday.response :json, :content_type => /\bjson$/
|
23
|
+
|
24
|
+
faraday.use FaradayMiddleware::FollowRedirects
|
25
|
+
faraday.use QualtricsAPI::RequestErrorHandler
|
26
|
+
|
27
|
+
faraday.adapter Faraday.default_adapter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
|
3
|
+
class RequestErrorHandler < Faraday::Response::Middleware
|
4
|
+
|
5
|
+
def on_complete(env)
|
6
|
+
case env[:status]
|
7
|
+
when 404
|
8
|
+
raise NotFoundError, "Not Found"
|
9
|
+
when 400
|
10
|
+
raise BadRequestError, error_message(JSON.parse(env[:body]))
|
11
|
+
when 401
|
12
|
+
raise UnauthorizedError, error_message(JSON.parse(env[:body]))
|
13
|
+
when 500
|
14
|
+
raise InternalServerError, error_message(JSON.parse(env[:body]))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def error_message(response)
|
21
|
+
meta = response["meta"]
|
22
|
+
[ "[",
|
23
|
+
meta["status"], " - ",
|
24
|
+
meta["qualtricsErrorCode"] || meta["internalErrorCode"],
|
25
|
+
"] ",
|
26
|
+
meta["errorMessage"]
|
27
|
+
].join
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class NotFoundError < StandardError; end
|
33
|
+
class BadRequestError < StandardError; end
|
34
|
+
class UnauthorizedError < StandardError; end
|
35
|
+
class InternalServerError < StandardError; end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
|
3
|
+
class ResponseExport
|
4
|
+
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@conn = options[:connection]
|
9
|
+
@id = options[:id]
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_status
|
13
|
+
res = @conn.get('surveys/responseExports/' + @id).body["result"]
|
14
|
+
@export_progress = res["percentComplete"]
|
15
|
+
@file_url = res["fileUrl"]
|
16
|
+
@completed = true if @export_progress == 100.0
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def status
|
21
|
+
update_status unless completed?
|
22
|
+
"#{@export_progress}%"
|
23
|
+
end
|
24
|
+
|
25
|
+
def percent_completed
|
26
|
+
update_status unless completed?
|
27
|
+
@export_progress
|
28
|
+
end
|
29
|
+
|
30
|
+
def completed?
|
31
|
+
@completed == true
|
32
|
+
end
|
33
|
+
|
34
|
+
def file_url
|
35
|
+
update_status unless completed?
|
36
|
+
@file_url
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
class ResponseExportCollection
|
3
|
+
extend Forwardable
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :all
|
7
|
+
|
8
|
+
def_delegator :all, :each
|
9
|
+
def_delegator :all, :size
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@conn = options[:connection]
|
13
|
+
@all = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](export_id); find(export_id); end
|
17
|
+
def find(export_id)
|
18
|
+
@all.select do |response_export|
|
19
|
+
response_export.id == export_id
|
20
|
+
end.first || QualtricsAPI::ResponseExport.new(:id => export_id , connection: @conn)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
module Services
|
3
|
+
class ResponseExportService
|
4
|
+
attr_reader :survey_id,
|
5
|
+
:response_set_id,
|
6
|
+
:file_type,
|
7
|
+
:last_response_id,
|
8
|
+
:start_date,
|
9
|
+
:end_date,
|
10
|
+
:limit,
|
11
|
+
:included_question_ids,
|
12
|
+
:max_rows,
|
13
|
+
:use_labels,
|
14
|
+
:decimal_format,
|
15
|
+
:seen_unanswered_recode,
|
16
|
+
:use_local_time,
|
17
|
+
:spss_string_length,
|
18
|
+
:result
|
19
|
+
|
20
|
+
def initialize(options = {})
|
21
|
+
@conn = options[:connection]
|
22
|
+
@survey_id = options[:survey_id]
|
23
|
+
@response_set_id = options[:response_set_id]
|
24
|
+
@file_type = options[:file_type] || 'CSV'
|
25
|
+
@last_response_id = options[:last_response_id]
|
26
|
+
@start_date = options[:start_date]
|
27
|
+
@end_date = options[:end_date]
|
28
|
+
@limit = options[:limit]
|
29
|
+
@included_question_ids = options[:included_question_ids]
|
30
|
+
@max_rows = options[:max_rows]
|
31
|
+
@use_labels = options.has_key?(:use_labels) ? options[:use_labels] : false
|
32
|
+
@decimal_format = options[:decimal_format] || '.'
|
33
|
+
@seen_unanswered_recode = options[:seen_unanswered_recode]
|
34
|
+
@use_local_time = options.has_key?(:use_local_time) ? options[:use_local_time] : false
|
35
|
+
@spss_string_length = options[:spss_string_length]
|
36
|
+
@id = options[:id]
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
response = @conn.get("surveys/#{@survey_id}/responseExports", export_params)
|
41
|
+
export_id = response.body["result"]["exportStatus"].split('/').last
|
42
|
+
@result = ResponseExport.new(id: export_id, connection: @conn)
|
43
|
+
end
|
44
|
+
|
45
|
+
def export_configurations
|
46
|
+
{
|
47
|
+
response_set_id: @response_set_id,
|
48
|
+
file_type: @file_type,
|
49
|
+
last_response_id: @last_response_id,
|
50
|
+
start_date: @start_date,
|
51
|
+
end_date: @end_date,
|
52
|
+
limit: @limit,
|
53
|
+
included_question_ids: @included_question_ids,
|
54
|
+
max_rows: @max_rows,
|
55
|
+
use_labels: @use_labels,
|
56
|
+
decimal_format: @decimal_format,
|
57
|
+
seen_unanswered_recode: @seen_unanswered_recode,
|
58
|
+
use_local_time: @use_local_time,
|
59
|
+
spss_string_length: @spss_string_length
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def param_mappings
|
66
|
+
{
|
67
|
+
response_set_id: "responseSetId",
|
68
|
+
file_type: "fileType",
|
69
|
+
last_response_id: "lastResponseId",
|
70
|
+
start_date: "startDate",
|
71
|
+
end_date: "endDate",
|
72
|
+
limit: "limit",
|
73
|
+
included_question_ids: "includedQuestionIds",
|
74
|
+
max_rows: "maxRows",
|
75
|
+
use_labels: "useLabels",
|
76
|
+
decimal_format: "decimalFormat",
|
77
|
+
seen_unanswered_recode: "seenUnansweredRecode",
|
78
|
+
use_local_time: "useLocalTime",
|
79
|
+
spss_string_length: "spssStringLength"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def export_params
|
84
|
+
export_configurations.map do |config_key, value|
|
85
|
+
[param_mappings[config_key], value] unless value.nil? || value.to_s.empty?
|
86
|
+
end.compact.to_h
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
|
3
|
+
class Survey
|
4
|
+
attr_accessor :id, :name, :owner_id, :last_modified, :status
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
attributes_mappings.each do |key, qualtrics_key|
|
8
|
+
instance_variable_set "@#{key}", options[qualtrics_key]
|
9
|
+
end
|
10
|
+
@conn = options[:connection]
|
11
|
+
end
|
12
|
+
|
13
|
+
def export_responses(export_options = {})
|
14
|
+
QualtricsAPI::Services::ResponseExportService.new(export_options.merge(survey_id: id, connection: @conn))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def attributes_mappings
|
20
|
+
{
|
21
|
+
:id => "id",
|
22
|
+
:name => "name",
|
23
|
+
:owner_id => "ownerId",
|
24
|
+
:last_modified => "lastModified",
|
25
|
+
:status => "status"
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module QualtricsAPI
|
2
|
+
|
3
|
+
class SurveyCollection
|
4
|
+
extend Forwardable
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_accessor :scope_id
|
8
|
+
attr_reader :all
|
9
|
+
|
10
|
+
def_delegator :all, :each
|
11
|
+
def_delegator :all, :size
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@conn = options[:connection]
|
15
|
+
@scope_id = options[:scope_id]
|
16
|
+
@all = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(options = {})
|
20
|
+
@all = []
|
21
|
+
update_query_attributes(options)
|
22
|
+
parse_fetch_response(@conn.get('surveys', query_params))
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def query_attributes
|
27
|
+
{
|
28
|
+
:scope_id => @scope_id
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_query_attributes(new_attributes = {})
|
33
|
+
@scope_id = new_attributes[:scope_id] if new_attributes.has_key? :scope_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](survey_id); find(survey_id); end
|
37
|
+
def find(survey_id)
|
38
|
+
@all.select do |survey|
|
39
|
+
survey.id == survey_id
|
40
|
+
end.first || QualtricsAPI::Survey.new("id" => survey_id , connection: @conn)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def attributes_mapping
|
46
|
+
{
|
47
|
+
:scope_id => "scopeId"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def query_params
|
52
|
+
query_attributes.map do |k, v|
|
53
|
+
[attributes_mapping[k], v] unless v.nil? || v.to_s.empty?
|
54
|
+
end.compact.to_h
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_fetch_response(response)
|
58
|
+
@all = response.body["result"].map do |result|
|
59
|
+
QualtricsAPI::Survey.new result.merge(connection: @conn)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'qualtrics_api/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "qualtrics_api"
|
9
|
+
spec.version = QualtricsAPI::VERSION
|
10
|
+
spec.authors = ["Yurui Zhang"]
|
11
|
+
spec.email = ["yuruiology@gmail.com"]
|
12
|
+
spec.summary = %q{A Ruby wrapper for Qualtrics REST API v3.0}
|
13
|
+
spec.description = %q{A Ruby wrapper for Qualtrics REST API version 3.0.
|
14
|
+
See https://co1.qualtrics.com/APIDocs/ for API documents.}
|
15
|
+
spec.homepage = ""
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "faraday", "~> 0.9.1"
|
24
|
+
spec.add_dependency "faraday_middleware", "~> 0.9.1"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.2.0"
|
29
|
+
spec.add_development_dependency "vcr", "~> 2.9.3"
|
30
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QualtricsAPI::Client do
|
4
|
+
|
5
|
+
subject { QualtricsAPI::Client.new(:api_token => "someToken") }
|
6
|
+
|
7
|
+
it "has an api token" do
|
8
|
+
expect(subject.api_token).to eq "someToken"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "does not allow changing the token once initialized" do
|
12
|
+
expect(subject).to_not respond_to(:api_token=)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#response_exports" do
|
16
|
+
it "returns a ResponseExportCollection" do
|
17
|
+
expect(subject.response_exports).to be_a QualtricsAPI::ResponseExportCollection
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets connection" do
|
21
|
+
expect(subject.surveys.instance_variable_get(:@conn)).to eq subject.connection
|
22
|
+
end
|
23
|
+
|
24
|
+
it "caches the collection" do
|
25
|
+
expect(subject.response_exports.object_id).to eq subject.response_exports.object_id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#surveys" do
|
30
|
+
it "returns a SurveyCollection" do
|
31
|
+
expect(subject.surveys).to be_a QualtricsAPI::SurveyCollection
|
32
|
+
end
|
33
|
+
|
34
|
+
it "sets connection" do
|
35
|
+
expect(subject.surveys.instance_variable_get(:@conn)).to eq subject.connection
|
36
|
+
end
|
37
|
+
|
38
|
+
it "assigns scope_id if passed" do
|
39
|
+
expect(subject.surveys(:scope_id => "someId").scope_id).to eq "someId"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "caches the surveys" do
|
43
|
+
expect(subject.surveys.object_id).to eq subject.surveys.object_id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QualtricsAPI::ResponseExportCollection do
|
4
|
+
let(:connection) { double('connection') }
|
5
|
+
|
6
|
+
subject { described_class.new connection: connection }
|
7
|
+
|
8
|
+
it "has no @all when initialized" do
|
9
|
+
expect(subject.all).to eq []
|
10
|
+
end
|
11
|
+
|
12
|
+
it "takes a connection" do
|
13
|
+
expect(subject.instance_variable_get(:@conn)).to eq connection
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#find, #[]" do
|
17
|
+
let(:export_1) { QualtricsAPI::ResponseExport.new :id => "export1" }
|
18
|
+
let(:export_2) { QualtricsAPI::ResponseExport.new :id => "export2" }
|
19
|
+
|
20
|
+
it "finds the export by id" do
|
21
|
+
subject.instance_variable_set :@all, [export_1, export_2]
|
22
|
+
expect(subject.find("export1")).to eq export_1
|
23
|
+
expect(subject["export2"]).to eq export_2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns a new survey with the id" do
|
27
|
+
sut = subject["eee 3"]
|
28
|
+
expect(sut).to be_a QualtricsAPI::ResponseExport
|
29
|
+
expect(sut.id).to eq "eee 3"
|
30
|
+
expect(sut.instance_variable_get(:@conn)).to eq connection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|