Notifcations - Adding "HTML Color" notification format option (#2837)

pull/2842/head
dgtlmoon 1 month ago committed by GitHub
parent c5fe188b28
commit a3a3ab0622
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -12,11 +12,12 @@ def customSequenceMatcher(
include_removed: bool = True, include_removed: bool = True,
include_added: bool = True, include_added: bool = True,
include_replaced: bool = True, include_replaced: bool = True,
include_change_type_prefix: bool = True include_change_type_prefix: bool = True,
html_colour: bool = False
) -> Iterator[List[str]]: ) -> Iterator[List[str]]:
""" """
Compare two sequences and yield differences based on specified parameters. Compare two sequences and yield differences based on specified parameters.
Args: Args:
before (List[str]): Original sequence before (List[str]): Original sequence
after (List[str]): Modified sequence after (List[str]): Modified sequence
@ -25,26 +26,33 @@ def customSequenceMatcher(
include_added (bool): Include added parts include_added (bool): Include added parts
include_replaced (bool): Include replaced parts include_replaced (bool): Include replaced parts
include_change_type_prefix (bool): Add prefixes to indicate change types include_change_type_prefix (bool): Add prefixes to indicate change types
html_colour (bool): Use HTML background colors for differences
Yields: Yields:
List[str]: Differences between sequences List[str]: Differences between sequences
""" """
cruncher = difflib.SequenceMatcher(isjunk=lambda x: x in " \t", a=before, b=after) cruncher = difflib.SequenceMatcher(isjunk=lambda x: x in " \t", a=before, b=after)
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
if include_equal and tag == 'equal': if include_equal and tag == 'equal':
yield before[alo:ahi] yield before[alo:ahi]
elif include_removed and tag == 'delete': elif include_removed and tag == 'delete':
prefix = "(removed) " if include_change_type_prefix else '' if html_colour:
yield [f"{prefix}{line}" for line in same_slicer(before, alo, ahi)] yield [f'<span style="background-color: #ffcecb;">{line}</span>' for line in same_slicer(before, alo, ahi)]
else:
yield [f"(removed) {line}" for line in same_slicer(before, alo, ahi)] if include_change_type_prefix else same_slicer(before, alo, ahi)
elif include_replaced and tag == 'replace': elif include_replaced and tag == 'replace':
prefix_changed = "(changed) " if include_change_type_prefix else '' if html_colour:
prefix_into = "(into) " if include_change_type_prefix else '' yield [f'<span style="background-color: #ffcecb;">{line}</span>' for line in same_slicer(before, alo, ahi)] + \
yield [f"{prefix_changed}{line}" for line in same_slicer(before, alo, ahi)] + \ [f'<span style="background-color: #dafbe1;">{line}</span>' for line in same_slicer(after, blo, bhi)]
[f"{prefix_into}{line}" for line in same_slicer(after, blo, bhi)] else:
yield [f"(changed) {line}" for line in same_slicer(before, alo, ahi)] + \
[f"(into) {line}" for line in same_slicer(after, blo, bhi)] if include_change_type_prefix else same_slicer(before, alo, ahi) + same_slicer(after, blo, bhi)
elif include_added and tag == 'insert': elif include_added and tag == 'insert':
prefix = "(added) " if include_change_type_prefix else '' if html_colour:
yield [f"{prefix}{line}" for line in same_slicer(after, blo, bhi)] yield [f'<span style="background-color: #dafbe1;">{line}</span>' for line in same_slicer(after, blo, bhi)]
else:
yield [f"(added) {line}" for line in same_slicer(after, blo, bhi)] if include_change_type_prefix else same_slicer(after, blo, bhi)
def render_diff( def render_diff(
previous_version_file_contents: str, previous_version_file_contents: str,
@ -55,11 +63,12 @@ def render_diff(
include_replaced: bool = True, include_replaced: bool = True,
line_feed_sep: str = "\n", line_feed_sep: str = "\n",
include_change_type_prefix: bool = True, include_change_type_prefix: bool = True,
patch_format: bool = False patch_format: bool = False,
html_colour: bool = False
) -> str: ) -> str:
""" """
Render the difference between two file contents. Render the difference between two file contents.
Args: Args:
previous_version_file_contents (str): Original file contents previous_version_file_contents (str): Original file contents
newest_version_file_contents (str): Modified file contents newest_version_file_contents (str): Modified file contents
@ -70,7 +79,8 @@ def render_diff(
line_feed_sep (str): Separator for lines in output line_feed_sep (str): Separator for lines in output
include_change_type_prefix (bool): Add prefixes to indicate change types include_change_type_prefix (bool): Add prefixes to indicate change types
patch_format (bool): Use patch format for output patch_format (bool): Use patch format for output
html_colour (bool): Use HTML background colors for differences
Returns: Returns:
str: Rendered difference str: Rendered difference
""" """
@ -88,10 +98,11 @@ def render_diff(
include_removed=include_removed, include_removed=include_removed,
include_added=include_added, include_added=include_added,
include_replaced=include_replaced, include_replaced=include_replaced,
include_change_type_prefix=include_change_type_prefix include_change_type_prefix=include_change_type_prefix,
html_colour=html_colour
) )
def flatten(lst: List[Union[str, List[str]]]) -> str: def flatten(lst: List[Union[str, List[str]]]) -> str:
return line_feed_sep.join(flatten(x) if isinstance(x, list) else x for x in lst) return line_feed_sep.join(flatten(x) if isinstance(x, list) else x for x in lst)
return flatten(rendered_diff) return flatten(rendered_diff)

@ -31,6 +31,7 @@ valid_notification_formats = {
'Text': NotifyFormat.TEXT, 'Text': NotifyFormat.TEXT,
'Markdown': NotifyFormat.MARKDOWN, 'Markdown': NotifyFormat.MARKDOWN,
'HTML': NotifyFormat.HTML, 'HTML': NotifyFormat.HTML,
'HTML Color': 'htmlcolor',
# Used only for editing a watch (not for global) # Used only for editing a watch (not for global)
default_notification_format_for_watch: default_notification_format_for_watch default_notification_format_for_watch: default_notification_format_for_watch
} }
@ -76,6 +77,9 @@ def process_notification(n_object, datastore):
# Get the notification body from datastore # Get the notification body from datastore
n_body = jinja_render(template_str=n_object.get('notification_body', ''), **notification_parameters) n_body = jinja_render(template_str=n_object.get('notification_body', ''), **notification_parameters)
if n_object.get('notification_format', '').startswith('HTML'):
n_body = n_body.replace("\n", '<br>')
n_title = jinja_render(template_str=n_object.get('notification_title', ''), **notification_parameters) n_title = jinja_render(template_str=n_object.get('notification_title', ''), **notification_parameters)
url = url.strip() url = url.strip()

@ -442,4 +442,61 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage
assert b"Error: You must have atleast one watch configured for 'test notification' to work" in res.data assert b"Error: You must have atleast one watch configured for 'test notification' to work" in res.data
def test_html_color_notifications(client, live_server, measure_memory_usage):
#live_server_setup(live_server)
set_original_response()
if os.path.isfile("test-datastore/notification.txt"):
os.unlink("test-datastore/notification.txt")
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
# otherwise other settings would have already existed from previous tests in this file
res = client.post(
url_for("settings_page"),
data={
"application-fetch_backend": "html_requests",
"application-minutes_between_check": 180,
"application-notification_body": '{{diff}}',
"application-notification_format": "HTML Color",
"application-notification_urls": test_notification_url,
"application-notification_title": "New ChangeDetection.io Notification - {{ watch_url }}",
},
follow_redirects=True
)
assert b'Settings updated' in res.data
test_url = url_for('test_endpoint', _external=True)
res = client.post(
url_for("form_quick_watch_add"),
data={"url": test_url, "tags": 'nice one'},
follow_redirects=True
)
assert b"Watch added" in res.data
wait_for_all_checks(client)
set_modified_response()
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
assert b'1 watches queued for rechecking.' in res.data
wait_for_all_checks(client)
time.sleep(3)
with open("test-datastore/notification.txt", 'r') as f:
x = f.read()
assert '<span style="background-color: #ffcecb;">Which is across multiple lines' in x
client.get(
url_for("form_delete", uuid="all"),
follow_redirects=True
)

@ -44,11 +44,17 @@ class update_worker(threading.Thread):
else: else:
snapshot_contents = "No snapshot/history available, the watch should fetch atleast once." snapshot_contents = "No snapshot/history available, the watch should fetch atleast once."
html_colour_enable = False
# HTML needs linebreak, but MarkDown and Text can use a linefeed # HTML needs linebreak, but MarkDown and Text can use a linefeed
if n_object.get('notification_format') == 'HTML': if n_object.get('notification_format') == 'HTML':
line_feed_sep = "<br>" line_feed_sep = "<br>"
# Snapshot will be plaintext on the disk, convert to some kind of HTML # Snapshot will be plaintext on the disk, convert to some kind of HTML
snapshot_contents = snapshot_contents.replace('\n', line_feed_sep) snapshot_contents = snapshot_contents.replace('\n', line_feed_sep)
elif n_object.get('notification_format') == 'HTML Color':
line_feed_sep = "<br>"
# Snapshot will be plaintext on the disk, convert to some kind of HTML
snapshot_contents = snapshot_contents.replace('\n', line_feed_sep)
html_colour_enable = True
else: else:
line_feed_sep = "\n" line_feed_sep = "\n"
@ -69,7 +75,7 @@ class update_worker(threading.Thread):
n_object.update({ n_object.update({
'current_snapshot': snapshot_contents, 'current_snapshot': snapshot_contents,
'diff': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=line_feed_sep), 'diff': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=line_feed_sep, html_colour=html_colour_enable),
'diff_added': diff.render_diff(prev_snapshot, current_snapshot, include_removed=False, line_feed_sep=line_feed_sep), 'diff_added': diff.render_diff(prev_snapshot, current_snapshot, include_removed=False, line_feed_sep=line_feed_sep),
'diff_full': diff.render_diff(prev_snapshot, current_snapshot, include_equal=True, line_feed_sep=line_feed_sep), 'diff_full': diff.render_diff(prev_snapshot, current_snapshot, include_equal=True, line_feed_sep=line_feed_sep),
'diff_patch': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=line_feed_sep, patch_format=True), 'diff_patch': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=line_feed_sep, patch_format=True),

Loading…
Cancel
Save