diff --git a/.gitmodules b/.gitmodules index db2aae6..09e4ea4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "dashboard/dashboard_components"] path = dashboard/dashboard_components url = git@github.com:lanalabs/dashboard_components.git +[submodule "minimalistic_dashboard/dashboard_components"] + path = minimalistic_dashboard/dashboard_components + url = git@github.com:lanalabs/dashboard_components.git diff --git a/README.md b/README.md index 4aaa067..e774b48 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,113 @@ +develop.sh +emphaise what partial results and where can be accessed + # Python Dashboard for Lana PM -A documented example for python dashboards that can be used as a starting point for any new dashboards. The example shows a multipage app with a rather simple page 1 and a more complicated page 2. Page one can be seen as an example of how to create simple dashboard pages with elements being mostly independent of each other. Page 2 shows a complicated case where a table can be updated by the user, which then calculates the cost and updates a KPI below. Additionally a graph shows the occurences of the event selected in the table. For documentation of how to create plots, we refer to the documentation of Ploty and Dash. For an explanation of how to make api requests to the backend please see the Pylana Documentation and the api specifications of Lana Process Mining. -## Getting started -### Install requirements -Create a new conda environment using: -`conda create env --env name` +A documented example for python dashboards that can be used as a starting point +for any new dashboards. An example project _dashboards_ shows a multipage app +with a rather simple page-1 and a more complex page-2. + +* Page-1 can be seen as an example of how to create simple dashboard page with + elements being mostly independent of each other. + +* Page 2 shows a more complex case where a table can be updated by the user, + which then calculates the cost and updates a KPI below. + +Additionally a graph shows the occurences of the event selected in the table. +For documentation of how to create plots, we refer to the documentation of Plotly +and Dash. For an explanation of how to make api requests to the backend please +see the Pylana documentation and the api specifications of Lana Process Mining. + +Additionally, a rather minimalistic dashboard is also provided by the project +_minimalistic\_dashboard_, which is potentially the simplest example that +involves the smallest set of external dependencies. + +## Getting started for a local setup without Process Mining front-end + +There are two potential ways to choose from for developing any dashboard, in +order to run the desired code: + - Running the entire system via docker-compose on a local computer. This + involves to be able to run _Process Mining_ as a whole. + - Developing the dashboard code independently and uploading the code + incrementally over and over again until ready to some remotely available + setup. (not recommended) + +### Preparing a (your) local system + for using Python + +Make sure the submodules are pulled and up-to-date on the example project of +your interest, execute in the root of this repository: + +`git submodule update --init --recursive` + +Create a new conda environment using in one of the example project folder: + +`conda-shell` +`conda create -n ` + +where __ is the name of the conda environment to be used. + +Activate that environment using: -Activate that environment using: `conda activate ` -Install required packages: -`conda install requirements.txt` -`conda install lana_listener-0.0.1.tar.gz` +Install required packages: + +`conda install -u` + +`pip install pylana` +`pip install lana_listener-0.0.1.tar.gz` + +Where _pylana_ is available in public repository; +_lana-listener_ component shall be sourced as a ready made package from lanalabs/github. + +One may use a _requirements.txt_ to install generic dependencies into the +particular conda environment that is hosted natively on the local system in +order to develop the dashboard. Note that, these installed dependencies will not +be carried over onto the target system as installed ones! + +When the setup of the local system and the remote system diverges, e.g. +supported python versions, available dependencies, etc; that may result in +failure. + +A __requirements.txt_ file may be used, that is defined in the mining repository's +container setup that is actually used for production setup. ### Setting up configs and running the dashboard locally -It is possible to run the dashboard independent of LANA PM. -Simply fill in the API key and log_id into the `lana_listener` object and start `index.py`. - -### Uploading your first dashboard -Simply follow the steps in the jupyter-notebook to create a dashbaord and link it to the log. Once you have the ```dashboard_id``` and it is connected to the log, you will be able to upload source code either using the notebook, or using the ```upload.sh``` script. -For a detailed instruction of how to upload also other types of dashboards, as well as a Bash and Python guide to upload, please see ```UploadTutorial.pdf```. - -### Uploading (new) source-code -First stop tracking the ```upload.sh``` file so that you can change the file without pushing the changes to git by accident. -```git update-index --assume-unchanged upload.sh``` -After filling in the api_key, dashboard_id and url, open the terminal and run -```bash upload.sh``` + +It is possible to run the dashboard independent of LANA PM. +Simply fill in the API key and log_id into the `lana_listener` object and start +`index.py` in your own python (potentially _conda_) environment. + +### Deploying your dashboard + +Simply follow the steps in the jupyter-notebook to create a dashboard and link +it to the log. Once you have the `dashboard_id` and it is connected to the +log, you will be able to upload source code either using the notebook, or using +the `upload.sh` script. For a detailed instruction of how to upload also +other types of dashboards, as well as a Bash and Python guide to upload, please +see `UploadTutorial.pdf`. + +### Uploading/replacing source-code of an existing Advanced dashboard + +First, stop tracking the `upload.sh` file by git on your local machine, so that +you can modify its content without making actual changes to the original version +in the repository, by accident. + +`git update-index --assume-unchanged upload.sh` + +After assigning value to the `API_KEY`, `DASHBOARD_ID` and `URL` variables +respectively in the `upload.sh` scipt, open a terminal and execute it: + +`bash upload.sh` + Refresh the Advanced Dashboard page in LANA PM and the dashboard should appear. ## Add Graphs, Interactions and Pages -This repository follows a basic structure to allow for multiple pages. Each page has its own URL with navigation enabled by the navbar at the top. The folder structure is as follows: + +The provided _dashboards_ project example follows a basic structure to allow for multiple pages. Each page +has its own URL with navigation enabled by the navigation-bar at the top. The folder +structure is as follows: ``` - app.py @@ -42,10 +120,14 @@ This repository follows a basic structure to allow for multiple pages. Each page |-- indicator_objects.py |-- api_requests.py ``` -Common functionality should be shared across pages using the `dashboard-components` submodule. Elements that have the same formatting should be defined as functions and not as copy pasted code. If API calls or data can be used by other functions, they should also be implemented in an accessible way. The more functions elements and functions are implemented, the fewer time it will take to build new dashboards. -On how to use submodules please see the Wiki entry here: -https://github.com/lanalabs/python-dashboard/wiki/Working-with-submodules-in-VS-Code - - +Common functionalities should be shared across pages using the +`dashboard-components` git-submodule. Elements that have the same formatting +should be defined as functions and not as copy pasted code. In case API calls or +data needs to be used by any other function, those should also be implemented in +an accessible way. The more function elements and functions are implemented, +the fewer time it will take to build new dashboards. On how to use submodules +please see the Wiki entry here: +https://github.com/lanalabs/python-dashboard/wiki/Working-with-submodules-in-VS-Code +. diff --git a/develop.sh b/develop.sh new file mode 100755 index 0000000..b630499 --- /dev/null +++ b/develop.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +#set -e + +LOG_ID="" +API_KEY="" +DASHBOARD_NAME="csimple" +ASSETSRC="minimalistic_dashboard" +URL="http://localhost:4000" +ASSET="../upload.zip" + +while [ 1 ]; do + rm -f $ASSET + pushd $ASSETSRC + zip -r $ASSET * + popd + + DASHBOARD_ID=$(curl -X POST ${URL}/api/v2/custom-dashboards \ + -H "Authorization: API-Key ${API_KEY}" \ + -H "Content-Type: application/json" \ + --data-raw '{ + "name": "csimple", + "type": "python_dashboard" + }' | jq -r .id) + + echo "Advanced dashboard id: ${DASHBOARD_ID}" + + echo "Uploading ..." + + curl -X POST ${URL}/api/v2/custom-dashboards/${DASHBOARD_ID}/source \ + -H "Authorization: API-Key ${API_KEY}" \ + -F file=@"./upload.zip" + + echo "Connecting ..." + + curl -X POST ${URL}/api/v2/resource-connections \ + -H "Authorization: API-Key ${API_KEY}" \ + -H "Content-Type: application/json" \ + --data-raw '{ + "log_id":"'"${LOG_ID}"'", + "custom_dashboard_id":"'"${DASHBOARD_ID}"'" + }' 1>/dev/null + + echo + echo "Either press Ctrl-C to interrupt _or_ ENTER to clean up and move on!" + + read + + echo "DELETING dashboard ..." + + curl -X DELETE ${URL}/api/v2/custom-dashboards/${DASHBOARD_ID} \ + -H "Authorization: API-Key ${API_KEY}" \ + -H "Content-Type: application/json" + + echo "Press Enter to move on!" + read +done + +exit 0 diff --git a/minimalistic_dashboard/api_calls.py b/minimalistic_dashboard/api_calls.py new file mode 100644 index 0000000..09e6c75 --- /dev/null +++ b/minimalistic_dashboard/api_calls.py @@ -0,0 +1,18 @@ +import requests +import argparse +import ast + +from dashboard_components.api_abstraction import aggregate + +def number_of_cases(log_id, auth_token, tfs=[]) -> int: + """Returns the number of cases.""" + + df = aggregate(auth_token, + trace_filter_sequence=tfs, + log_id=log_id, + metric="frequency", + aggregation_function="sum" + ) + return int(df["frequency"].iloc[0]) + + diff --git a/minimalistic_dashboard/app.py b/minimalistic_dashboard/app.py new file mode 100644 index 0000000..4d9d070 --- /dev/null +++ b/minimalistic_dashboard/app.py @@ -0,0 +1,30 @@ +import dash +import os +import dash_bootstrap_components as dbc + +from flask import Flask +from urllib.parse import urlparse + +try: + fooConfig = urlparse(os.environ['JANUS_URL']) + config = {"scheme": fooConfig.scheme, + "host": fooConfig.hostname, + "port": fooConfig.port, + "url": fooConfig.url, + "dashboard_id": os.path.basename(os.getcwd())} +except Exception: + config = {"scheme": "http", + "host": "janus", + "port": 4000, + "url": "http://localhost", + "dashboard_id": os.path.basename(os.getcwd())} + +application = Flask("example_dashboard") + +app = dash.Dash(name=__name__, + server=application, + suppress_callback_exceptions=True, + external_stylesheets=[dbc.themes.FLATLY], + url_base_pathname=f'/{config["dashboard_id"]}/') + +server = app.server diff --git a/minimalistic_dashboard/apps/page_1.py b/minimalistic_dashboard/apps/page_1.py new file mode 100644 index 0000000..9204c62 --- /dev/null +++ b/minimalistic_dashboard/apps/page_1.py @@ -0,0 +1,42 @@ +import lana_listener +import dash_html_components as html +import dash_bootstrap_components as dbc + +from app import app +from api_calls import number_of_cases +from dash.dependencies import Input, Output +from dashboard_components.indicator_objects import indicator_div + +llistener = lana_listener.LanaListener( + id='LanaListener', + lana_api_key='', + lana_log_id='', + lana_trace_filter_sequence='[]' +) + +layout = html.Div(children=[llistener, + html.Br(), + dbc.Row([dbc.Col(id="number_cases", width={"size": 3}), + dbc.Col(id="number_cases_tfs", width={"size": 3}) + ])]) + +@app.callback( + Output(component_id='number_cases', component_property='children'), + [Input('LanaListener', 'lana_api_key'), + Input('LanaListener', 'lana_log_id')] +) +def number_cases(api_key, log_id): + num_cases = number_of_cases(log_id, api_key) + ind = indicator_div(num_cases, title="Anzahl Cases (ohne TFS)") + return ind + +@app.callback( + Output(component_id='number_cases_tfs', component_property='children'), + [Input('LanaListener', 'lana_api_key'), + Input('LanaListener', 'lana_log_id'), + Input('LanaListener', 'lana_trace_filter_sequence')] +) +def number_case_tfs(api_key, log_id, tfs): + num_cases = number_of_cases(log_id, api_key, tfs) + ind = indicator_div(num_cases, title="Anzahl Cases (mit TFS)") + return ind diff --git a/minimalistic_dashboard/dashboard_components b/minimalistic_dashboard/dashboard_components new file mode 160000 index 0000000..9ca27b4 --- /dev/null +++ b/minimalistic_dashboard/dashboard_components @@ -0,0 +1 @@ +Subproject commit 9ca27b4063c20bb68c2524f9d518e63128d4920b diff --git a/minimalistic_dashboard/index.py b/minimalistic_dashboard/index.py new file mode 100644 index 0000000..0c25b4a --- /dev/null +++ b/minimalistic_dashboard/index.py @@ -0,0 +1,26 @@ +import dash_core_components as dcc +import dash_html_components as html + +from app import app, application, config +from apps import page_1 +from dash.dependencies import Input, Output + +print(f"Server {str(application)} running.") + +app.layout = html.Div([ + dcc.Location(id='url', refresh=True), + html.Div(id='page-content') +]) + +@app.callback(Output('page-content', 'children'), + [Input('url', 'pathname')]) +def display_page(pathname): + dashboard_id = config["dashboard_id"] + if pathname == f'/{dashboard_id}/apps/app1': + return page_1.layout + else: + return page_1.layout + + +if __name__ == '__main__': + app.run_server(debug=False) diff --git a/upload.sh b/upload.sh index d6665f9..efbfc38 100644 --- a/upload.sh +++ b/upload.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + API_KEY="" DASHBOARD_ID="" URL="" @@ -8,4 +9,4 @@ cd dashboard; zip -r ../upload.zip *; cd .. curl -X POST "${URL}/api/v2/custom-dashboards/${DASHBOARD_ID}/source" \ -H "Authorization: API-Key ${API_KEY}" \ - -F file=@"upload.zip" \ No newline at end of file + -F file=@"upload.zip"