From 0285d00f131e9d2ef7875735eae10cc4f6e8ace2 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Fri, 17 Nov 2023 17:21:26 +0100 Subject: [PATCH] UI - Clicking the "[Diff]" link should take you to the difference starting at the relative time to when you last viewed the difference page (#1989) --- .github/workflows/test-only.yml | 1 + changedetectionio/__init__.py | 4 +- changedetectionio/model/Watch.py | 32 +++++++++++ .../templates/watch-overview.html | 13 ++++- .../tests/unit/test_watch_model.py | 54 +++++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 changedetectionio/tests/unit/test_watch_model.py diff --git a/.github/workflows/test-only.yml b/.github/workflows/test-only.yml index 3064e97d..7acb7c7a 100644 --- a/.github/workflows/test-only.yml +++ b/.github/workflows/test-only.yml @@ -51,6 +51,7 @@ jobs: run: | # Unit tests docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_notification_diff' + docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_watch_model' # All tests docker run --network changedet-network test-changedetectionio bash -c 'cd changedetectionio && ./run_basic_tests.sh' diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 9edea3cc..6a95b156 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -961,7 +961,7 @@ def changedetection_app(config=None, datastore_o=None): # Read as binary and force decode as UTF-8 # Windows may fail decode in python if we just use 'r' mode (chardet decode exception) from_version = request.args.get('from_version') - from_version_index = -2 # second newest + from_version_index = -2 # second newest if from_version and from_version in dates: from_version_index = dates.index(from_version) else: @@ -970,7 +970,7 @@ def changedetection_app(config=None, datastore_o=None): try: from_version_file_contents = watch.get_history_snapshot(dates[from_version_index]) except Exception as e: - from_version_file_contents = "Unable to read to-version at index{}.\n".format(dates[from_version_index]) + from_version_file_contents = f"Unable to read to-version at index {dates[from_version_index]}.\n" to_version = request.args.get('to_version') to_version_index = -1 diff --git a/changedetectionio/model/Watch.py b/changedetectionio/model/Watch.py index 90858e39..50cbb6b3 100644 --- a/changedetectionio/model/Watch.py +++ b/changedetectionio/model/Watch.py @@ -262,6 +262,38 @@ class model(dict): bump = self.history return self.__newest_history_key + # Given an arbitrary timestamp, find the closest next key + # For example, last_viewed = 1000 so it should return the next 1001 timestamp + # + # used for the [diff] button so it can preset a smarter from_version + @property + def get_next_snapshot_key_to_last_viewed(self): + + """Unfortunately for now timestamp is stored as string key""" + keys = list(self.history.keys()) + if not keys: + return None + + last_viewed = int(self.get('last_viewed')) + prev_k = keys[0] + sorted_keys = sorted(keys, key=lambda x: int(x)) + sorted_keys.reverse() + + # When the 'last viewed' timestamp is greater than the newest snapshot, return second last + if last_viewed > int(sorted_keys[0]): + return sorted_keys[1] + + for k in sorted_keys: + if int(k) < last_viewed: + if prev_k == sorted_keys[0]: + # Return the second last one so we dont recommend the same version compares itself + return sorted_keys[1] + + return prev_k + prev_k = k + + return keys[0] + def get_history_snapshot(self, timestamp): import brotli filepath = self.history[timestamp] diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/templates/watch-overview.html index 4b04ead0..85620356 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/templates/watch-overview.html @@ -82,12 +82,15 @@ {% endif %} {% for watch in (watches|sort(attribute=sort_attribute, reverse=sort_order == 'asc'))|pagination_slice(skip=pagination.skip) %} + + {% set is_unviewed = watch.newest_history_key| int > watch.last_viewed and watch.history_n>=2 %} + {{ loop.index+pagination.skip }} @@ -167,7 +170,13 @@ class="recheck pure-button pure-button-primary">{% if watch.uuid in queued_uuids %}Queued{% else %}Recheck{% endif %} Edit {% if watch.history_n >= 2 %} - Diff + + {% if is_unviewed %} + Diff + {% else %} + Diff + {% endif %} + {% else %} {% if watch.history_n == 1 or (watch.history_n ==0 and watch.error_text_ctime )%} Preview diff --git a/changedetectionio/tests/unit/test_watch_model.py b/changedetectionio/tests/unit/test_watch_model.py new file mode 100644 index 00000000..78ca60b2 --- /dev/null +++ b/changedetectionio/tests/unit/test_watch_model.py @@ -0,0 +1,54 @@ +#!/usr/bin/python3 + +# run from dir above changedetectionio/ dir +# python3 -m unittest changedetectionio.tests.unit.test_notification_diff + +import unittest +import os + +from changedetectionio.model import Watch + +# mostly +class TestDiffBuilder(unittest.TestCase): + + def test_watch_get_suggested_from_diff_timestamp(self): + import uuid as uuid_builder + watch = Watch.model(datastore_path='/tmp', default={}) + watch.ensure_data_dir_exists() + + watch['last_viewed'] = 110 + + watch.save_history_text(contents=b"hello world", timestamp=100, snapshot_id=str(uuid_builder.uuid4())) + watch.save_history_text(contents=b"hello world", timestamp=105, snapshot_id=str(uuid_builder.uuid4())) + watch.save_history_text(contents=b"hello world", timestamp=109, snapshot_id=str(uuid_builder.uuid4())) + watch.save_history_text(contents=b"hello world", timestamp=112, snapshot_id=str(uuid_builder.uuid4())) + watch.save_history_text(contents=b"hello world", timestamp=115, snapshot_id=str(uuid_builder.uuid4())) + watch.save_history_text(contents=b"hello world", timestamp=117, snapshot_id=str(uuid_builder.uuid4())) + + p = watch.get_next_snapshot_key_to_last_viewed + assert p == "112", "Correct last-viewed timestamp was detected" + + # When there is only one step of difference from the end of the list, it should return second-last change + watch['last_viewed'] = 116 + p = watch.get_next_snapshot_key_to_last_viewed + assert p == "115", "Correct 'second last' last-viewed timestamp was detected when using the last timestamp" + + watch['last_viewed'] = 99 + p = watch.get_next_snapshot_key_to_last_viewed + assert p == "100" + + watch['last_viewed'] = 200 + p = watch.get_next_snapshot_key_to_last_viewed + assert p == "115", "When the 'last viewed' timestamp is greater than the newest snapshot, return second last " + + watch['last_viewed'] = 109 + p = watch.get_next_snapshot_key_to_last_viewed + assert p == "109", "Correct when its the same time" + + # new empty one + watch = Watch.model(datastore_path='/tmp', default={}) + p = watch.get_next_snapshot_key_to_last_viewed + assert p == None, "None when no history available" + +if __name__ == '__main__': + unittest.main()