--- /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!"))
+ }