Skip to content

Commit 5502074

Browse files
committed
added db_pre_save callback which inserts additional objects over the api; see #79234
1 parent b7bee32 commit 5502074

5 files changed

Lines changed: 259 additions & 117 deletions

File tree

manifest.master.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,12 @@ callbacks:
382382
type: objecttype
383383
objecttypes:
384384
- write_event
385+
- name: "write (insert/update) an object over the api"
386+
callback: write_db
387+
filter:
388+
type: objecttype
389+
objecttypes:
390+
- write_db
385391
- name: "bounce json back for 'bounce'"
386392
callback: bounce
387393
filter:
@@ -453,7 +459,21 @@ callbacks:
453459
type: body
454460
args:
455461
- type: value
456-
value: "%_exec.pluginDir%/server/db_pre_save+write_event/write_event.py"
462+
value: "%_exec.pluginDir%/server/db_pre_save+write_api/write_event.py"
463+
write_db:
464+
exec:
465+
service: python3
466+
commands:
467+
- prog: python3
468+
stdin:
469+
type: body
470+
stderr:
471+
type: body
472+
stdout:
473+
type: body
474+
args:
475+
- type: value
476+
value: "%_exec.pluginDir%/server/db_pre_save+write_api/write_db.py"
457477
check:
458478
exec:
459479
service: "node"
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# encoding: utf-8
2+
3+
4+
import json
5+
import sys
6+
import requests
7+
8+
9+
# plugin response functions
10+
11+
# the direct response from the plugin uses stdin and stderr
12+
13+
14+
def stdout(msg: str):
15+
sys.stdout.write(msg)
16+
sys.stdout.write('\n')
17+
18+
19+
def stderr(msg: str):
20+
sys.stderr.write(msg)
21+
sys.stderr.write('\n')
22+
23+
24+
def return_response(response: dict):
25+
stdout(json.dumps(response))
26+
exit(0)
27+
28+
29+
def return_error_response(error: str):
30+
stderr(f'error in fylr-plugin-example: {error}')
31+
return_response(
32+
{
33+
'error': {
34+
'code': 'fylr-plugin-example.error',
35+
'statuscode': 400,
36+
'description': error,
37+
'error': error,
38+
},
39+
},
40+
)
41+
42+
43+
# -----------------------------
44+
45+
# helper functions to parse callback data from fylr
46+
47+
# the api url and access token are necessary if the plugin
48+
# needs to read/write more data over the fylr api
49+
50+
51+
def get_api_url(callback_data):
52+
url = get_json_value(callback_data, 'info.api_url')
53+
if not url:
54+
return_error_response('info.api_url missing!')
55+
return f'{url}/api/v1'
56+
57+
58+
def get_access_token(callback_data):
59+
access_token = get_json_value(callback_data, 'info.api_user_access_token')
60+
if not access_token:
61+
return_error_response('info.api_user_access_token missing!')
62+
return access_token
63+
64+
65+
# -----------------------------
66+
67+
# fylr api functions
68+
69+
# the plugin can call the fylr api to read/write more data
70+
71+
72+
def fylr_api_headers(access_token):
73+
return {'authorization': f'Bearer {access_token}'}
74+
75+
76+
def get_from_api(api_url, path, access_token):
77+
resp = requests.get(
78+
url=f'{api_url}/{path}',
79+
headers=fylr_api_headers(access_token),
80+
)
81+
82+
return resp.text, resp.status_code
83+
84+
85+
def post_to_api(api_url, path, access_token, payload=None):
86+
resp = requests.post(
87+
url=f'{api_url}/{path}',
88+
headers=fylr_api_headers(access_token),
89+
data=payload,
90+
)
91+
92+
return resp.text, resp.status_code
93+
94+
95+
# -----------------------------
96+
97+
98+
def get_json_value(js, path, expected=False, split_char='.'):
99+
100+
current = js
101+
path_parts = []
102+
current_part = ''
103+
104+
for i in range(len(path)):
105+
if path[i] != split_char:
106+
current_part += path[i]
107+
if i == len(path) - 1:
108+
path_parts.append(current_part)
109+
continue
110+
111+
if i > 0 and path[i - 1] == '\\':
112+
current_part += path[i]
113+
continue
114+
115+
if len(current_part) > 0:
116+
path_parts.append(current_part)
117+
current_part = ''
118+
119+
for path_part in path_parts:
120+
path_part = path_part.replace('\\' + split_char, split_char)
121+
122+
if not isinstance(current, dict) or path_part not in current:
123+
if expected:
124+
raise Exception('expected: ' + path_part)
125+
else:
126+
return None
127+
128+
current = current[path_part]
129+
130+
return current
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# encoding: utf-8
2+
3+
4+
import sys
5+
import json
6+
import util
7+
8+
9+
def main():
10+
11+
# read the callback data from fylr
12+
callback_data = json.loads(sys.stdin.read())
13+
14+
# get the server api url and access token
15+
api_url = util.get_api_url(callback_data)
16+
17+
# get the oauth2 access token for the api
18+
access_token = util.get_access_token(callback_data)
19+
20+
objects = util.get_json_value(callback_data, 'objects')
21+
if not isinstance(objects, list):
22+
util.return_response(callback_data)
23+
24+
updated_objects = []
25+
26+
# iterate over the objects and count how many were inserted/updated
27+
for obj in objects:
28+
29+
if not isinstance(obj, dict):
30+
continue
31+
32+
# get the "titel" from the object,
33+
# if it is set create a new linked object with the same name.
34+
# insert the object over the api and link it in this object
35+
objecttype = util.get_json_value(obj, '_objecttype')
36+
title = util.get_json_value(obj, f'{objecttype}.titel')
37+
if not title:
38+
continue
39+
40+
# create a new "linked_object" payload
41+
# and post it to the api/v1/db endpoint
42+
resp_text, statuscode = util.post_to_api(
43+
api_url=api_url,
44+
path='db/linked_object',
45+
access_token=access_token,
46+
payload=json.dumps(
47+
[
48+
{
49+
'_comment': '<inserted by fylr-plugin-example>',
50+
'_mask': "_all_fields",
51+
'_objecttype': "linked_object",
52+
"linked_object": {
53+
"_version": 1,
54+
"name": title,
55+
},
56+
}
57+
],
58+
indent=4,
59+
),
60+
)
61+
if statuscode != 200:
62+
# could not insert the new linked object -> api error
63+
util.return_error_response(
64+
f'could not insert linked_object: api error (code {statuscode}): {resp_text}'
65+
)
66+
67+
# use a lookup to insert the new linked object
68+
obj[objecttype]['link'] = {
69+
'_mask': "_all_fields",
70+
'_objecttype': "linked_object",
71+
"linked_object": {
72+
"_version": 1,
73+
"lookup:_id": {
74+
"name": title,
75+
},
76+
},
77+
}
78+
79+
# this object was updated, it must be returned to fylr
80+
updated_objects.append(obj)
81+
82+
# only return the objects which were updated.
83+
# fylr will save all other objects from the callback without any changes
84+
util.return_response({"objects": updated_objects})
85+
86+
87+
if __name__ == '__main__':
88+
main()

server/db_pre_save+write_event/write_event.py renamed to server/db_pre_save+write_api/write_event.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,18 @@
88

99
def main():
1010

11-
orig_data = json.loads(sys.stdin.read())
11+
# read the callback data from fylr
12+
callback_data = json.loads(sys.stdin.read())
1213

13-
# get the server url
14-
api_url = util.get_json_value(orig_data, 'info.api_url')
15-
if api_url is None:
16-
util.return_error_response('info.api_url missing!')
17-
api_url += '/api/v1'
14+
# get the server api url and access token
15+
api_url = util.get_api_url(callback_data)
1816

19-
# get a session token
20-
access_token = util.get_json_value(
21-
orig_data, 'info.api_user_access_token')
22-
if access_token is None:
23-
util.return_error_response('info.api_user_access_token missing!')
17+
# get the oauth2 access token for the api
18+
access_token = util.get_access_token(callback_data)
2419

25-
objects = util.get_json_value(orig_data, 'objects')
20+
objects = util.get_json_value(callback_data, 'objects')
2621
if not isinstance(objects, list):
27-
util.return_response(orig_data)
22+
util.return_response(callback_data)
2823

2924
objecttype_count = {}
3025

@@ -42,7 +37,7 @@ def main():
4237
'updated': 0,
4338
}
4439

45-
version = util.get_json_value(obj, objecttype + '._version')
40+
version = util.get_json_value(obj, f'{objecttype}._version')
4641
if version == 1:
4742
objecttype_count[objecttype]['inserted'] += 1
4843
else:
@@ -54,15 +49,19 @@ def main():
5449
api_url=api_url,
5550
path='event?background=1',
5651
access_token=access_token,
57-
payload=util.dumpjs({
58-
'event': {
59-
'type': 'EXAMPLE_PLUGIN_OBJECT_STATISTICS',
60-
'objecttype': objecttype,
61-
'info': objecttype_count[objecttype]
62-
}
63-
})
52+
payload=json.dumps(
53+
{
54+
'event': {
55+
'type': 'EXAMPLE_PLUGIN_OBJECT_STATISTICS',
56+
'objecttype': objecttype,
57+
'info': objecttype_count[objecttype],
58+
}
59+
},
60+
indent=4,
61+
),
6462
)
6563

64+
# return an empty objects array, this indicates to fylr that there were no changes in the objects
6665
util.return_response({"objects": []})
6766

6867

0 commit comments

Comments
 (0)