Add a diagnostic tab on archive transfer
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 11 Oct 2016 17:01:55 +0200
changeset 1688 0f3d38ffd42d
parent 1687 84ac160473a8
child 1733 15c2c768d215
Add a diagnostic tab on archive transfer displaying problems detected by the "doctor". Update internal structure so we may link to a sensible place in the UI where the error could be fixed (the correct tab).
entities/diag.py
i18n/en.po
i18n/fr.po
test/test_diag.py
test/test_views.py
views/archivetransfer.py
--- a/entities/diag.py	Tue Oct 11 16:46:35 2016 +0200
+++ b/entities/diag.py	Tue Oct 11 17:01:55 2016 +0200
@@ -24,13 +24,14 @@
 
 ALL_FORMATS = frozenset(('SEDA 2.0', 'SEDA 1.0', 'SEDA 0.2'))
 
-Rule = namedtuple('Rule', ['impacted_formats', 'message', 'watch'])
+Rule = namedtuple('Rule', ['impacted_formats', 'message', 'tab_id', 'watch'])
 RULES = {
     'seda1_need_access_rule': Rule(
         set(['SEDA 1.0']),
         _("First level archive unit must have an associated access rule to be exportable "
           "in SEDA 1. You may define it on the archive unit or as a default rule on the "
           "transfer."),
+        'seda_au_management_tab',
         set([
             'seda_archive_unit',
             'seda_alt_archive_unit_archive_unit_ref_id',
@@ -39,13 +40,19 @@
 }
 
 
-class CompatError(namedtuple('_CompatError', ['impacted_formats', 'message'])):
+class CompatError(namedtuple('_CompatError', ['impacted_formats', 'message', 'tab_id', 'entity'])):
     """Convenience class holding information about a problem in a profile forbidding export to some
-    format.
+    format:
+
+    * `impacted_formats`: set of formats that are no more available because of this error,
+    * `message`: message string explaining the problem,
+    * `entity`: 1st class entity where the error lies (one of archive unit, data object, etc.),
+    * `tab`: entity's tab where the problem may be fixed.
     """
-    def __new__(cls, rule_id):
+    def __new__(cls, rule_id, entity):
         rule = RULES[rule_id]
-        return super(CompatError, cls).__new__(cls, rule.impacted_formats, rule.message)
+        return super(CompatError, cls).__new__(cls, rule.impacted_formats, rule.message,
+                                               rule.tab_id, entity)
 
 
 class ISEDACompatAnalyzer(EntityAdapter):
@@ -66,12 +73,12 @@
         """Yield :class:`CompatError` describing a problem that prevents the given profile to be
         compatible with some format.
         """
-        for rule_id in self.failing_rule_ids():
-            yield CompatError(rule_id)
+        for rule_id, entity in self.failing_rules():
+            yield CompatError(rule_id, entity)
 
-    def failing_rule_ids(self):
-        """Yield rule identifiers describing a problem that prevents the given profile to be
-        compatible with some format.
+    def failing_rules(self):
+        """Yield (rule identifier, problematic entity) describing a problem that prevents the given
+        profile to be compatible with some format.
         """
         profile = self.entity
         # First level archive unit needs an access rule (SEDA 1)
@@ -79,4 +86,4 @@
             for archive_unit in profile.archive_units:
                 seq = archive_unit.first_level_choice.content_sequence
                 if not seq.reverse_seda_access_rule:
-                    yield 'seda1_need_access_rule'
+                    yield 'seda1_need_access_rule', archive_unit
--- a/i18n/en.po	Tue Oct 11 16:46:35 2016 +0200
+++ b/i18n/en.po	Tue Oct 11 17:01:55 2016 +0200
@@ -100,9 +100,24 @@
 "necessarily the identifier that will appear in the final sheet."
 msgstr ""
 
+msgid "Entity"
+msgstr ""
+
+msgid ""
+"First level archive unit must have an associated access rule to be "
+"exportable in SEDA 1. You may define it on the archive unit or as a default "
+"rule on the transfer."
+msgstr ""
+
 msgid "HTML documentation"
 msgstr ""
 
+msgid "Impacted formats"
+msgstr ""
+
+msgid "Message"
+msgstr ""
+
 msgid "New Agent"
 msgstr ""
 
@@ -529,9 +544,24 @@
 msgid "New SEDAwhen"
 msgstr ""
 
+msgid "No problem detected, congratulation!"
+msgstr ""
+
 msgid "Relationship target of"
 msgstr ""
 
+msgid "SEDA 0.2 RNG export"
+msgstr ""
+
+msgid "SEDA 0.2 XSD export"
+msgstr ""
+
+msgid "SEDA 1.0 RNG export"
+msgstr ""
+
+msgid "SEDA 1.0 XSD export"
+msgstr ""
+
 msgid "SEDA 2.0 RNG export"
 msgstr ""
 
@@ -1817,6 +1847,10 @@
 "`scheme_relation_type`)."
 msgstr ""
 
+#, python-brace-format
+msgid "Supported formats: {0}."
+msgstr ""
+
 msgid "This Agent"
 msgstr ""
 
@@ -4954,6 +4988,9 @@
 msgid "seda_at_data_objects_tab"
 msgstr ""
 
+msgid "seda_at_diagnose_tab"
+msgstr ""
+
 msgid "seda_at_management_tab"
 msgstr ""
 
--- a/i18n/fr.po	Tue Oct 11 16:46:35 2016 +0200
+++ b/i18n/fr.po	Tue Oct 11 17:01:55 2016 +0200
@@ -103,9 +103,27 @@
 "profil - ce n'est pas nécessairement l'identifiant qui sera utilisé dans les "
 "bordereaux de transfert"
 
+msgid "Entity"
+msgstr "Entité"
+
+msgid ""
+"First level archive unit must have an associated access rule to be "
+"exportable in SEDA 1. You may define it on the archive unit or as a default "
+"rule on the transfer."
+msgstr ""
+"Une unité d'archive de premier niveau doit être associée à une règle de "
+"communicabilité pour être exportable en SEDA 1. Vous pouvez la définir sur "
+"l'unité d'archive ou comme règle par défaut sur le transfer"
+
 msgid "HTML documentation"
 msgstr "documentation HTML"
 
+msgid "Impacted formats"
+msgstr "Formats impactés"
+
+msgid "Message"
+msgstr "Message"
+
 msgid "New Agent"
 msgstr ""
 
@@ -532,14 +550,29 @@
 msgid "New SEDAwhen"
 msgstr ""
 
+msgid "No problem detected, congratulation!"
+msgstr "Aucun problème détecté, félicitations !"
+
 msgid "Relationship target of"
 msgstr "Cible des relations"
 
+msgid "SEDA 0.2 RNG export"
+msgstr "export SEDA 0.2 RNG"
+
+msgid "SEDA 0.2 XSD export"
+msgstr "export SEDA 0.2 XSD"
+
+msgid "SEDA 1.0 RNG export"
+msgstr "export SEDA 1.0 RNG"
+
+msgid "SEDA 1.0 XSD export"
+msgstr "export SEDA 1.0 XSD"
+
 msgid "SEDA 2.0 RNG export"
-msgstr ""
+msgstr "export SEDA 2.0 RNG"
 
 msgid "SEDA 2.0 XSD export"
-msgstr ""
+msgstr "export SEDA 2.0 XSD"
 
 msgid "SEDA profile tree"
 msgstr "arbre du profil SEDA"
@@ -1820,6 +1853,10 @@
 "`scheme_relation_type`)."
 msgstr ""
 
+#, python-brace-format
+msgid "Supported formats: {0}."
+msgstr "Formats supportés : {0}."
+
 msgid "This Agent"
 msgstr ""
 
@@ -4964,6 +5001,9 @@
 msgid "seda_at_data_objects_tab"
 msgstr "objets-données"
 
+msgid "seda_at_diagnose_tab"
+msgstr "diagnostic"
+
 msgid "seda_at_management_tab"
 msgstr "gestion"
 
--- a/test/test_diag.py	Tue Oct 11 16:46:35 2016 +0200
+++ b/test/test_diag.py	Tue Oct 11 17:01:55 2016 +0200
@@ -35,7 +35,7 @@
             self.assertDiagnostic(doctor, ('SEDA 2.0', 'SEDA 0.2'), 'seda1_need_access_rule')
 
     def assertDiagnostic(self, doctor, expected_formats, *expected_rule_ids):
-        rule_ids = set(doctor.failing_rule_ids())
+        rule_ids = set(rule_id for rule_id, entity in doctor.failing_rules())
         self.assertEqual(rule_ids, set(expected_rule_ids))
         self.assertEqual(doctor.diagnose(), set(expected_formats))
         self.assertEqual(doctor.entity.compat_list, ', '.join(sorted(expected_formats)))
--- a/test/test_views.py	Tue Oct 11 16:46:35 2016 +0200
+++ b/test/test_views.py	Tue Oct 11 17:01:55 2016 +0200
@@ -22,7 +22,7 @@
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web import INTERNAL_FIELD_VALUE
 
-from testutils import create_transfer_to_bdo
+from testutils import create_transfer_to_bdo, create_archive_unit
 
 
 class ManagementRulesTC(CubicWebTC):
@@ -143,5 +143,16 @@
                          {'novalue_include_rtype': False, 'novalue_label': u'<no value specified>'})
 
 
+class ArchiveTransferDiagnoseTabTC(CubicWebTC):
+
+    def test_diagnose_tab(self):
+        with self.admin_access.web_request() as req:
+            transfer = req.create_entity('SEDAArchiveTransfer', title=u'diagnosis testing')
+            unit, unit_alt, unit_alt_seq = create_archive_unit(transfer)
+            req.cnx.commit()
+            # ensure the diagnosis tab display correctly
+            self.view('seda_at_diagnose_tab', req=req, rset=transfer.as_rset())
+
+
 if __name__ == '__main__':
     unittest.main()
--- a/views/archivetransfer.py	Tue Oct 11 16:46:35 2016 +0200
+++ b/views/archivetransfer.py	Tue Oct 11 17:01:55 2016 +0200
@@ -17,7 +17,7 @@
 
 from logilab.common.decorators import monkeypatch
 
-from cubicweb import _
+from cubicweb import _, tags
 from cubicweb.predicates import is_instance
 from cubicweb.web import formwidgets as fw
 from cubicweb.web.views import tabs, uicfg, reledit
@@ -44,6 +44,7 @@
 
 pvs.tag_attribute(('SEDAArchiveTransfer', 'title'), 'hidden')
 pvs.tag_attribute(('SEDAArchiveTransfer', 'simplified_profile'), 'hidden')
+pvs.tag_attribute(('SEDAArchiveTransfer', 'compat_list'), 'hidden')
 
 simplified_pvs = copy_rtag(pvs, __name__,
                            is_instance('SEDAArchiveTransfer') & simplified_profile())
@@ -86,6 +87,7 @@
         _('seda_at_data_objects_tab'),
         _('seda_at_archive_units_tab'),
         _('seda_at_related_transfers_tab'),
+        _('seda_at_diagnose_tab'),
     ]
 
 
@@ -195,6 +197,31 @@
       'seda_related_transfer_reference SEDAArchiveTransfer %(linkto)s)')
 
 
+class ArchiveTransferDiagnoseTab(viewlib.SubObjectsTab):
+    """Tab to diagnose supported format of an archive transfer"""
+    __regid__ = 'seda_at_diagnose_tab'
+    __select__ = (tabs.PrimaryTab.__select__
+                  & is_instance('SEDAArchiveTransfer'))
+
+    def entity_call(self, entity):
+        self.w(u'<p class="bg-info">')
+        self.w(self._cw._('Supported formats: {0}.').format(entity.compat_list))
+        self.w(u'</p>')
+        doctor = entity.cw_adapt_to('ISEDACompatAnalyzer')
+        data = [(tags.a(e.entity.dc_title(),
+                        href=e.entity.absolute_url(tab=e.tab_id)),
+                 ', '.join(e.impacted_formats),
+                 self._cw._(e.message))
+                for e in doctor.detect_problems()]
+        if data:
+            self.wview('pyvaltable', pyvalue=data,
+                       headers=(self._cw._('Entity'),
+                                self._cw._('Impacted formats'),
+                                self._cw._('Message')))
+        else:
+            self.w(self._cw._('No problem detected, congratulation!'))
+
+
 @monkeypatch(reledit.AutoClickAndEditFormView)
 def _compute_formid_value(self, rschema, role, rvid, formid):
     """Overriden to give rtype/role information to the view"""