src/pyams_content_es/zmi/db.py
changeset 150 16da0050b814
child 170 6ca1818afe51
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content_es/zmi/db.py	Fri Mar 01 14:28:33 2019 +0100
@@ -0,0 +1,185 @@
+#
+# Copyright (c) 2008-2019 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+from itertools import tee
+
+from elasticsearch_dsl import Q, Search
+from pyramid.renderers import render
+from pyramid.view import view_config
+from pyramid_es import get_client
+from z3c.form import button, field
+from zope.interface import Interface, alsoProvides, implementer
+from zope.intid import IIntIds
+
+from pyams_content.shared.common import IWfSharedContent
+from pyams_content_es import _
+from pyams_content_es.interfaces import IContentIndexerUtility
+from pyams_form.form import AJAXAddForm, ajax_config
+from pyams_form.schema import CloseButton
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.interfaces.viewlet import ITableItemColumnActionsMenu
+from pyams_skin.layer import IPyAMSLayer
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_utils.container import find_objects_providing
+from pyams_utils.interfaces import MANAGE_SYSTEM_PERMISSION
+from pyams_utils.interfaces.data import IObjectData
+from pyams_utils.progress import init_progress_status, set_progress_status
+from pyams_utils.registry import get_utility, query_utility
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_workflow.interfaces import IWorkflowState
+from pyams_zmi.form import AdminDialogAddForm
+from pyams_zmi.layer import IAdminLayer
+from pyams_zmi.zmi.control_panel import UtilitiesTable
+
+
+@viewlet_config(name='check-index.menu', context=IContentIndexerUtility, layer=IAdminLayer,
+                view=UtilitiesTable, manager=ITableItemColumnActionsMenu, permission=MANAGE_SYSTEM_PERMISSION,
+                weight=10)
+class ContentIndexerDatabaseCheckerMenu(ToolbarMenuItem):
+    """Content indexer database checker menu"""
+
+    label = _("Check index contents...")
+    label_css_class = 'fa fa-fw fa-check'
+    url = 'check-index.html'
+    modal_target = True
+    stop_propagation = True
+
+
+class IContentIndexerDatabaseCheckerButtons(Interface):
+    """Content indexer database checker buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    check = button.Button(name='check', title=_("Check index"))
+
+
+@pagelet_config(name='check-index.html', context=IContentIndexerUtility, layer=IPyAMSLayer,
+                permission=MANAGE_SYSTEM_PERMISSION)
+@ajax_config(name='check-index.json', context=IContentIndexerUtility, layer=IPyAMSLayer,
+             base=AJAXAddForm)
+@implementer(IObjectData)
+class ContentIndexerDatabaseCheckerForm(AdminDialogAddForm):
+    """Content indexer database checker form"""
+
+    @property
+    def title(self):
+        return self.context.__name__
+
+    legend = _("Check index contents")
+    icon_css_class = 'fa fa-fw fa-check'
+
+    prefix = 'checker_form.'
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IContentIndexerDatabaseCheckerButtons)
+
+    edit_permission = MANAGE_SYSTEM_PERMISSION
+
+    object_data = {
+        'ams-form-hide-submit-footer': True
+    }
+
+    @property
+    def form_target(self):
+        return '#{}'.format(self.id)
+
+    def updateActions(self):
+        super(ContentIndexerDatabaseCheckerForm, self).updateActions()
+        if 'check' in self.actions:
+            action = self.actions['check']
+            action.addClass('btn-primary')
+            action.object_data = {
+                'ams-progress-handler': 'get-progress-status.json'
+            }
+            alsoProvides(action, IObjectData)
+
+    def createAndAdd(self, data):
+        request = self.request
+        translate = request.localizer.translate
+        client = get_client(request)
+        progress_id = request.params.get('progress_id')
+        if progress_id:
+            init_progress_status(progress_id, request.principal.id, "Elasticsearch index check")
+        results = {}
+        search = Search(using=client.es, index=client.index) \
+            .query(Q('exists', **{'field': 'content_type'})) \
+            .source(['internal_id', 'workflow.status'])
+        count, first, page_size = -1, 0, 100
+        while count != 0:
+            count = 0
+            items = search[first:first+page_size]
+            for index_item in items:
+                try:
+                    results[index_item.internal_id] = index_item.workflow.status
+                except AttributeError:
+                    pass
+                count += 1
+            first += count
+            if progress_id:
+                set_progress_status(progress_id,
+                                    message=translate(_("Loading index data ({})...")).format(first))
+        intids = get_utility(IIntIds)
+        for index, content in enumerate(find_objects_providing(request.root, IWfSharedContent)):
+            internal_id = intids.register(content)
+            state = IWorkflowState(content, None)
+            if (state is not None) and (results.get(internal_id) != state.state):
+                yield internal_id, content
+            if progress_id and not (index % 100):
+                set_progress_status(progress_id,
+                                    message=translate(_("Loading database contents ({})...")).format(index))
+        if progress_id:
+            set_progress_status(progress_id, 'finished')
+
+    def get_ajax_output(self, changes):
+        check, items = tee(changes)
+        try:
+            next(check)
+        except StopIteration:
+            return {
+                'status': 'success',
+                'content': {
+                    'html': render('templates/index-ok.pt', {'view': self},
+                                   request=self.request)
+                },
+                'close_form': False
+            }
+        else:
+            return {
+                'status': 'info',
+                'content': {
+                    'html': render('templates/index-updater.pt', {'view': self, 'items': items},
+                                   request=self.request)
+                },
+                'close_form': False
+            }
+
+    @staticmethod
+    def get_version(content):
+        return IWorkflowState(content).version_id
+
+
+@view_config(name='update-index.json', context=IContentIndexerUtility, request_type=IPyAMSLayer,
+             permission=MANAGE_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def update_index(request):
+    """Update given missing documents into index"""
+    indexer = request.context
+    intids = query_utility(IIntIds)
+    for name, value in request.params.items():
+        if name != 'internal_id':
+            continue
+        content = intids.queryObject(int(value))
+        if content is not None:
+            indexer.index_document(content)
+    return {
+        'status': 'success',
+        'message': request.localizer.translate(_("Requested contents have been re-indexed!"))
+    }