Skip to content

Commit 97b20a5

Browse files
authored
Merge pull request #22 from dynata/POP-2278
Pop 2278
2 parents 1e0684c + c4767b8 commit 97b20a5

File tree

5 files changed

+322
-1
lines changed

5 files changed

+322
-1
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ This is an InnerSource python project. It is the work of someone who thought it
77
This repository is maintained by
88

99
1. [Ridley Larsen](@RidleyLarsen)
10-
1. [Bradley Wogsland](@wogsland)
10+
2. [Bradley Wogsland](@wogsland)
11+
3. [Nathan Workman](@nathanworkman)
1112

1213
### Community Guidelines
1314

dynatademand/api.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
SCHEMAS = [
99
"project_new",
10+
"lineitem_update",
1011
]
1112

1213

@@ -170,6 +171,22 @@ def get_project_detailed_report(self, project_id):
170171
def get_line_item(self, project_id, line_item_id):
171172
return self._api_get('/projects/{}/lineItems/{}'.format(project_id, line_item_id))
172173

174+
def update_line_item(self, project_id, lineitem_id, lineitem_data):
175+
'''
176+
Updates the specified line item by setting the values of the parameters passed.
177+
Any parameters not provided will be left unchanged.
178+
'''
179+
# Update an existing line item. Uses the "lineitem_update" schema.
180+
self._validate_object("lineitem_update", lineitem_data)
181+
response_data = self._api_post('/projects/{}/lineItems/{}'.format(project_id, lineitem_id), lineitem_data)
182+
if response_data.get('status').get('message') != 'success':
183+
raise DemandAPIError(
184+
"Could not update line item. Demand API responded with: {}".format(
185+
response_data
186+
)
187+
)
188+
return response_data
189+
173190
def get_line_items(self, project_id):
174191
return self._api_get('/projects/{}/lineItems'.format(project_id))
175192

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
{
2+
"type": "object",
3+
"title": "New Line Item",
4+
"properties": {
5+
"extLineItemId": {
6+
"type": "string",
7+
"description": "A unique identifier for your Line Item"
8+
},
9+
"title": {
10+
"type": "string",
11+
"description": "A Line Item title of your choosing"
12+
},
13+
"countryISOCode": {
14+
"type": "string",
15+
"description": "2 letter ISO Country Code",
16+
"minLength": 2,
17+
"maxLength": 2
18+
},
19+
"languageISOCode": {
20+
"type": "string",
21+
"description": "2 letter ISO Language Code",
22+
"minLength": 2,
23+
"maxLength": 2
24+
},
25+
"surveyURL": {
26+
"type": "string",
27+
"description": "Survey URL to send panelist into. "
28+
},
29+
"surveyTestURL": {
30+
"type": "string",
31+
"description": "Survey Test URL. Required for survey verification"
32+
},
33+
"indicativeIncidence": {
34+
"type": "integer",
35+
"description": "Percent of panelists you expect to qualify for the survey after they go in"
36+
},
37+
"daysInField": {
38+
"type": "integer",
39+
"description": "Number of days the survey should be active in the field. We do not stop the survey after daysInField has expired"
40+
},
41+
"lengthOfInterview": {
42+
"type": "integer",
43+
"description": "Average number of minutes it takes a user to complete your survey"
44+
},
45+
"deliveryType": {
46+
"type": "string",
47+
"default": "BALANCED",
48+
"description": "The plan on how responses will flow into the survey.",
49+
"enum": [
50+
"SLOW",
51+
"BALANCED",
52+
"FAST"
53+
]
54+
},
55+
"sources": {
56+
"type": "array",
57+
"description": "The supplier source that you would like to buy sample from. You can get the list of sources available to your account using the [Get List of Sources](/demand-api-reference/data_endpoints/supplier-sources/get-sources) endpoint",
58+
"default": 100,
59+
"items": {
60+
"type": "object",
61+
"properties": {
62+
"id": {
63+
"type": "integer"
64+
}
65+
}
66+
}
67+
},
68+
"targets": {
69+
"type": "array",
70+
"description": "Total count of survey completes required for this line item",
71+
"items": {
72+
"type": "object",
73+
"properties": {
74+
"count": {
75+
"description": "Total number of counts required for this line item for the specified target type",
76+
"type": "integer"
77+
},
78+
"dailyLimit": {
79+
"description": "Total number of daily counts required for this line item",
80+
"type": "integer"
81+
},
82+
"type": {
83+
"type": "string",
84+
"description": "The type of counts required for this line item.",
85+
"enum": [
86+
"COMPLETE"
87+
]
88+
}
89+
}
90+
}
91+
},
92+
"quotaPlan": {
93+
"type": "object",
94+
"title": "Quota Plan",
95+
"description": "Defines the type of respondents you want to invite for the survey",
96+
"properties": {
97+
"filters": {
98+
"type": "array",
99+
"description": "Filters are minimum set of targeting that every respondent must have in order to qualify for the study. Only attributes that have `isAllowedInFilters = true` is allowed to be used in `filters`",
100+
"items": {
101+
"type": "object",
102+
"properties": {
103+
"attributeId": {
104+
"type": "string",
105+
"description": "The attribute you want to target respondents on"
106+
},
107+
"options": {
108+
"type": "array",
109+
"description": "The options of the attribute you want to target respondents on",
110+
"uniqueItems": true,
111+
"items": {
112+
"type": "string"
113+
}
114+
},
115+
"operator": {
116+
"type": "string",
117+
"enum": [
118+
"exclude",
119+
"include"
120+
],
121+
"default": "include",
122+
"description": "The operator to use for the attribute options."
123+
}
124+
}
125+
}
126+
},
127+
"quotaGroups": {
128+
"type": "array",
129+
"description": "Quota groups define the allocated targeting attributes for panelists within this line item. Only attributes that have `isAllowedInQuotas = true` is allowed in `quotaGroups`.",
130+
"items": {
131+
"type": "object",
132+
"properties": {
133+
"name": {
134+
"type": "string",
135+
"description": "A quota group name of your choosing"
136+
},
137+
"quotaCells": {
138+
"type": "array",
139+
"description": "Quota Cells define the percentage allocation for the required targeting. A quota cell is made up of a collection of quota Nodes",
140+
"items": {
141+
"type": "object",
142+
"properties": {
143+
"quotaNodes": {
144+
"type": "array",
145+
"description": "Quota Nodes define the collection of attributes and options being targeted.",
146+
"items": {
147+
"type": "object",
148+
"properties": {
149+
"attributeId": {
150+
"type": "string",
151+
"description": "The attribute you want to target respondents on"
152+
},
153+
"options": {
154+
"type": "array",
155+
"description": "The options of the attribute you want to target respondents on",
156+
"uniqueItems": true,
157+
"items": {
158+
"type": "string"
159+
}
160+
},
161+
"operator": {
162+
"type": "string",
163+
"enum": [
164+
"exclude",
165+
"include"
166+
],
167+
"default": "include",
168+
"description": "**Deprecated field** The operator to use for the attribute options."
169+
}
170+
}
171+
}
172+
},
173+
"count": {
174+
"type": "integer",
175+
"description": "The count of respondents you want to qualify for the defined quota cell"
176+
}
177+
}
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
},
186+
"required": [
187+
"extLineItemId",
188+
"countryISOCode",
189+
"languageISOCode",
190+
"indicativeIncidence",
191+
"daysInField",
192+
"lengthOfInterview",
193+
"targets"
194+
]
195+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"extLineItemId": "lineItem002",
3+
"title": "US College",
4+
"countryISOCode": "US",
5+
"languageISOCode": "en",
6+
"surveyURL": "www.mysurvey.com/live/survey?pid=<#DubKnowledge[1500/Entity id]>&k2=<#Project[Secure Key 2]>&psid=<#IdParameter[Value]>",
7+
"surveyTestURL": "www.mysurvey.com/test/survey?pid=<#DubKnowledge[1500/Entity id]>&k2=<#Project[Secure Key 2]>&psid=<#IdParameter[Value]>",
8+
"indicativeIncidence": 20,
9+
"daysInField": 20,
10+
"lengthOfInterview": 10,
11+
"deliveryType": "BALANCED",
12+
"sources": [
13+
{
14+
"id": 100
15+
}
16+
],
17+
"targets": [
18+
{
19+
"count": 200,
20+
"dailyLimit": 0,
21+
"type": "COMPLETE"
22+
}
23+
],
24+
"quotaPlan": {
25+
"filters": [
26+
{
27+
"attributeId": "61961",
28+
"options": [
29+
"3",
30+
"4"
31+
],
32+
"operator": "include"
33+
}
34+
],
35+
"quotaGroups": [
36+
{
37+
"name": "Gender Distribution",
38+
"quotaCells": [
39+
{
40+
"quotaNodes": [
41+
{
42+
"attributeId": "11",
43+
"options": [
44+
"1"
45+
]
46+
}
47+
],
48+
"count": 130
49+
},
50+
{
51+
"quotaNodes": [
52+
{
53+
"attributeId": "11",
54+
"options": [
55+
"2"
56+
]
57+
}
58+
],
59+
"count": 70
60+
}
61+
]
62+
}
63+
]
64+
},
65+
"state": "PROVISIONED",
66+
"stateReason": "Created by Client",
67+
"stateLastUpdatedAt": "04/01/2018 00:00:00",
68+
"createdAt": "04/01/2018 00:00:00",
69+
"updatedAt": "04/01/2018 00:00:00",
70+
"launchedAt": null,
71+
"endLinks": {
72+
"complete": "https://api.researchnow.com/respondent/exit?rst=1&psid={psid}&med={calculatedSecurityCode}",
73+
"screenout": "https://api.researchnow.com/respondent/exit?rst=2&psid={psid}",
74+
"overquota": "https://api.researchnow.com/respondent/exit?rst=3&psid={psid}",
75+
"securityKey1": "35040",
76+
"securityLevel": "MEDIUM"
77+
}
78+
}

tests/test_line_items.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import responses
77

88
from dynatademand.api import DemandAPIClient
9+
from dynatademand.errors import DemandAPIError
910

1011
BASE_HOST = "http://test-url.example"
1112

@@ -49,3 +50,32 @@ def test_get_line_item_detailed_report(self):
4950
self.api.get_line_item_detailed_report(1, 100)
5051
self.assertEqual(len(responses.calls), 1)
5152
self.assertEqual(responses.calls[0].response.json(), line_item_detailed_report_json)
53+
54+
@responses.activate
55+
def test_update_line_item(self):
56+
with open('./tests/test_files/examples/lineitem_update.json', 'r') as new_lineitem_file:
57+
update_lineitem_data = json.load(new_lineitem_file)
58+
59+
# Success response
60+
responses.add(
61+
responses.POST,
62+
'{}/sample/v1/projects/1/lineItems/1'.format(BASE_HOST),
63+
json={'status': {'message': 'success'}},
64+
status=200
65+
)
66+
# Response with error status
67+
responses.add(
68+
responses.POST,
69+
'{}/sample/v1/projects/1/lineItems/1'.format(BASE_HOST),
70+
json={'status': {'message': 'error'}},
71+
status=200
72+
)
73+
74+
# Test success response
75+
self.api.update_line_item(1, 1, update_lineitem_data)
76+
self.assertEqual(len(responses.calls), 1)
77+
78+
# Test error response
79+
with self.assertRaises(DemandAPIError):
80+
self.api.update_line_item(1, 1, update_lineitem_data)
81+
self.assertEqual(len(responses.calls), 2)

0 commit comments

Comments
 (0)