Skip to content

Commit

Permalink
Merge pull request #30 from KnowledgeSeed/navigation
Browse files Browse the repository at this point in the history
support navigation into app,dinamic email sender, basic request logger
  • Loading branch information
ote82 authored Nov 29, 2023
2 parents a6272bf + 5500c6c commit 1d94707
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 10 deletions.
1 change: 1 addition & 0 deletions analogic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@
from .signal_receiver import SignalReceiver
from .default_signal_receiver import DefaultSignalReceiver
from .long_running_task_executor import LongRunningTaskExecutor
from .request_logger import RequestLogger
8 changes: 4 additions & 4 deletions analogic/analogic.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ def _load_logging(app):
log_config = json.load(file)
for h in log_config['handlers']:
if 'filename' in log_config['handlers'][h]:
if h == 'scheduler_file_handler':
file_name = f"logs/scheduler_{os.getpid()}.log"
else:
file_name = log_config['handlers'][h]['filename']
# if h == 'scheduler_file_handler':
# file_name = f"logs/scheduler_{os.getpid()}.log"
# else:
file_name = log_config['handlers'][h]['filename']

log_config['handlers'][h]['filename'] = os.path.join(app.instance_path, file_name)

Expand Down
64 changes: 64 additions & 0 deletions analogic/authentication_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from analogic.exceptions import AnalogicProxyException, AnalogicAccessDeniedException, AnalogicException, \
AnalogicAcceptedException
from analogic.session_handler import SessionHandler
from analogic.request_logger import RequestLogger
from analogic.setting import SettingManager
import logging
import pandas as pd
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -67,6 +69,7 @@ def __init__(self, setting):
self.logged_in_user_session_name = '_logged_in_user_name'
self._logger = logging.getLogger(self.setting.get_instance())
self.session_handler = SessionHandler(self.setting.get_instance_and_name())
self.request_logger = RequestLogger()
self.user_subscriptions = {}

def initialize(self):
Expand All @@ -78,6 +81,15 @@ def get_setting(self):
def get_logged_in_user_name(self):
return self.session_handler.get(self.logged_in_user_session_name)

def set_navigation_parameters(self, value):
self.session_handler.set('navigation_parameters', value)

def get_navigation_parameters(self):
return self.session_handler.get('navigation_parameters')

def clear_navigation_parameters(self):
self.session_handler.delete('navigation_parameters')

@login_required
def pivot(self):
username = self.get_logged_in_user_name()
Expand Down Expand Up @@ -141,16 +153,21 @@ def middleware(self):

def _get_check_access_mdx(self, force_server_side_query=False):
if request.args.get('server') is not None or force_server_side_query is True:

if request.method == 'GET':
body = request.args
elif len(request.form) > 0:
body = request.form.to_dict()
else:
body = orjson.loads(request.data)

key = body.get('key')

if body.get('key_suffix') is not None:
key = key + '_' + body['key_suffix']

key = key + self.get_setting().get_check_access_repository_yml_suffix()

mdx = self.setting.get_mdx(key)

if mdx is None:
Expand All @@ -162,34 +179,73 @@ def _get_check_access_mdx(self, force_server_side_query=False):
if type(body[k]) is not dict:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))

self.log_request(**body)

return mdx.encode('utf-8')

return None

def log_request(self, **kwargs):
if self.setting.is_request_logger_enabled() is True:
params = kwargs.copy()

params['logged_in_user'] = self.get_logged_in_user_name()

params.update(self.get_additional_log_request_parameters())

self.request_logger.log_request(**params)

def log_write_request(self, **kwargs):
if self.setting.is_write_request_logger_enabled() is True and self.is_write_request():
params = kwargs.copy()

params['logged_in_user'] = self.get_logged_in_user_name()

params.update(self.get_additional_log_request_parameters())

self.request_logger.log_write_request(**params)

def get_additional_log_request_parameters(self):
return {}

def is_write_request(self):
return 'tm1.ExecuteWithReturn' in request.full_path

def _get_server_side_mdx(self, force_server_side_query=False):

if request.args.get('server') is not None or force_server_side_query is True:

if request.method == 'GET':
body = request.args
elif len(request.form) > 0:
body = request.form.to_dict()
else:
body = orjson.loads(request.data)

key = body['key']

if body.get('key_suffix') is not None:
key = key + '_' + body['key_suffix']

mdx = self.setting.get_mdx(key)

if isinstance(mdx, dict):

if 'required_permissions' in mdx and self.check_permission(mdx.get('required_permissions')) is False:
raise AnalogicAccessDeniedException('You do not have access to run the query {}'.format(key))

mdx = mdx['query']

mdx = self._set_custom_mdx_data(mdx)

for k in body:

if type(body[k]) is not dict:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))

self.log_request(**body)
self.log_write_request(**body)

return mdx.encode('utf-8')
else:
return ''.encode('utf-8')
Expand Down Expand Up @@ -442,6 +498,14 @@ def get_setting_parameter_descriptions():
'sessionExpiresInMinutes': {
'required': False,
'description': 'Numeric value. Session will expire after the added value. Default 20'
},
SettingManager.ENABLE_REQUEST_LOGGER_PARAMETER_NAME: {
'required': False,
'description': 'Boolean value, default false. All of the request parameter will be added to the log'
},
SettingManager.ENABLE_WRITE_REQUEST_LOGGER_PARAMETER_NAME: {
'required': False,
'description': 'Boolean value, default false. All of the write request parameter will be added to the log'
}
}

Expand Down
5 changes: 5 additions & 0 deletions analogic/cam.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ def get_tm1_service(self):
def _extend_login_session(self):
session.modified = True

def _set_custom_mdx_data(self, mdx):
if mdx is not None and len(mdx) > 0:
return mdx.replace('$activeUser', self.get_logged_in_user_name())
return mdx

@staticmethod
def get_setting_parameter_descriptions():
result = super(Cam, Cam).get_setting_parameter_descriptions()
Expand Down
31 changes: 28 additions & 3 deletions analogic/core_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
from analogic.endpoint import AnalogicEndpoint
from analogic.authentication_provider import get_authentication_provider

from flask import request, redirect
import base64

core_endpoints = AnalogicEndpoint('core_endpoints', __name__)


@core_endpoints.analogic_endpoint_route('/', methods=['GET', 'POST'])
def index():
return get_authentication_provider().index()
authentication_provider = get_authentication_provider()

navigation_parameters = request.args.get('p')

if navigation_parameters is not None:
authentication_provider.set_navigation_parameters(navigation_parameters)
return redirect(authentication_provider.setting.get_base_url())

response = authentication_provider.index()

return response


@core_endpoints.analogic_endpoint_route('/navigation_parameters', methods=['GET'])
def navigation_parameters():
authentication_provider = get_authentication_provider()

navigation_parameters = authentication_provider.get_navigation_parameters()

if navigation_parameters is not None:

authentication_provider.clear_navigation_parameters()

return {'navigation_parameters': navigation_parameters}, 200, {'Content-type': 'application/json'}



@core_endpoints.analogic_endpoint_route('/login', methods=['GET', 'POST'])
Expand Down Expand Up @@ -44,6 +69,7 @@ def export():
def clear_cache():
return get_authentication_provider().clear_cache()


@core_endpoints.analogic_endpoint_route('/pivot', methods=['GET', 'POST'])
def pivot():
return get_authentication_provider().pivot()
Expand All @@ -52,4 +78,3 @@ def pivot():
@core_endpoints.analogic_endpoint_route('/middleware', methods=['GET', 'POST'])
def middleware():
return get_authentication_provider().middleware()

5 changes: 4 additions & 1 deletion analogic/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ def send_email_by_params(setting, post_data):

smtp_config = cnf['smtp']

sender_email = EmailManager._get_required_config_value(smtp_config, 'sender_email')
if post_data.get('sender_email') is None:
sender_email = EmailManager._get_required_config_value(smtp_config, 'sender_email')
else:
sender_email = post_data.get('sender_email')

receiver_email = post_data.get('receiver_email')

Expand Down
50 changes: 50 additions & 0 deletions analogic/logging.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,46 @@
"encoding": "utf8"
},

"request_logger_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "logs/request.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
},

"request_logger_json_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "json",
"filename": "logs/request_json.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
},

"write_request_logger_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "logs/write_request.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
},

"write_request_logger_json_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "json",
"filename": "logs/write_request_json.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
},

"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
Expand All @@ -114,6 +154,16 @@
"level": "INFO",
"handlers": ["login_file_handler", "login_json_file_handler"],
"propagate": false
},
"request_logger": {
"level": "INFO",
"handlers": ["request_logger_file_handler", "request_logger_json_file_handler"],
"propagate": false
},
"write_request_logger": {
"level": "INFO",
"handlers": ["write_request_logger_file_handler", "write_request_logger_json_file_handler"],
"propagate": false
},
"werkzeug": {
"level": "INFO",
Expand Down
2 changes: 1 addition & 1 deletion analogic/pivot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from TM1py.Objects import Subset
from TM1py.Services import TM1Service
from TM1py.Utils import format_url
from flask send_file
from flask import send_file


def call(tm1: TM1Service, username, cube_name=None, dimension_name=None, hierarchy_name=None, subset_name=None, element_names=None, subset_name_to_remove=None, selected_cards=None, options=None, export_data=None):
Expand Down
13 changes: 13 additions & 0 deletions analogic/request_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import logging

class RequestLogger:

def __init__(self):
self.request_logger = logging.getLogger('request_logger')
self.write_request_logger = logging.getLogger('write_request_logger')

def log_request(self, **kwargs):
self.request_logger.info(kwargs)

def log_write_request(self, **kwargs):
self.write_request_logger.info(kwargs)
10 changes: 10 additions & 0 deletions analogic/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

class SettingManager:

ENABLE_REQUEST_LOGGER_PARAMETER_NAME = '_enable_request_logger'
ENABLE_WRITE_REQUEST_LOGGER_PARAMETER_NAME = '_enable_write_request_logger'


def __init__(self, analogic_application_path, instance='default'):
self.site_root = analogic_application_path
self.instance = instance
Expand Down Expand Up @@ -173,5 +177,11 @@ def get_permission_query_repository_yml_key(self):
def get_permission_session_name(self):
return self.get_instance() + '_analogic_permissions'

def is_request_logger_enabled(self):
return self.get_config().get(self.ENABLE_REQUEST_LOGGER_PARAMETER_NAME, False)

def is_write_request_logger_enabled(self):
return self.get_config().get(self.ENABLE_WRITE_REQUEST_LOGGER_PARAMETER_NAME, False)

def getLogger(self):
return self._logger
17 changes: 16 additions & 1 deletion analogic/static/assets/js/framework/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,22 @@ window.onerror = (msg, url, lineNum, colNum, error) => {

Widgets.systemValueGlobalCompanyProductPlanVersion = 'Budget';

Widgets[app.mainPage].renderWidget().then(() => Utils.checkScreenResolution());
Auth.getAjaxRequest('navigation_parameters', {}, 'GET').then((data) => {
let page = app.mainPage;
if (data.navigation_parameters) {
let navigation_parameters = JSON.parse(atob(data.navigation_parameters));

if (navigation_parameters.page) {
page = navigation_parameters.page;
}
for(let key in navigation_parameters) {
if (key !== 'page') {
Widgets[key] = navigation_parameters[key];
}
}
}
Widgets[page].renderWidget().then(() => Utils.checkScreenResolution());
});
}


Expand Down
7 changes: 7 additions & 0 deletions analogic/static/assets/js/framework/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,13 @@ const Utils = {
},
undefinedOrFalse(val) {
return typeof val === 'undefined' || val === false;
},
getNavigationUrl(params) {
let url = window.location.href.split('?')[0];
if (params) {
url += '?p=' + btoa(JSON.stringify(params));
}
return url;
}
};

Expand Down

0 comments on commit 1d94707

Please sign in to comment.