Skip to content

Commit

Permalink
Merge pull request #118 from andrewduckett/patch/ref-link
Browse files Browse the repository at this point in the history
feat: Add a link  to Glide Element when available
  • Loading branch information
vetsin authored Oct 11, 2024
2 parents a0435b9 + 5be0795 commit d6a4f7d
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 18 deletions.
3 changes: 2 additions & 1 deletion pysnc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ def _set_params(self, record=None):
params = {} if record is None else record._parameters()
if 'sysparm_display_value' not in params:
params['sysparm_display_value'] = 'all'
params['sysparm_exclude_reference_link'] = 'true' # Scratch it!
if 'sysparm_exclude_reference_link' not in params:
params['sysparm_exclude_reference_link'] = 'true' # Scratch it!, by default
params['sysparm_suppress_pagination_header'] = 'true' # Required for large queries
return params

Expand Down
101 changes: 84 additions & 17 deletions pysnc/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ class GlideElement(str):
def __new__(cls, name, value, *args, **kwargs):
return super(GlideElement, cls).__new__(cls, value)

def __init__(self, name: str, value=None, display_value=None, parent_record=None):
def __init__(self, name: str, value=None, display_value=None, parent_record=None, link=None):
self._name = name
self._value = None
self._display_value = None
self._changed = False
self._link = None
if isinstance(value, dict):
self._value = value['value']
# only bother to set display value if it's different
if self._value != value['display_value']:
self._display_value = value['display_value']
if 'link' in value:
self._link = value['link']
else:
self._value = value
if display_value:
Expand Down Expand Up @@ -63,6 +66,12 @@ def get_display_value(self) -> Any:
return self._display_value
return self._value

def get_link(self) -> Any:
"""
get the link of a field, if it has one, else None
"""
return self._link

def set_value(self, value):
"""
set the value for the field. Will also set the display_value to `None`
Expand All @@ -85,6 +94,16 @@ def set_display_value(self, value: Any):
self._changed = True
self._display_value = value

def set_link(self, link: Any):
"""
set the reference link for the field -- generally speaking does not have any affect upstream (to the server)
"""
if isinstance(link, GlideElement):
link = link.get_link()
if self._link != link:
self._changed = True
self._link = link

def changes(self) -> bool:
"""
:return: if we have changed this value
Expand All @@ -103,12 +122,20 @@ def nil(self) -> bool:

def serialize(self) -> dict:
"""
Returns a dict with the `value`,`display_value` keys
"""
return {
'value': self.get_value(),
'display_value': self.get_display_value()
}
Returns a dict with the `value`,`display_value`, `link` keys
"""
return (
{
'value': self.get_value(),
'display_value': self.get_display_value()
}
if self.get_link() is None
else {
'value': self.get_value(),
'display_value': self.get_display_value(),
'link': self.get_link()
}
)

def date_numeric_value(self) -> int:
"""
Expand Down Expand Up @@ -266,6 +293,7 @@ def __init__(self, client: 'ServiceNowClient', table: str, batch_size: int=500,
self.__order: str = "ORDERBYsys_id" # we *need* a default order in the event we page, see issue#96
self.__is_new_record: bool = False
self.__display_value: Union[bool, str] = 'all'
self.__exclude_reference_link: bool = True
self.__rewindable = rewindable

def _clear_query(self):
Expand All @@ -285,6 +313,7 @@ def _parameters(self):
ret['sysparm_view'] = self.__view

ret['sysparm_display_value'] = str(self.display_value).lower()
ret['sysparm_exclude_reference_link'] = str(self.exclude_reference_link).lower()
# Batch size matters! Transaction limits will exceed.
# This also means we have to be pretty specific with limits
limit = None
Expand Down Expand Up @@ -423,6 +452,19 @@ def display_value(self, display_value):
assert display_value in [True, False, 'all']
self.__display_value = display_value

@property
def exclude_reference_link(self):
return self.__exclude_reference_link

@exclude_reference_link.setter
def exclude_reference_link(self, exclude_reference_link):
"""
True: Exclude Table API links for reference fields.
False: Include Table API links for reference fields.
"""
assert exclude_reference_link in [True, False]
self.__exclude_reference_link = exclude_reference_link

def order_by(self, column: str):
"""
Set the order in ascending
Expand Down Expand Up @@ -697,6 +739,8 @@ def _get_value(self, item, key='value'):
o = obj[item]
if key == 'display_value':
return o.get_display_value()
if key == 'link':
return o.get_link()
return o.get_value()
return None

Expand Down Expand Up @@ -764,6 +808,21 @@ def set_display_value(self, field, value):
else:
c[field].set_display_value(value)

def set_link(self, field, value):
"""
Set the link for a field.
:param str field: The field
:param value: The Value
"""
c = self._current()
if c is None:
raise NoRecordException('cannot get a value from nothing, did you forget to call next() or initialize()?')
if field not in c:
c[field] = GlideElement(field, link=value, parent_record=self)
else:
c[field].set_link(value)

def get_link(self, no_stack=False) -> str:
"""
Generate a full URL to the current record. sys_id will be null if there is no current record.
Expand Down Expand Up @@ -949,7 +1008,7 @@ def add_not_null_query(self, field) -> QueryCondition:
"""
return self.__query.add_not_null_query(field)

def _serialize(self, record, display_value, fields=None, changes_only=False):
def _serialize(self, record, display_value, fields=None, changes_only=False, exclude_reference_link=True):
if isinstance(display_value, str):
v_type = 'both'
else:
Expand All @@ -965,19 +1024,27 @@ def compress(obj):
if isinstance(value, GlideElement):
if changes_only and not value.changes():
continue
if v_type == 'display_value':
ret[key] = value.get_display_value()
elif v_type == 'both':
ret[key] = value.serialize()
if exclude_reference_link or value.get_link() is None:
if v_type == 'display_value':
ret[key] = value.get_display_value()
elif v_type == 'both':
ret[key] = value.serialize()
else:
ret[key] = value.get_value()
else:
ret[key] = value.get_value()
serialized = value.serialize()
if v_type == 'display_value':
serialized.pop('value', None)
elif v_type == 'value':
serialized.pop('display_value', None)
ret[key] = serialized
else:
ret[key] = value.get_value()
return ret

return compress(record)

def serialize(self, display_value=False, fields=None, fmt=None, changes_only=False) -> Any:
def serialize(self, display_value=False, fields=None, fmt=None, changes_only=False, exclude_reference_link=True) -> Any:
"""
Turn current record into a dictGlideRecord(None, 'incident')
Expand Down Expand Up @@ -1005,9 +1072,9 @@ def transform(obj):
return transform(self) # i know this is inconsistent, self vs current
else:
c = self._current()
return self._serialize(c, display_value, fields, changes_only)
return self._serialize(c, display_value, fields, changes_only, exclude_reference_link)

def serialize_all(self, display_value=False, fields=None, fmt=None) -> list:
def serialize_all(self, display_value=False, fields=None, fmt=None, exclude_reference_link=True) -> list:
"""
Serialize the entire query. See serialize() docs for details on parameters
Expand All @@ -1016,7 +1083,7 @@ def serialize_all(self, display_value=False, fields=None, fmt=None) -> list:
:param fmt:
:return: list
"""
return [record.serialize(display_value, fields, fmt) for record in self]
return [record.serialize(display_value, fields, fmt, exclude_reference_link) for record in self]

def to_pandas(self, columns=None, mode='smart'):
"""
Expand Down
17 changes: 17 additions & 0 deletions test/test_snc_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ def test_serialization(self):
self.assertEqual(json.dumps(gr2.serialize(display_value=True)), '{"two_field": "Some Value"}')
self.assertEqual(gr2.two_field.get_name(), 'two_field')

def test_serialization_with_link(self):
gr = GlideRecord(None, 'incident')
gr.initialize()
gr.test_field = 'some string'
self.assertTrue(isinstance(gr.test_field, GlideElement))
r = gr.serialize()
self.assertEqual(json.dumps(r), '{"test_field": "some string"}')

gr.set_value('test_field', 'somevalue')
gr.set_display_value('test_field', 'Some Value')
gr.set_link('test_field', 'https://dev00000.service-now.com/api/now/table/sys___/abcde12345')
print(gr.serialize())
self.assertEqual(json.dumps(gr.serialize()), '{"test_field": "somevalue"}')
self.assertEqual(json.dumps(gr.serialize(display_value=True, exclude_reference_link=False)), '{"test_field": {"display_value": "Some Value", "link": "https://dev00000.service-now.com/api/now/table/sys___/abcde12345"}}')
self.assertEqual(json.dumps(gr.serialize(display_value=False, exclude_reference_link=False)), '{"test_field": {"value": "somevalue", "link": "https://dev00000.service-now.com/api/now/table/sys___/abcde12345"}}')
self.assertEqual(json.dumps(gr.serialize(display_value='both', exclude_reference_link=False)), '{"test_field": {"value": "somevalue", "display_value": "Some Value", "link": "https://dev00000.service-now.com/api/now/table/sys___/abcde12345"}}')

def test_set_element(self):
element = GlideElement('state', '3', 'Pending Change')

Expand Down
31 changes: 31 additions & 0 deletions test/test_snc_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,37 @@ def test_serialize_display(self):
self.assertEqual(data, {'intfield': 5, 'strfield': 'my string display value'})
client.session.close()

def test_serialize_reference_link(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('some_table')
gr.initialize()
gr.reffield = 'my reference'
gr.set_link('reffield', 'https://dev00000.service-now.com/api/now/table/sys___/abcde12345')

data = gr.serialize(exclude_reference_link=False)
self.assertIsNotNone(data)
self.assertEqual(gr.get_value('reffield'), 'my reference')
self.assertEqual(gr.get_link('reffield'), 'https://dev00000.service-now.com/api/now/table/sys___/abcde12345')
self.assertEqual(gr.serialize(exclude_reference_link=False), {'reffield':{'value': 'my reference','link':'https://dev00000.service-now.com/api/now/table/sys___/abcde12345'}})
self.assertEqual(data, {'reffield':{'value': 'my reference','link':'https://dev00000.service-now.com/api/now/table/sys___/abcde12345'}})
client.session.close()

def test_serialize_reference_link_all(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('some_table')
gr.initialize()
gr.reffield = 'my reference'
gr.set_link('reffield', 'https://dev00000.service-now.com/api/now/table/sys___/abcde12345')
gr.set_display_value('reffield', 'my reference display')

data = gr.serialize(exclude_reference_link=False)
self.assertIsNotNone(data)
self.assertEqual(gr.get_value('reffield'), 'my reference')
self.assertEqual(gr.get_link('reffield'), 'https://dev00000.service-now.com/api/now/table/sys___/abcde12345')
self.assertEqual(gr.serialize(exclude_reference_link=False), {'reffield':{'value': 'my reference','display_value': 'my reference display', 'link':'https://dev00000.service-now.com/api/now/table/sys___/abcde12345'}})
self.assertEqual(data, {'reffield':{'value': 'my reference','display_value': 'my reference display', 'link':'https://dev00000.service-now.com/api/now/table/sys___/abcde12345'}})
client.session.close()

def test_str(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('some_table')
Expand Down

0 comments on commit d6a4f7d

Please sign in to comment.