Introduce an AJAX button for sending test notifications instead of the checkbox (#519)

pull/522/head
dgtlmoon 3 years ago committed by GitHub
parent 2620818ba7
commit 3034d047c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -400,6 +400,37 @@ def changedetection_app(config=None, datastore_o=None):
return output return output
# AJAX endpoint for sending a test
@app.route("/notification/send-test", methods=['POST'])
@login_required
def ajax_callback_send_notification_test():
import apprise
apobj = apprise.Apprise()
# validate URLS
if not len(request.form['notification_urls'].strip()):
return make_response({'error': 'No Notification URLs set'}, 400)
for server_url in request.form['notification_urls'].splitlines():
if not apobj.add(server_url):
message = '{} is not a valid AppRise URL.'.format(server_url)
return make_response({'error': message}, 400)
try:
n_object = {'watch_url': request.form['window_url'],
'notification_urls': request.form['notification_urls'].splitlines(),
'notification_title': request.form['notification_title'].strip(),
'notification_body': request.form['notification_body'].strip(),
'notification_format': request.form['notification_format'].strip()
}
notification_q.put(n_object)
except Exception as e:
return make_response({'error': str(e)}, 400)
return 'OK'
@app.route("/scrub", methods=['GET', 'POST']) @app.route("/scrub", methods=['GET', 'POST'])
@login_required @login_required
def scrub_page(): def scrub_page():
@ -561,20 +592,6 @@ def changedetection_app(config=None, datastore_o=None):
# Queue the watch for immediate recheck # Queue the watch for immediate recheck
update_q.put(uuid) update_q.put(uuid)
if form.trigger_check.data:
if len(form.notification_urls.data):
n_object = {'watch_url': form.url.data.strip(),
'notification_urls': form.notification_urls.data,
'notification_title': form.notification_title.data,
'notification_body': form.notification_body.data,
'notification_format': form.notification_format.data,
'uuid': uuid
}
notification_q.put(n_object)
flash('Test notification queued.')
else:
flash('No notification URLs set, cannot send test.', 'error')
# Diff page [edit] link should go back to diff page # Diff page [edit] link should go back to diff page
if request.args.get("next") and request.args.get("next") == 'diff' and not form.save_and_preview_button.data: if request.args.get("next") and request.args.get("next") == 'diff' and not form.save_and_preview_button.data:
return redirect(url_for('diff_history_page', uuid=uuid)) return redirect(url_for('diff_history_page', uuid=uuid))
@ -650,19 +667,6 @@ def changedetection_app(config=None, datastore_o=None):
datastore.data['settings']['application']['ignore_whitespace'] = form.ignore_whitespace.data datastore.data['settings']['application']['ignore_whitespace'] = form.ignore_whitespace.data
datastore.data['settings']['application']['real_browser_save_screenshot'] = form.real_browser_save_screenshot.data datastore.data['settings']['application']['real_browser_save_screenshot'] = form.real_browser_save_screenshot.data
if form.trigger_check.data:
if len(form.notification_urls.data):
n_object = {'watch_url': "Test from changedetection.io!",
'notification_urls': form.notification_urls.data,
'notification_title': form.notification_title.data,
'notification_body': form.notification_body.data,
'notification_format': form.notification_format.data,
}
notification_q.put(n_object)
flash('Test notification queued.')
else:
flash('No notification URLs set, cannot send test.', 'error')
if not os.getenv("SALTED_PASS", False) and form.password.encrypted_password: if not os.getenv("SALTED_PASS", False) and form.password.encrypted_password:
datastore.data['settings']['application']['password'] = form.password.encrypted_password datastore.data['settings']['application']['password'] = form.password.encrypted_password
flash("Password protection enabled.", 'notice') flash("Password protection enabled.", 'notice')
@ -1027,10 +1031,14 @@ def changedetection_app(config=None, datastore_o=None):
flash("Error") flash("Error")
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route("/api/delete", methods=['GET']) @app.route("/api/delete", methods=['GET'])
@login_required @login_required
def api_delete(): def api_delete():
uuid = request.args.get('uuid') uuid = request.args.get('uuid')
# More for testing, possible to return the first/only
if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop()
datastore.delete(uuid) datastore.delete(uuid)
flash('Deleted.') flash('Deleted.')

@ -306,7 +306,6 @@ class commonSettingsForm(Form):
notification_title = StringField('Notification Title', default=default_notification_title, validators=[validators.Optional(), ValidateTokensList()]) notification_title = StringField('Notification Title', default=default_notification_title, validators=[validators.Optional(), ValidateTokensList()])
notification_body = TextAreaField('Notification Body', default=default_notification_body, validators=[validators.Optional(), ValidateTokensList()]) notification_body = TextAreaField('Notification Body', default=default_notification_body, validators=[validators.Optional(), ValidateTokensList()])
notification_format = SelectField('Notification Format', choices=valid_notification_formats.keys(), default=default_notification_format) notification_format = SelectField('Notification Format', choices=valid_notification_formats.keys(), default=default_notification_format)
trigger_check = BooleanField('Send test notification on save')
fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()]) fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False) extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False)

@ -0,0 +1,42 @@
$(document).ready(function() {
$('#send-test-notification').click(function (e) {
e.preventDefault();
// this can be global
var csrftoken = $('input[name=csrf_token]').val();
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
})
data = {
window_url : window.location.href,
notification_urls : $('#notification_urls').val(),
notification_title : $('#notification_title').val(),
notification_body : $('#notification_body').val(),
notification_format : $('#notification_format').val(),
}
for (key in data) {
if (!data[key].length) {
alert(key+" is empty, cannot send test.")
return;
}
}
$.ajax({
type: "POST",
url: notification_base_url,
data : data
}).done(function(data){
console.log(data);
alert('Sent');
}).fail(function(data){
console.log(data);
alert('Error: '+data.responseJSON.error);
})
});
});

@ -18,6 +18,8 @@
<li>Go here for <a href="{{url_for('notification_logs')}}">Notification debug logs</a></li> <li>Go here for <a href="{{url_for('notification_logs')}}">Notification debug logs</a></li>
</ul> </ul>
</div> </div>
<br/>
<a id="send-test-notification" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Send test notification</a>
</div> </div>
<div id="notification-customisation"> <div id="notification-customisation">
<div class="pure-control-group"> <div class="pure-control-group">
@ -93,7 +95,4 @@
</span> </span>
</div> </div>
</div> </div>
<div class="pure-control-group">
{{ render_field(form.trigger_check) }}
</div>
{% endmacro %} {% endmacro %}

@ -17,6 +17,7 @@
background-image: url({{url_for('static_content', group='images', filename='gradient-border.png')}}); background-image: url({{url_for('static_content', group='images', filename='gradient-border.png')}});
} }
</style> </style>
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='jquery-3.6.0.min.js')}}"></script>
</head> </head>
<body> <body>

@ -4,6 +4,10 @@
{% from '_helpers.jinja' import render_button %} {% from '_helpers.jinja' import render_button %}
{% from '_common_fields.jinja' import render_common_settings_form %} {% from '_common_fields.jinja' import render_common_settings_form %}
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script> <script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
<script>
var notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
</script>
<div class="edit-form monospaced-textarea"> <div class="edit-form monospaced-textarea">

@ -6,7 +6,10 @@
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='settings.js')}}" defer></script> <script type="text/javascript" src="{{url_for('static_content', group='js', filename='settings.js')}}" defer></script>
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script> <script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
<script>
var notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
</script>
<div class="edit-form"> <div class="edit-form">
<div class="tabs collapsable"> <div class="tabs collapsable">
<ul> <ul>

@ -2,15 +2,17 @@ import os
import time import time
import re import re
from flask import url_for from flask import url_for
from . util import set_original_response, set_modified_response, live_server_setup from . util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup
import logging import logging
from changedetectionio.notification import default_notification_body, default_notification_title from changedetectionio.notification import default_notification_body, default_notification_title
def test_setup(live_server):
live_server_setup(live_server)
# Hard to just add more live server URLs when one test is already running (I think) # Hard to just add more live server URLs when one test is already running (I think)
# So we add our test here (was in a different file) # So we add our test here (was in a different file)
def test_check_notification(client, live_server): def test_check_notification(client, live_server):
live_server_setup(live_server)
set_original_response() set_original_response()
# Give the endpoint time to spin up # Give the endpoint time to spin up
@ -49,9 +51,8 @@ def test_check_notification(client, live_server):
notification_url = url.replace('http', 'json') notification_url = url.replace('http', 'json')
print (">>>> Notification URL: "+notification_url) print (">>>> Notification URL: "+notification_url)
res = client.post(
url_for("edit_page", uuid="first"), notification_form_data = {"notification_urls": notification_url,
data={"notification_urls": notification_url,
"notification_title": "New ChangeDetection.io Notification - {watch_url}", "notification_title": "New ChangeDetection.io Notification - {watch_url}",
"notification_body": "BASE URL: {base_url}\n" "notification_body": "BASE URL: {base_url}\n"
"Watch URL: {watch_url}\n" "Watch URL: {watch_url}\n"
@ -64,69 +65,62 @@ def test_check_notification(client, live_server):
"Diff: {diff}\n" "Diff: {diff}\n"
"Diff Full: {diff_full}\n" "Diff Full: {diff_full}\n"
":-)", ":-)",
"notification_format": "Text", "notification_format": "Text"}
notification_form_data.update({
"url": test_url, "url": test_url,
"tag": "my tag", "tag": "my tag",
"title": "my title", "title": "my title",
"headers": "", "headers": "",
"fetch_backend": "html_requests", "fetch_backend": "html_requests"})
"trigger_check": "y"},
res = client.post(
url_for("edit_page", uuid="first"),
data=notification_form_data,
follow_redirects=True follow_redirects=True
) )
assert b"Updated watch." in res.data assert b"Updated watch." in res.data
assert b"Test notification queued" in res.data
# Hit the edit page, be sure that we saved it # Hit the edit page, be sure that we saved it
# Re #242 - wasnt saving?
res = client.get( res = client.get(
url_for("edit_page", uuid="first")) url_for("edit_page", uuid="first"))
assert bytes(notification_url.encode('utf-8')) in res.data assert bytes(notification_url.encode('utf-8')) in res.data
# Re #242 - wasnt saving?
assert bytes("New ChangeDetection.io Notification".encode('utf-8')) in res.data assert bytes("New ChangeDetection.io Notification".encode('utf-8')) in res.data
# Because we hit 'send test notification on save' ## Now recheck, and it should have sent the notification
time.sleep(3) time.sleep(3)
set_modified_response()
notification_submission = None notification_submission = None
# Verify what was sent as a notification, this file should exist
with open("test-datastore/notification.txt", "r") as f:
notification_submission = f.read()
# Did we see the URL that had a change, in the notification?
assert test_url in notification_submission
os.unlink("test-datastore/notification.txt")
set_modified_response()
# Trigger a check # Trigger a check
client.get(url_for("api_watch_checknow"), follow_redirects=True) client.get(url_for("api_watch_checknow"), follow_redirects=True)
# Give the thread time to pick it up
time.sleep(3) time.sleep(3)
# Verify what was sent as a notification, this file should exist
# Did the front end see it?
res = client.get(
url_for("index"))
assert bytes("just now".encode('utf-8')) in res.data
notification_submission=None
# Verify what was sent as a notification
with open("test-datastore/notification.txt", "r") as f: with open("test-datastore/notification.txt", "r") as f:
notification_submission = f.read() notification_submission = f.read()
# Did we see the URL that had a change, in the notification? os.unlink("test-datastore/notification.txt")
assert test_url in notification_submission
# Did we see the URL that had a change, in the notification?
# Diff was correctly executed # Diff was correctly executed
assert test_url in notification_submission
assert ':-)' in notification_submission
assert "Diff Full: Some initial text" in notification_submission assert "Diff Full: Some initial text" in notification_submission
assert "Diff: (changed) Which is across multiple lines" in notification_submission assert "Diff: (changed) Which is across multiple lines" in notification_submission
assert "(into ) which has this one new line" in notification_submission assert "(into ) which has this one new line" in notification_submission
# Re #342 - check for accidental python byte encoding of non-utf8/string
assert "b'" not in notification_submission
assert re.search('Watch UUID: [0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', notification_submission, re.IGNORECASE)
assert "Watch title: my title" in notification_submission
assert "Watch tag: my tag" in notification_submission
assert "diff/" in notification_submission
assert "preview/" in notification_submission
assert ":-)" in notification_submission
assert "New ChangeDetection.io Notification - {}".format(test_url) in notification_submission
if env_base_url: if env_base_url:
# Re #65 - did we see our BASE_URl ? # Re #65 - did we see our BASE_URl ?
@ -135,50 +129,17 @@ def test_check_notification(client, live_server):
else: else:
logging.debug(">>> Skipping BASE_URL check") logging.debug(">>> Skipping BASE_URL check")
## Now configure something clever, we go into custom config (non-default) mode, this is returned by the endpoint
with open("test-datastore/endpoint-content.txt", "w") as f:
f.write(";jasdhflkjadshf kjhsdfkjl ahslkjf haslkjd hfaklsj hf\njl;asdhfkasj stuff we will detect\n")
res = client.post(
url_for("settings_page"),
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
"notification_urls": "json://foobar.com", #Re #143 should not see that it sent without [test checkbox]
"minutes_between_check": 180,
"fetch_backend": "html_requests",
},
follow_redirects=True
)
assert b"Settings updated." in res.data
# Re #143 - should not see this if we didnt hit the test box
assert b"Test notification queued" not in res.data
# Trigger a check # This should insert the {current_snapshot}
set_more_modified_response()
client.get(url_for("api_watch_checknow"), follow_redirects=True) client.get(url_for("api_watch_checknow"), follow_redirects=True)
# Give the thread time to pick it up
time.sleep(3) time.sleep(3)
# Verify what was sent as a notification, this file should exist
# Did the front end see it?
res = client.get(
url_for("index"))
assert bytes("just now".encode('utf-8')) in res.data
with open("test-datastore/notification.txt", "r") as f: with open("test-datastore/notification.txt", "r") as f:
notification_submission = f.read() notification_submission = f.read()
print ("Notification submission was:", notification_submission) assert "Ohh yeah awesome" in notification_submission
# Re #342 - check for accidental python byte encoding of non-utf8/string
assert "b'" not in notification_submission
assert re.search('Watch UUID: [0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', notification_submission, re.IGNORECASE)
assert "Watch title: my title" in notification_submission
assert "Watch tag: my tag" in notification_submission
assert "diff/" in notification_submission
assert "preview/" in notification_submission
assert ":-)" in notification_submission
assert "New ChangeDetection.io Notification - {}".format(test_url) in notification_submission
# This should insert the {current_snapshot}
assert "stuff we will detect" in notification_submission
# Prove that "content constantly being marked as Changed with no Updating causes notification" is not a thing # Prove that "content constantly being marked as Changed with no Updating causes notification" is not a thing
# https://github.com/dgtlmoon/changedetection.io/discussions/192 # https://github.com/dgtlmoon/changedetection.io/discussions/192
@ -186,33 +147,39 @@ def test_check_notification(client, live_server):
# Trigger a check # Trigger a check
client.get(url_for("api_watch_checknow"), follow_redirects=True) client.get(url_for("api_watch_checknow"), follow_redirects=True)
time.sleep(3) time.sleep(1)
client.get(url_for("api_watch_checknow"), follow_redirects=True) client.get(url_for("api_watch_checknow"), follow_redirects=True)
time.sleep(3) time.sleep(1)
client.get(url_for("api_watch_checknow"), follow_redirects=True) client.get(url_for("api_watch_checknow"), follow_redirects=True)
time.sleep(3) time.sleep(1)
assert os.path.exists("test-datastore/notification.txt") == False assert os.path.exists("test-datastore/notification.txt") == False
# cleanup for the next
client.get(
url_for("api_delete", uuid="first"),
follow_redirects=True
)
# Now adding a wrong token should give us an error
def test_notification_validation(client, live_server):
#live_server_setup(live_server)
time.sleep(3)
# re #242 - when you edited an existing new entry, it would not correctly show the notification settings
# Add our URL to the import page
test_url = url_for('test_endpoint', _external=True)
res = client.post( res = client.post(
url_for("settings_page"), url_for("api_watch_add"),
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}", data={"url": test_url, "tag": 'nice one'},
"notification_body": "Rubbish: {rubbish}\n",
"notification_format": "Text",
"notification_urls": "json://foobar.com",
"minutes_between_check": 180,
"fetch_backend": "html_requests"
},
follow_redirects=True follow_redirects=True
) )
with open("xxx.bin", "wb") as f:
assert bytes("is not a valid token".encode('utf-8')) in res.data f.write(res.data)
assert b"Watch added" in res.data
# Re #360 some validation # Re #360 some validation
res = client.post( res = client.post(
url_for("edit_page", uuid="first"), url_for("edit_page", uuid="first"),
data={"notification_urls": notification_url, data={"notification_urls": 'json://localhost/foobar',
"notification_title": "", "notification_title": "",
"notification_body": "", "notification_body": "",
"notification_format": "Text", "notification_format": "Text",
@ -220,8 +187,28 @@ def test_check_notification(client, live_server):
"tag": "my tag", "tag": "my tag",
"title": "my title", "title": "my title",
"headers": "", "headers": "",
"fetch_backend": "html_requests", "fetch_backend": "html_requests"},
"trigger_check": "y"},
follow_redirects=True follow_redirects=True
) )
assert b"Notification Body and Title is required when a Notification URL is used" in res.data assert b"Notification Body and Title is required when a Notification URL is used" in res.data
# Now adding a wrong token should give us an error
res = client.post(
url_for("settings_page"),
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
"notification_body": "Rubbish: {rubbish}\n",
"notification_format": "Text",
"notification_urls": "json://localhost/foobar",
"time_between_check": {'seconds': 180},
"fetch_backend": "html_requests"
},
follow_redirects=True
)
assert bytes("is not a valid token".encode('utf-8')) in res.data
# cleanup for the next
client.get(
url_for("api_delete", uuid="first"),
follow_redirects=True
)

@ -35,6 +35,24 @@ def set_modified_response():
return None return None
def set_more_modified_response():
test_return_data = """<html>
<head><title>modified head title</title></head>
<body>
Some initial text</br>
<p>which has this one new line</p>
</br>
So let's see what happens. </br>
Ohh yeah awesome<br/>
</body>
</html>
"""
with open("test-datastore/endpoint-content.txt", "w") as f:
f.write(test_return_data)
return None
def live_server_setup(live_server): def live_server_setup(live_server):
@ -82,7 +100,7 @@ def live_server_setup(live_server):
if data != None: if data != None:
f.write(data) f.write(data)
print("\n>> Test notification endpoint was hit.\n") print("\n>> Test notification endpoint was hit.\n", data)
return "Text was set" return "Text was set"

Loading…
Cancel
Save