From 1f2277b134237aaa2f1d1e74bf97ae0ed9796fa0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 6 Sep 2021 15:51:27 +0200
Subject: [PATCH 1/2] logged-errors: add deprecated usages (#56613)

---
 tests/test_logged_errors.py | 31 +++++++++++++++++++++++++
 wcs/logged_errors.py        |  7 +++++-
 wcs/publisher.py            | 22 ++++++++++++++++--
 wcs/sql.py                  | 45 +++++++++++++++++++++----------------
 4 files changed, 83 insertions(+), 22 deletions(-)
 create mode 100644 tests/test_logged_errors.py

diff --git a/tests/test_logged_errors.py b/tests/test_logged_errors.py
new file mode 100644
index 00000000..86584e3a
--- /dev/null
+++ b/tests/test_logged_errors.py
@@ -0,0 +1,31 @@
+import pytest
+
+from .utilities import clean_temporary_pub, create_temporary_pub
+
+
+@pytest.fixture
+def pub(request):
+    return create_temporary_pub(sql_mode=True)
+
+
+def teardown_module(module):
+    clean_temporary_pub()
+
+
+def test_deprecated_error(pub):
+    pub.loggederror_class.wipe()
+
+    pub.record_deprecated_usage('foo bar')
+    assert pub.loggederror_class.count() == 1
+    error = pub.loggederror_class.select()[0]
+    assert error.summary == '[DEPRECATED] foo bar'
+    assert error.kind == 'deprecated_usage'
+    assert error.occurences_count == 1
+
+    pub.record_deprecated_usage('foo bar')
+    pub.record_deprecated_usage('foo bar')
+    assert pub.loggederror_class.count() == 1
+    error = pub.loggederror_class.select()[0]
+    assert error.summary == '[DEPRECATED] foo bar'
+    assert error.kind == 'deprecated_usage'
+    assert error.occurences_count == 3
diff --git a/wcs/logged_errors.py b/wcs/logged_errors.py
index b698ae2c..6bce05ca 100644
--- a/wcs/logged_errors.py
+++ b/wcs/logged_errors.py
@@ -28,6 +28,7 @@ class LoggedError:
     _names = 'logged-errors'
 
     id = None
+    kind = None
     tech_id = None
     summary = None
     formdef_class = None
@@ -58,9 +59,11 @@ class LoggedError:
         expression=None,
         expression_type=None,
         exception=None,
+        kind=None,
     ):
         # noqa pylint: disable=too-many-arguments
         error = cls()
+        error.kind = kind
         error.summary = str(error_summary)
         error.traceback = plain_error_msg
         error.expression = expression
@@ -98,6 +101,7 @@ class LoggedError:
         if not self.id:
             return
         self.occurences_count += 1
+        self.kind = error.kind
         self.latest_occurence_timestamp = now()
         # update with new error context
         self.formdata_id = error.formdata_id
@@ -111,7 +115,7 @@ class LoggedError:
         self.store()
 
     @classmethod
-    def record_error(cls, error_summary, plain_error_msg, publisher, *args, **kwargs):
+    def record_error(cls, error_summary, plain_error_msg, publisher, kind=None, *args, **kwargs):
         formdef = kwargs.pop('formdef', None)
         formdata = kwargs.pop('formdata', None)
         workflow = kwargs.pop('workflow', None)
@@ -137,6 +141,7 @@ class LoggedError:
             formdata=formdata,
             formdef=formdef,
             workflow=workflow,
+            kind=kind,
             *args,
             **kwargs,
         )
diff --git a/wcs/publisher.py b/wcs/publisher.py
index 8b2dd799..e76fae72 100644
--- a/wcs/publisher.py
+++ b/wcs/publisher.py
@@ -358,8 +358,19 @@ class WcsPublisher(QommonPublisher):
         conn.commit()
         cur.close()
 
+    def record_deprecated_usage(self, *args, **kwargs):
+        return self.record_error(context='[DEPRECATED]', deprecated_usage=True, *args, **kwargs)
+
     def record_error(
-        self, error_summary=None, context=None, exception=None, record=True, notify=False, *args, **kwargs
+        self,
+        error_summary=None,
+        context=None,
+        exception=None,
+        record=True,
+        notify=False,
+        deprecated_usage=False,
+        *args,
+        **kwargs,
     ):
         if not record and not notify:
             return
@@ -391,8 +402,15 @@ class WcsPublisher(QommonPublisher):
 
         logged_exception = None
         if record and self.loggederror_class:
+            kind = 'deprecated_usage' if deprecated_usage else None
             logged_exception = self.loggederror_class.record_error(
-                error_summary, plain_error_msg, publisher=self, exception=exception, *args, **kwargs
+                error_summary,
+                plain_error_msg,
+                publisher=self,
+                exception=exception,
+                kind=kind,
+                *args,
+                **kwargs,
             )
         if not notify or logged_exception and logged_exception.occurences_count > 1:
             # notify only first occurence
diff --git a/wcs/sql.py b/wcs/sql.py
index a535acb7..c5b6ba80 100644
--- a/wcs/sql.py
+++ b/wcs/sql.py
@@ -1092,23 +1092,24 @@ def do_loggederrors_table(concurrently=False):
     if cur.fetchone()[0] == 0:
         cur.execute(
             '''CREATE TABLE %s (id SERIAL PRIMARY KEY,
-                                        tech_id VARCHAR UNIQUE,
-                                        summary VARCHAR,
-                                        formdef_class VARCHAR,
-                                        formdata_id VARCHAR,
-                                        formdef_id VARCHAR,
-                                        workflow_id VARCHAR,
-                                        status_id VARCHAR,
-                                        status_item_id VARCHAR,
-                                        expression VARCHAR,
-                                        expression_type VARCHAR,
-                                        traceback TEXT,
-                                        exception_class VARCHAR,
-                                        exception_message VARCHAR,
-                                        occurences_count INTEGER,
-                                        first_occurence_timestamp TIMESTAMP WITH TIME ZONE,
-                                        latest_occurence_timestamp TIMESTAMP WITH TIME ZONE
-                                        )'''
+                                kind VARCHAR,
+                                tech_id VARCHAR UNIQUE,
+                                summary VARCHAR,
+                                formdef_class VARCHAR,
+                                formdata_id VARCHAR,
+                                formdef_id VARCHAR,
+                                workflow_id VARCHAR,
+                                status_id VARCHAR,
+                                status_item_id VARCHAR,
+                                expression VARCHAR,
+                                expression_type VARCHAR,
+                                traceback TEXT,
+                                exception_class VARCHAR,
+                                exception_message VARCHAR,
+                                occurences_count INTEGER,
+                                first_occurence_timestamp TIMESTAMP WITH TIME ZONE,
+                                latest_occurence_timestamp TIMESTAMP WITH TIME ZONE
+                                )'''
             % table_name
         )
     cur.execute(
@@ -1121,6 +1122,10 @@ def do_loggederrors_table(concurrently=False):
 
     needed_fields = {x[0] for x in LoggedError._table_static_fields}
 
+    # migrations
+    if 'kind' not in existing_fields:
+        cur.execute('''ALTER TABLE %s ADD COLUMN kind VARCHAR''' % table_name)
+
     # delete obsolete fields
     for field in existing_fields - needed_fields:
         cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field))
@@ -3033,6 +3038,7 @@ class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
     _table_name = 'loggederrors'
     _table_static_fields = [
         ('id', 'serial'),
+        ('kind', 'varchar'),
         ('tech_id', 'varchar'),
         ('summary', 'varchar'),
         ('formdef_class', 'varchar'),
@@ -3435,7 +3441,7 @@ def get_period_total(
 # latest migration, number + description (description is not used
 # programmaticaly but will make sure git conflicts if two migrations are
 # separately added with the same number)
-SQL_LEVEL = (52, 'store digests on formdata and carddata')
+SQL_LEVEL = (53, 'add kind column on logged_errors table')
 
 
 def migrate_global_views(conn, cur):
@@ -3614,9 +3620,10 @@ def migrate():
     if sql_level < 42:
         # 42: create snapshots table
         do_snapshots_table()
-    if sql_level < 48:
+    if sql_level < 53:
         # 47: store LoggedErrors in SQL
         # 48: remove acked attribute from LoggedError
+        # 53: add kind column to logged_errors table
         do_loggederrors_table()
     if sql_level < 50:
         # 49: store Role in SQL
-- 
2.30.2

