Apply client-side push refactoring to calendar, infolog, timesheet

This commit is contained in:
nathangray 2021-03-04 11:27:35 -07:00
parent dd97c0f316
commit df54dcace4
8 changed files with 57 additions and 312 deletions

View File

@ -199,9 +199,12 @@ var EgwApp = /** @class */ (function () {
* *
* @param pushData * @param pushData
* @param grant_fields List of fields in pushData.acl with account IDs that might grant access eg: info_responsible * @param grant_fields List of fields in pushData.acl with account IDs that might grant access eg: info_responsible
* @param appname Optional, to check against the grants for a different application. Defaults to this.appname.
*
* @return boolean Entry has ACL access
*/ */
EgwApp.prototype._push_grant_check = function (pushData, grant_fields) { EgwApp.prototype._push_grant_check = function (pushData, grant_fields, appname) {
var grants = egw.grants(this.appname); var grants = egw.grants(appname || this.appname);
// No grants known // No grants known
if (!grants) if (!grants)
return true; return true;

View File

@ -324,10 +324,13 @@ export abstract class EgwApp
* *
* @param pushData * @param pushData
* @param grant_fields List of fields in pushData.acl with account IDs that might grant access eg: info_responsible * @param grant_fields List of fields in pushData.acl with account IDs that might grant access eg: info_responsible
* @param appname Optional, to check against the grants for a different application. Defaults to this.appname.
*
* @return boolean Entry has ACL access
*/ */
_push_grant_check(pushData : PushData, grant_fields : string[]) : boolean _push_grant_check(pushData : PushData, grant_fields : string[], appname? : string) : boolean
{ {
let grants = egw.grants(this.appname); let grants = egw.grants(appname || this.appname);
// No grants known // No grants known
if(!grants) return true; if(!grants) return true;

View File

@ -534,18 +534,22 @@ var CalendarApp = /** @class */ (function (_super) {
* @param {number} pushData.account_id User that caused the notification * @param {number} pushData.account_id User that caused the notification
*/ */
CalendarApp.prototype.push = function (pushData) { CalendarApp.prototype.push = function (pushData) {
// Calendar cares about calendar & infolog
if (pushData.app !== this.appname && pushData.app !== 'infolog')
return;
switch (pushData.app) { switch (pushData.app) {
case "calendar": case "calendar":
if (pushData.type === 'delete') { if (pushData.type === 'delete') {
return _super.prototype.push.call(this, pushData); return _super.prototype.push.call(this, pushData);
} }
return this.push_calendar(pushData); return this.push_calendar(pushData);
case "infolog": default:
if (jQuery.extend([], egw.preference("integration_toggle", "calendar")).indexOf(pushData.app) >= 0) {
if (pushData.app == "infolog") {
return this.push_infolog(pushData); return this.push_infolog(pushData);
} }
// Other integration here
// TODO
debugger;
}
}
}; };
/** /**
* Handle a push about infolog * Handle a push about infolog
@ -555,17 +559,19 @@ var CalendarApp = /** @class */ (function (_super) {
CalendarApp.prototype.push_infolog = function (pushData) { CalendarApp.prototype.push_infolog = function (pushData) {
var _this = this; var _this = this;
var _a; var _a;
// Check if we have access
if (!this._push_grant_check(pushData, ["info_owner", "info_responsible"], "infolog")) {
return;
}
// check visibility - grants is ID => permission of people we're allowed to see // check visibility - grants is ID => permission of people we're allowed to see
var owners = [];
var infolog_grants = egw.grants(pushData.app); var infolog_grants = egw.grants(pushData.app);
// Filter what's allowed down to those we care about // Filter what's allowed down to those we care about
var filtered = Object.keys(infolog_grants).filter(function (account) { return _this.state.owner.indexOf(account) >= 0; }); var filtered = Object.keys(infolog_grants).filter(function (account) { return _this.state.owner.indexOf(account) >= 0; });
// Check if we're interested in displaying by owner / responsible
var owner_check = filtered.filter(function (value) { var owner_check = filtered.filter(function (value) {
return pushData.acl.info_owner == value || pushData.acl.info_responsible.indexOf(value) >= 0; return pushData.acl.info_owner == value || pushData.acl.info_responsible.indexOf(value) >= 0;
}); });
if (!owner_check || owner_check.length == 0) { if (!owner_check || owner_check.length == 0) {
// The owner is not in the list of what we're allowed / care about // The owner is not in the list of what we care about
return; return;
} }
// Only need to update the list if we're on that view // Only need to update the list if we're on that view

View File

@ -465,9 +465,6 @@ class CalendarApp extends EgwApp
*/ */
push(pushData) push(pushData)
{ {
// Calendar cares about calendar & infolog
if(pushData.app !== this.appname && pushData.app !== 'infolog') return;
switch (pushData.app) switch (pushData.app)
{ {
case "calendar": case "calendar":
@ -476,9 +473,18 @@ class CalendarApp extends EgwApp
return super.push(pushData); return super.push(pushData);
} }
return this.push_calendar(pushData); return this.push_calendar(pushData);
case "infolog": default:
if(jQuery.extend([],egw.preference("integration_toggle","calendar")).indexOf(pushData.app) >= 0)
{
if(pushData.app == "infolog")
{
return this.push_infolog(pushData); return this.push_infolog(pushData);
} }
// Other integration here
// TODO
debugger;
}
}
} }
/** /**
@ -488,20 +494,24 @@ class CalendarApp extends EgwApp
*/ */
private push_infolog(pushData : PushData) private push_infolog(pushData : PushData)
{ {
// Check if we have access
if(!this._push_grant_check(pushData, ["info_owner","info_responsible"],"infolog"))
{
return;
}
// check visibility - grants is ID => permission of people we're allowed to see // check visibility - grants is ID => permission of people we're allowed to see
let owners = [];
let infolog_grants = egw.grants(pushData.app); let infolog_grants = egw.grants(pushData.app);
// Filter what's allowed down to those we care about // Filter what's allowed down to those we care about
let filtered = Object.keys(infolog_grants).filter(account => this.state.owner.indexOf(account) >= 0); let filtered = Object.keys(infolog_grants).filter(account => this.state.owner.indexOf(account) >= 0);
// Check if we're interested in displaying by owner / responsible
let owner_check = filtered.filter(function(value) { let owner_check = filtered.filter(function(value) {
return pushData.acl.info_owner == value || pushData.acl.info_responsible.indexOf(value) >= 0; return pushData.acl.info_owner == value || pushData.acl.info_responsible.indexOf(value) >= 0;
}) })
if(!owner_check || owner_check.length == 0) if(!owner_check || owner_check.length == 0)
{ {
// The owner is not in the list of what we're allowed / care about // The owner is not in the list of what we care about
return; return;
} }

View File

@ -31,7 +31,6 @@ require("../jsapi/egw_global");
require("../etemplate/et2_types"); require("../etemplate/et2_types");
var egw_app_1 = require("../../api/js/jsapi/egw_app"); var egw_app_1 = require("../../api/js/jsapi/egw_app");
var etemplate2_1 = require("../../api/js/etemplate/etemplate2"); var etemplate2_1 = require("../../api/js/etemplate/etemplate2");
var et2_extension_nextmatch_1 = require("../../api/js/etemplate/et2_extension_nextmatch");
var CRM_1 = require("../../addressbook/js/CRM"); var CRM_1 = require("../../addressbook/js/CRM");
/** /**
* UI for Infolog * UI for Infolog
@ -49,6 +48,9 @@ var InfologApp = /** @class */ (function (_super) {
var _this = var _this =
// call parent // call parent
_super.call(this, 'infolog') || this; _super.call(this, 'infolog') || this;
// These fields help with push filtering & access control to see if we care about a push message
_this.push_grant_fields = ["info_owner", "info_responsible"];
_this.push_filter_fields = ["info_owner", "info_responsible"];
_this._action_ids = []; _this._action_ids = [];
_this._action_all = false; _this._action_all = false;
return _this; return _this;
@ -154,96 +156,6 @@ var InfologApp = /** @class */ (function (_super) {
this.et2._inst.refresh(_msg, _app, _id, _type); 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) {
var _this = this;
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);
}
// If we know about it and it's an update, just update.
// This must be before all ACL checks, as responsible might have changed and entry need to be removed
// (server responds then with null / no entry causing the entry to disapear)
if (pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData))) {
return this.et2.getInstanceManager().refresh("", pushData.app, pushData.id, pushData.type);
}
// check visibility - grants is ID => permission of people we're allowed to see
if (typeof this._grants === 'undefined') {
this._grants = egw.grants(this.appname);
}
// check user has a grant from owner or a responsible
if (this._grants && typeof this._grants[pushData.acl.info_owner] === 'undefined' &&
// responsible gets implicit access, so we need to check them too
!pushData.acl.info_responsible.filter(function (res) { return typeof _this._grants[res] !== 'undefined'; }).length) {
// No ACL access
return;
}
// no responsible means, owner is responsible
if (!pushData.acl.info_responsible || !pushData.acl.info_responsible.length) {
pushData.acl.info_responsible = [pushData.acl.info_owner];
}
// Filter what's allowed down to those we care about
var filters = {
owner: { col: "info_owner", filter_values: [] },
responsible: { col: "info_responsible", filter_values: [] }
};
if (this.et2) {
this.et2.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) {
// no filter set
if (field_filter.filter_values.length == 0)
return "continue";
// acl value is a scalar (not array) --> check contained in filter
if (pushData.acl && typeof pushData.acl[field_filter.col] !== 'object') {
if (field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) < 0) {
return { value: void 0 };
}
return "continue";
}
// acl value is an array (eg. info_responsible) --> check intersection with filter
if (!field_filter.filter_values.filter(function (account) { return pushData.acl[field_filter.col].indexOf(account) >= 0; }).length) {
return { value: void 0 };
}
};
// check filters against ACL data
for (var _i = 0, _a = Object.values(filters); _i < _a.length; _i++) {
var field_filter = _a[_i];
var state_1 = _loop_1(field_filter);
if (typeof state_1 === "object")
return state_1.value;
}
// Pass actual refresh on to just nextmatch
var nm = this.et2.getDOMWidgetById('nm');
nm.refresh(pushData.id, pushData.type);
};
/** /**
* Retrieve the current state of the application for future restoration * Retrieve the current state of the application for future restoration
* *

View File

@ -31,8 +31,9 @@ import {CRMView} from "../../addressbook/js/CRM";
class InfologApp extends EgwApp class InfologApp extends EgwApp
{ {
// Hold on to ACL grants // These fields help with push filtering & access control to see if we care about a push message
private _grants : any; protected push_grant_fields = ["info_owner","info_responsible"];
protected push_filter_fields = ["info_owner","info_responsible"]
/** /**
* Constructor * Constructor
@ -163,108 +164,6 @@ class InfologApp extends EgwApp
this.et2._inst.refresh(_msg, _app, _id, _type); 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);
}
// If we know about it and it's an update, just update.
// This must be before all ACL checks, as responsible might have changed and entry need to be removed
// (server responds then with null / no entry causing the entry to disapear)
if (pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData)))
{
return this.et2.getInstanceManager().refresh("", pushData.app, pushData.id, pushData.type);
}
// check visibility - grants is ID => permission of people we're allowed to see
if (typeof this._grants === 'undefined')
{
this._grants = egw.grants(this.appname);
}
// check user has a grant from owner or a responsible
if (this._grants && typeof this._grants[pushData.acl.info_owner] === 'undefined' &&
// responsible gets implicit access, so we need to check them too
!pushData.acl.info_responsible.filter(res => typeof this._grants[res] !== 'undefined').length)
{
// No ACL access
return;
}
// no responsible means, owner is responsible
if (!pushData.acl.info_responsible || !pushData.acl.info_responsible.length)
{
pushData.acl.info_responsible = [pushData.acl.info_owner];
}
// Filter what's allowed down to those we care about
let filters = {
owner: {col: "info_owner", filter_values: []},
responsible: {col: "info_responsible", filter_values: []}
};
if(this.et2)
{
this.et2.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);
}
// check filters against ACL data
for(let field_filter of Object.values(filters))
{
// no filter set
if (field_filter.filter_values.length == 0) continue;
// acl value is a scalar (not array) --> check contained in filter
if (pushData.acl && typeof pushData.acl[field_filter.col] !== 'object')
{
if (field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) < 0)
{
return;
}
continue;
}
// acl value is an array (eg. info_responsible) --> check intersection with filter
if(!field_filter.filter_values.filter(account => pushData.acl[field_filter.col].indexOf(account) >= 0).length)
{
return;
}
}
// Pass actual refresh on to just nextmatch
let nm = <et2_nextmatch>this.et2.getDOMWidgetById('nm');
nm.refresh(pushData.id, pushData.type);
}
/** /**
* Retrieve the current state of the application for future restoration * Retrieve the current state of the application for future restoration

View File

@ -30,7 +30,6 @@ require("jqueryui");
require("../jsapi/egw_global"); require("../jsapi/egw_global");
require("../etemplate/et2_types"); require("../etemplate/et2_types");
var egw_app_1 = require("../../api/js/jsapi/egw_app"); var egw_app_1 = require("../../api/js/jsapi/egw_app");
var etemplate2_1 = require("../../api/js/etemplate/etemplate2");
/** /**
* UI for timesheet * UI for timesheet
* *
@ -39,7 +38,11 @@ var etemplate2_1 = require("../../api/js/etemplate/etemplate2");
var TimesheetApp = /** @class */ (function (_super) { var TimesheetApp = /** @class */ (function (_super) {
__extends(TimesheetApp, _super); __extends(TimesheetApp, _super);
function TimesheetApp() { function TimesheetApp() {
return _super.call(this, 'timesheet') || this; var _this = _super.call(this, 'timesheet') || this;
// These fields help with push filtering & access control to see if we care about a push message
_this.push_grant_fields = ["ts_owner"];
_this.push_filter_fields = ["ts_owner"];
return _this;
} }
/** /**
* This function is called when the etemplate2 object is loaded * This function is called when the etemplate2 object is loaded
@ -174,49 +177,6 @@ var TimesheetApp = /** @class */ (function (_super) {
if (widget) if (widget)
return widget.options.value; return widget.options.value;
}; };
/**
* 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: requires full reload for proper sorting
* @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
*/
TimesheetApp.prototype.push = function (pushData) {
var _a, _b, _c;
// timesheed does NOT care about other apps data
if (pushData.app !== this.appname)
return;
if (pushData.type === 'delete') {
return _super.prototype.push.call(this, pushData);
}
// This must be before all ACL checks, as owner might have changed and entry need to be removed
// (server responds then with null / no entry causing the entry to disapear)
if (pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData))) {
return etemplate2_1.etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type);
}
// all other cases (add, edit, update) are handled identical
// check visibility
if (typeof this._grants === 'undefined') {
this._grants = egw.grants(this.appname);
}
if (typeof this._grants[pushData.acl.ts_owner] === 'undefined')
return;
// check if we might not see it because of an owner filter
var nm = (_a = this.et2) === null || _a === void 0 ? void 0 : _a.getWidgetById('nm');
var nm_value = (_b = nm) === null || _b === void 0 ? void 0 : _b.getValue();
if (nm && nm_value && ((_c = nm_value.col_filter) === null || _c === void 0 ? void 0 : _c.ts_owner) && nm_value.col_filter.ts_owner != pushData.acl.ts_owner) {
return;
}
etemplate2_1.etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type);
};
/** /**
* Run action via ajax * Run action via ajax
* *

View File

@ -29,6 +29,10 @@ import {etemplate2} from "../../api/js/etemplate/etemplate2";
class TimesheetApp extends EgwApp class TimesheetApp extends EgwApp
{ {
// These fields help with push filtering & access control to see if we care about a push message
protected push_grant_fields = ["ts_owner"];
protected push_filter_fields = ["ts_owner"]
constructor() constructor()
{ {
super('timesheet'); super('timesheet');
@ -199,58 +203,6 @@ class TimesheetApp extends EgwApp
if(widget) return widget.options.value; if(widget) return widget.options.value;
} }
private _grants : any;
/**
* 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: requires full reload for proper sorting
* @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)
{
// timesheed does NOT care about other apps data
if (pushData.app !== this.appname) return;
if (pushData.type === 'delete')
{
return super.push(pushData);
}
// This must be before all ACL checks, as owner might have changed and entry need to be removed
// (server responds then with null / no entry causing the entry to disapear)
if (pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData)))
{
return etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type);
}
// all other cases (add, edit, update) are handled identical
// check visibility
if (typeof this._grants === 'undefined')
{
this._grants = egw.grants(this.appname);
}
if (typeof this._grants[pushData.acl.ts_owner] === 'undefined') return;
// check if we might not see it because of an owner filter
let nm = <et2_nextmatch>this.et2?.getWidgetById('nm');
let nm_value = nm?.getValue();
if (nm && nm_value && nm_value.col_filter?.ts_owner && nm_value.col_filter.ts_owner != pushData.acl.ts_owner)
{
return;
}
etemplate2.app_refresh("",pushData.app, pushData.id, pushData.type);
}
/** /**
* Run action via ajax * Run action via ajax
* *