From 6d2af76576039ce2f780413ccfd24b8692d8cf88 Mon Sep 17 00:00:00 2001 From: nathangray Date: Thu, 23 Jul 2020 13:34:08 -0600 Subject: [PATCH] * Infolog: Push updates --- api/js/jsapi/app_base.js | 2 +- api/js/jsapi/egw_app.js | 1 - api/js/jsapi/egw_app.ts | 1 - infolog/inc/class.infolog_bo.inc.php | 14 +++- infolog/inc/class.infolog_hooks.inc.php | 1 + infolog/inc/class.infolog_ui.inc.php | 6 +- infolog/js/app.js | 84 ++++++++++++++++++++++-- infolog/js/app.ts | 86 ++++++++++++++++++++++++- 8 files changed, 181 insertions(+), 14 deletions(-) diff --git a/api/js/jsapi/app_base.js b/api/js/jsapi/app_base.js index a6ee02ed07..f5ff1b1302 100644 --- a/api/js/jsapi/app_base.js +++ b/api/js/jsapi/app_base.js @@ -248,7 +248,7 @@ var AppJS = (function(){ "use strict"; return Class.extend( * @param uid * @param current_order */ - nm_refresh_add: function(nm, uid, current_order) + nm_refresh_index: function(nm, uid, current_order) { // Do we have a modified field so we can check nm sort order? if(this.modification_field_name) diff --git a/api/js/jsapi/egw_app.js b/api/js/jsapi/egw_app.js index cc04b098f4..978109f36e 100644 --- a/api/js/jsapi/egw_app.js +++ b/api/js/jsapi/egw_app.js @@ -193,7 +193,6 @@ var EgwApp = /** @class */ (function () { */ EgwApp.prototype.nm_refresh_index = function (nm, uid, current_order, update_type) { var _a; - return false; // Do we have a modified field so we can check nm sort order? if (this.modification_field_name) { var value = nm.getValue(); diff --git a/api/js/jsapi/egw_app.ts b/api/js/jsapi/egw_app.ts index 9c5550435f..31975bdf17 100644 --- a/api/js/jsapi/egw_app.ts +++ b/api/js/jsapi/egw_app.ts @@ -296,7 +296,6 @@ export abstract class EgwApp */ nm_refresh_index(nm: et2_nextmatch, uid: string, current_order: string[], update_type: string) : number|boolean { - return false; // Do we have a modified field so we can check nm sort order? if(this.modification_field_name) { diff --git a/infolog/inc/class.infolog_bo.inc.php b/infolog/inc/class.infolog_bo.inc.php index 6578e9b6d4..575c6047ab 100644 --- a/infolog/inc/class.infolog_bo.inc.php +++ b/infolog/inc/class.infolog_bo.inc.php @@ -716,6 +716,8 @@ class infolog_bo } if ($info['info_status'] != 'deleted') // dont notify of final purge of already deleted items { + Link::notify_update('infolog',$info_id,$info, 'delete'); + // send email notifications and do the history logging if(!$skip_notification) { @@ -749,9 +751,10 @@ class infolog_bo $skip_notification=false, $throw_exception=false, $purge_cfs=null, $ignore_acl=false) { $values = $values_in; + $change_type = 'update'; //echo "boinfolog::write()values="; _debug_array($values); - if (!$ignore_acl && (!$values['info_id'] && !$this->check_access(0,Acl::EDIT,$values['info_owner']) && - !$this->check_access(0,Acl::ADD,$values['info_owner']))) + if (!$ignore_acl && (!$values['info_id'] && !$this->check_access(0, Acl::EDIT, $values['info_owner']) && + !$this->check_access(0, Acl::ADD, $values['info_owner']))) { return false; } @@ -760,6 +763,10 @@ class infolog_bo { $old = $this->read($values['info_id'], false, 'server', $ignore_acl); } + else + { + $change_type = 'add'; + } if (($status_only = !$ignore_acl && $values['info_id'] && !$this->check_access($values,Acl::EDIT))) { @@ -830,6 +837,7 @@ class infolog_bo if ($forcestatus && !in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = $status; } $check_defaults = false; + $change_type = 'update'; } if ($check_defaults) { @@ -1012,7 +1020,7 @@ class infolog_bo } // notify the link-class about the update, as other apps may be subscribt to it - Link::notify_update('infolog',$info_id,$values); + Link::notify_update('infolog',$info_id,$values, $change_type); // pre-cache the new values self::set_link_cache($values); diff --git a/infolog/inc/class.infolog_hooks.inc.php b/infolog/inc/class.infolog_hooks.inc.php index ad86507066..95256808ac 100644 --- a/infolog/inc/class.infolog_hooks.inc.php +++ b/infolog/inc/class.infolog_hooks.inc.php @@ -73,6 +73,7 @@ class infolog_hooks 'edit_id' => 'info_id', 'edit_popup' => '760x570', 'merge' => true, + 'push_data' => ['info_type', 'info_owner','info_responsible', 'info_modified'] ); } diff --git a/infolog/inc/class.infolog_ui.inc.php b/infolog/inc/class.infolog_ui.inc.php index 27f5b67402..d5209a08c9 100644 --- a/infolog/inc/class.infolog_ui.inc.php +++ b/infolog/inc/class.infolog_ui.inc.php @@ -1826,7 +1826,7 @@ class infolog_ui $GLOBALS['egw']->preferences->add('infolog','preferred_type',$content['info_type']); $GLOBALS['egw']->preferences->save_repository(false,'user',false); $content['msg'] = lang('InfoLog entry saved'); - Framework::refresh_opener($content['msg'],'infolog',$info_id,$operation); + Framework::message($content['msg']); } $content['tabs'] = $active_tab; @@ -1881,12 +1881,12 @@ class infolog_ui ); if (!($content['msg'] = $this->delete($info_id,$referer,'edit'))) return; // checks ACL first - Framework::refresh_opener($content['msg'],'infolog',$info_id,'delete'); + Framework::message($content['msg']); } // called again after delete confirmation dialog elseif ($button == 'deleted' && $content['msg']) { - Framework::refresh_opener($content['msg'],'infolog',$info_id,'delete'); + Framework::message($content['msg']); } if ($button == 'save' || $button == 'cancel' || $button == 'delete' || $button == 'deleted') { diff --git a/infolog/js/app.js b/infolog/js/app.js index caeb9ca549..1d009baad4 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -30,6 +30,9 @@ require("jqueryui"); require("../jsapi/egw_global"); require("../etemplate/et2_types"); var egw_app_1 = require("../../api/js/jsapi/egw_app"); +var et2_widget_dialog_1 = require("../../api/js/etemplate/et2_widget_dialog"); +var etemplate2_1 = require("../../api/js/etemplate/etemplate2"); +var et2_extension_nextmatch_1 = require("../../api/js/etemplate/et2_extension_nextmatch"); /** * UI for Infolog * @@ -44,7 +47,7 @@ var InfologApp = /** @class */ (function (_super) { */ function InfologApp() { // call parent - return _super.call(this, 'infolog') || this; + return _super.call(this, 'infolog', 'info_datemodified') || this; } /** * Destructor @@ -143,6 +146,79 @@ var InfologApp = /** @class */ (function (_super) { this.et2._inst.refresh(_msg, _app, _id, _type); } }; + /** + * Handle a push notification about entry changes from the websocket + * + * @param pushData + * @param {string} pushData.app application name + * @param {(string|number)} pushData.id id of entry to refresh or null + * @param {string} pushData.type either 'update', 'edit', 'delete', 'add' or null + * - update: request just modified data from given rows. Sorting is not considered, + * so if the sort field is changed, the row will not be moved. + * - edit: rows changed, but sorting may be affected. Requires full reload. + * - delete: just delete the given rows clientside (no server interaction neccessary) + * - add: ask server for data, add in intelligently + * @param {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary + * @param {number} pushData.account_id User that caused the notification + */ + InfologApp.prototype.push = function (pushData) { + if (pushData.app !== this.appname) + return; + // pushData does not contain everything, just the minimum. + var event = pushData.acl || {}; + if (pushData.type === 'delete') { + return _super.prototype.push.call(this, pushData); + } + // check visibility - grants is ID => permission of people we're allowed to see + if (typeof this._grants === 'undefined') { + this._grants = egw.grants(this.appname); + } + if (this._grants && typeof this._grants[pushData.acl.info_owner] == "undefined") { + // No ACL access + return; + } + // If we know about it & it's a update, just update. + if (pushData.type == "update" && this.egw.dataHasUID(pushData.id) || pushData.type == "edit") { + return etemplate2_1.etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type); + } + // Filter what's allowed down to those we care about + var filters = { + owner: { col: "info_owner", filter_values: [] }, + responsible: { col: "info_responsible", filter_values: [] } + }; + for (var _i = 0, _a = etemplate2_1.etemplate2.getByApplication(this.appname); _i < _a.length; _i++) { + var et = _a[_i]; + et.widgetContainer.iterateOver(function (nm) { + var value = nm.getValue(); + if (!value || !value.col_filter) + return; + for (var _i = 0, _a = Object.values(filters); _i < _a.length; _i++) { + var field_filter = _a[_i]; + if (value.col_filter[field_filter.col]) { + field_filter.filter_values.push(value.col_filter[field_filter.col]); + } + } + }, this, et2_extension_nextmatch_1.et2_nextmatch); + } + var _loop_1 = function (field_filter) { + if (field_filter.filter_values.length == 0) + return "continue"; + if (pushData.acl && typeof pushData.acl[field_filter.col] == "string" && + field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) <= 0) { + return { value: void 0 }; + } + if (field_filter.filter_values.filter(function (account) { return pushData.acl[field_filter.col].indexOf(account) >= 0; }).length == 0) + return { value: void 0 }; + }; + for (var _b = 0, _c = Object.values(filters); _b < _c.length; _b++) { + var field_filter = _c[_b]; + var state_1 = _loop_1(field_filter); + if (typeof state_1 === "object") + return state_1.value; + } + // Pass actual refresh on to etemplate to take care of + etemplate2_1.etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type); + }; /** * Retrieve the current state of the application for future restoration * @@ -156,7 +232,7 @@ var InfologApp = /** @class */ (function (_super) { var state = _super.prototype.getState.call(this); var nm = {}; // Get index etemplate - var et2 = etemplate2.getById('infolog-index'); + var et2 = etemplate2_1.etemplate2.getById('infolog-index'); if (et2) { var content = et2.widgetContainer.getArrayMgr('content'); nm = content && content.data && content.data.nm ? content.data.nm : {}; @@ -289,10 +365,10 @@ var InfologApp = /** @class */ (function (_super) { child_button.style.display = children ? 'block' : 'none'; } var callbackDeleteDialog = function (button_id) { - if (button_id == et2_dialog.YES_BUTTON) { + if (button_id == et2_widget_dialog_1.et2_dialog.YES_BUTTON) { } }; - et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"), this.egw.lang("Delete"), {}, et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE); + et2_widget_dialog_1.et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"), this.egw.lang("Delete"), {}, et2_widget_dialog_1.et2_dialog.BUTTONS_YES_NO_CANCEL, et2_widget_dialog_1.et2_dialog.WARNING_MESSAGE); }; /** * Confirm delete diff --git a/infolog/js/app.ts b/infolog/js/app.ts index 90fd89ca8f..c6e84cb80b 100644 --- a/infolog/js/app.ts +++ b/infolog/js/app.ts @@ -18,6 +18,9 @@ import '../jsapi/egw_global'; import '../etemplate/et2_types'; import {EgwApp} from '../../api/js/jsapi/egw_app'; +import {et2_dialog} from "../../api/js/etemplate/et2_widget_dialog"; +import {etemplate2} from "../../api/js/etemplate/etemplate2"; +import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch"; /** * UI for Infolog @@ -27,6 +30,9 @@ import {EgwApp} from '../../api/js/jsapi/egw_app'; class InfologApp extends EgwApp { + // Hold on to ACL grants + private _grants : any; + /** * Constructor * @@ -35,7 +41,7 @@ class InfologApp extends EgwApp constructor() { // call parent - super('infolog'); + super('infolog', 'info_datemodified'); } /** @@ -150,6 +156,84 @@ class InfologApp extends EgwApp this.et2._inst.refresh(_msg, _app, _id, _type); } } + /** + * Handle a push notification about entry changes from the websocket + * + * @param pushData + * @param {string} pushData.app application name + * @param {(string|number)} pushData.id id of entry to refresh or null + * @param {string} pushData.type either 'update', 'edit', 'delete', 'add' or null + * - update: request just modified data from given rows. Sorting is not considered, + * so if the sort field is changed, the row will not be moved. + * - edit: rows changed, but sorting may be affected. Requires full reload. + * - delete: just delete the given rows clientside (no server interaction neccessary) + * - add: ask server for data, add in intelligently + * @param {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary + * @param {number} pushData.account_id User that caused the notification + */ + push(pushData) + { + if(pushData.app !== this.appname) return; + + // pushData does not contain everything, just the minimum. + let event = pushData.acl || {}; + + if(pushData.type === 'delete') + { + return super.push(pushData); + } + + // check visibility - grants is ID => permission of people we're allowed to see + if(typeof this._grants === 'undefined') + { + this._grants = egw.grants(this.appname); + } + if(this._grants && typeof this._grants[pushData.acl.info_owner] == "undefined") + { + // No ACL access + return; + } + + // If we know about it & it's a update, just update. + if(pushData.type == "update" && this.egw.dataHasUID(pushData.id) || pushData.type == "edit") + { + return etemplate2.app_refresh("",pushData.app, pushData.id, pushData.type); + } + + // Filter what's allowed down to those we care about + let filters = { + owner: {col: "info_owner", filter_values: []}, + responsible: {col: "info_responsible", filter_values: []} + }; + for(let et of etemplate2.getByApplication(this.appname)) + { + et.widgetContainer.iterateOver( function(nm) { + let value = nm.getValue(); + if(!value || !value.col_filter) return; + + for(let field_filter of Object.values(filters)) + { + if(value.col_filter[field_filter.col]) + { + field_filter.filter_values.push(value.col_filter[field_filter.col]); + } + } + },this, et2_nextmatch); + } + for(let field_filter of Object.values(filters)) + { + if(field_filter.filter_values.length == 0) continue; + if(pushData.acl && typeof pushData.acl[field_filter.col] == "string" && + field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) <=0) + { + return; + } + if(field_filter.filter_values.filter(account => pushData.acl[field_filter.col].indexOf(account) >=0).length == 0) return; + } + + // Pass actual refresh on to etemplate to take care of + etemplate2.app_refresh("",pushData.app, pushData.id, pushData.type); + } /** * Retrieve the current state of the application for future restoration