From c0fc15bb7e72bf5fa66e8a7bc1ad1c80212bfd5d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 15 Jun 2021 14:19:44 +0530 Subject: [PATCH 01/24] fix-35 - check for the value (#39) --- .../Dashboard/Widgets/MissingResources/ResourceInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js b/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js index 004612705..d4b1ed9b8 100644 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js +++ b/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js @@ -10,7 +10,7 @@ export default class ResourceInfo extends React.PureComponent {
- { `${ diffFromNowString(data.endedAt) } ago - ${ diffFromNowString(data.startedAt) } old` } + { data.endedAt && data.startedAt && `${ diffFromNowString(data.endedAt) } ago - ${ diffFromNowString(data.startedAt) } old` }
); From 9c3e9125519d4d1273e84ef65c6d826f0e1d499d Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Thu, 17 Jun 2021 14:07:19 +0200 Subject: [PATCH 02/24] fix(frontend-dev): webpack config for storybook launch --- frontend/.storybook/webpack.config.js | 3 ++- frontend/webpack.config.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/.storybook/webpack.config.js b/frontend/.storybook/webpack.config.js index afd395809..1a123fa52 100644 --- a/frontend/.storybook/webpack.config.js +++ b/frontend/.storybook/webpack.config.js @@ -3,7 +3,8 @@ const mainConfig = require('../webpack.config.js'); module.exports = async ({ config }) => { var conf = mainConfig(); - config.resolve.alias = Object.assign(pathAlias, config.resolve.alias); // Path Alias + config.resolve.alias = Object.assign(conf.resolve.alias, config.resolve.alias); // Path Alias + config.resolve.extensions = conf.resolve.extensions config.module.rules = conf.module.rules; config.module.rules[0].use[0] = 'style-loader'; // instead of separated css config.module.rules[1].use[0] = 'style-loader'; diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index d0e5b6ca6..70958ce58 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -129,7 +129,7 @@ module.exports = (envName = 'local') => { }, { test: /\.css$/, - include: [ /node_modules/, /app\/styles/ ], + include: [ path.join(__dirname, "node_modules"), path.join(__dirname, "app/styles") ], use: [ cssFileLoader, { @@ -148,7 +148,7 @@ module.exports = (envName = 'local') => { }, { test: /\.js$/, - include: path.join(__dirname, "app"), + include: [ path.join(__dirname, "app"), path.join(__dirname, ".storybook") ], use: babelLoader, }, { From 4e7eb65fd857fcb535ecabbd67634dd9a33d1be0 Mon Sep 17 00:00:00 2001 From: ourvakan Date: Thu, 17 Jun 2021 18:01:02 +0300 Subject: [PATCH 03/24] update versions with vulnerabilities --- ee/connectors/deploy/requirements_clickhouse.txt | 2 +- ee/connectors/deploy/requirements_pg.txt | 2 +- ee/connectors/deploy/requirements_redshift.txt | 2 +- ee/connectors/deploy/requirements_snowflake.txt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ee/connectors/deploy/requirements_clickhouse.txt b/ee/connectors/deploy/requirements_clickhouse.txt index 1002963e3..15381bdad 100644 --- a/ee/connectors/deploy/requirements_clickhouse.txt +++ b/ee/connectors/deploy/requirements_clickhouse.txt @@ -9,6 +9,6 @@ pytz==2021.1 requests==2.25.1 SQLAlchemy==1.3.23 tzlocal==2.1 -urllib3==1.26.3 +urllib3==1.26.5 PyYAML==5.4.1 diff --git a/ee/connectors/deploy/requirements_pg.txt b/ee/connectors/deploy/requirements_pg.txt index 2f11bc087..12f72ec7d 100644 --- a/ee/connectors/deploy/requirements_pg.txt +++ b/ee/connectors/deploy/requirements_pg.txt @@ -8,5 +8,5 @@ pytz==2021.1 requests==2.25.1 SQLAlchemy==1.3.23 tzlocal==2.1 -urllib3==1.26.3 +urllib3==1.26.5 PyYAML==5.4.1 diff --git a/ee/connectors/deploy/requirements_redshift.txt b/ee/connectors/deploy/requirements_redshift.txt index 3f6a75d42..42080cd4a 100644 --- a/ee/connectors/deploy/requirements_redshift.txt +++ b/ee/connectors/deploy/requirements_redshift.txt @@ -9,7 +9,7 @@ pytz==2021.1 requests==2.25.1 SQLAlchemy==1.3.23 tzlocal==2.1 -urllib3==1.26.3 +urllib3==1.26.5 pandas-redshift PyYAML awswrangler diff --git a/ee/connectors/deploy/requirements_snowflake.txt b/ee/connectors/deploy/requirements_snowflake.txt index 816ba6cfa..cc81ec16c 100644 --- a/ee/connectors/deploy/requirements_snowflake.txt +++ b/ee/connectors/deploy/requirements_snowflake.txt @@ -13,7 +13,7 @@ botocore==1.18.18 certifi==2020.6.20 cffi==1.14.3 chardet==3.0.4 -cryptography==2.9.2 +cryptography==3.4.7 idna==2.10 isodate==0.6.0 jmespath==0.10.0 @@ -30,5 +30,5 @@ requests==2.23.0 requests-oauthlib==1.3.0 s3transfer==0.3.3 six==1.15.0 -urllib3==1.25.11 +urllib3==1.26.5 From f12562518fb83ce4b93e6b5ee2d6073f09b8cbfd Mon Sep 17 00:00:00 2001 From: ourvakan Date: Thu, 17 Jun 2021 18:08:44 +0300 Subject: [PATCH 04/24] boilerplate --- .../buying_clients.ipynb | 1109 +++++++++++++++++ .../credentials.yml.example | 6 + .../explorational_analysis.ipynb | 560 +++++++++ 3 files changed, 1675 insertions(+) create mode 100644 ee/connectors/data_analysis_cookbook/buying_clients.ipynb create mode 100644 ee/connectors/data_analysis_cookbook/credentials.yml.example create mode 100644 ee/connectors/data_analysis_cookbook/explorational_analysis.ipynb diff --git a/ee/connectors/data_analysis_cookbook/buying_clients.ipynb b/ee/connectors/data_analysis_cookbook/buying_clients.ipynb new file mode 100644 index 000000000..350d3ea97 --- /dev/null +++ b/ee/connectors/data_analysis_cookbook/buying_clients.ipynb @@ -0,0 +1,1109 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Identifying bying clients\n", + "\n", + "In this notebook we will create a decision tree based model to identify clients who pay (buyers) and understand what makes a user a client (most relevant features).\n", + "\n", + "We divide our notebook into four stages: data preparation, feature engineering, model building, feature importance analysis" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import psycopg2\n", + "from IPython.display import display\n", + "import yaml\n" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## 1. Data preparation\n", + "\n", + "In this step we load from database (PostgreSQL in this example) data and keep it locally as a CSV file. '\n", + "The main reason for that is to be able to reproduce results quickly." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [], + "source": [ + "# Create a connection to the database\n", + "\n", + "# Load a config file with credentials\n", + "conf = yaml.load(\n", + " open(\"credentials.yml\"), Loader=yaml.FullLoader)['pg']\n", + "# Create a connection\n", + "conn = psycopg2.connect(\n", + " host=conf['host'],\n", + " port=conf['port'],\n", + " database=conf['database'],\n", + " user=conf['user'],\n", + " password=conf['password']\n", + ")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Here we prepare two functions to obtain data from the databases (or .csv files if they were pre-downloaded)" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [], + "source": [ + "def load_events(source='csv') -> pd.DataFrame:\n", + " \"\"\" Obtains session events from database or csv file\"\"\"\n", + " if source == 'db':\n", + " q = f'select * from connector_events where sessionid IN {sessions}'\n", + " all_events = pd.read_sql(q, conn)\n", + " all_events.to_csv('all_events_1454.csv', index=False)\n", + " elif source == 'csv':\n", + " all_events = pd.read_csv('all_events_1454_sep.csv', sep='|')\n", + " else:\n", + " raise ValueError(\"source parameter should be either 'csv' or 'db'\")\n", + " return all_events\n", + "\n", + "def load_sessions(source='csv') -> pd.DataFrame:\n", + " \"\"\" Obtains sessions information from database or csv file\"\"\"\n", + " if source == 'db':\n", + " q = f\"select * from connector_user_sessions where sessionid in {sessions}\"\n", + " all_sessions = pd.read_sql(q, conn)\n", + " # Saving as a CSV file is optional\n", + " all_sessions.to_csv(\"all_sessions.csv\", sep='|', index=False)\n", + " elif source == 'csv':\n", + " all_sessions = pd.read_csv(\"all_sessions.csv\", sep='|')\n", + " else:\n", + " raise ValueError(\"source parameter should be either 'csv' or 'db'\")\n", + " return all_sessions\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "In the connector's events table we found a client who had a button with a label \"PAY\" by performing a simple query\n", + "\n", + "`q = \"select session_id from connector_events where mouseclick_label = 'PAY\";`\n", + "\n", + "We went on to find all session ids of this client from our internal tables and saved it in `all_sessions_1454.csv`.\n", + "This step is unnecessary for clients and only explained for general clarity.\n", + "**The most important takeaway here, is that we have prepared a list of sessions for which we know\n", + "whether a click on \"PAY\" button has been made or not.**" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [], + "source": [ + "sessions_info = pd.read_csv(\"all_sessions_1454.csv\")\n", + "sessions = tuple(sessions_info['session_id'])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "First off, let's see that the dataset is highly imbalanced, because the number of buyers is much less than the number of ordinary visitors.\n", + "In fact, the percentage of buying clients (0.04%) is so small, it's not event seen on the pie chart.\n", + "Hence we're going to use special techniques for imbalanced datasets." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "buyers_count = sessions_info[sessions_info.paid == 1].shape[0]\n", + "regular_count = sessions_info.shape[0] - buyers_count\n", + "ax = plt.subplot(111)\n", + "wedges, texts, _ = ax.pie(x=(buyers_count, regular_count),\n", + " shadow=False,\n", + " labels=['Paying visitors', 'Regular visitors'],\n", + " autopct='%1.2f%%',\n", + " explode=(0, 0.8))\n", + "\n", + "for w in wedges:\n", + " w.set_linewidth(1)\n", + " w.set_edgecolor('white')\n", + "\n", + "plt.show()\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\david\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3361: DtypeWarning: Columns (9,11,12,13,26) have mixed types.Specify dtype option on import or set low_memory=False.\n", + " if (await self.run_code(code, result, async_=asy)):\n" + ] + } + ], + "source": [ + "all_events = load_events()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Let's take a look at the events dataset by printing one session" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [ + { + "data": { + "text/plain": " sessionid connectioninformation_downlink \\\n0 4207534060820504 NaN \n2 4207534060820504 NaN \n3 4207534060820504 NaN \n4 4207534060820504 NaN \n5 4207534060820504 NaN \n6 4207534060820504 NaN \n7 4207534060820504 NaN \n\n connectioninformation_type consolelog_level consolelog_value \\\n0 NaN NaN NaN \n2 NaN NaN NaN \n3 NaN NaN NaN \n4 NaN NaN NaN \n5 NaN NaN NaN \n6 NaN NaN NaN \n7 NaN NaN NaN \n\n customevent_messageid customevent_name customevent_payload \\\n0 NaN NaN NaN \n2 NaN NaN NaN \n3 NaN NaN NaN \n4 NaN NaN NaN \n5 NaN NaN NaN \n6 NaN NaN NaN \n7 NaN NaN NaN \n\n customevent_timestamp errorevent_message ... issueevent_messageid \\\n0 NaN NaN ... 5.680858e+09 \n2 NaN NaN ... 5.680913e+09 \n3 NaN NaN ... NaN \n4 NaN NaN ... 5.680859e+09 \n5 NaN NaN ... NaN \n6 NaN NaN ... NaN \n7 NaN NaN ... 5.680898e+09 \n\n issueevent_timestamp issueevent_type \\\n0 1.614202e+12 click_rage \n2 1.614202e+12 click_rage \n3 NaN NaN \n4 1.614202e+12 click_rage \n5 NaN NaN \n6 NaN NaN \n7 1.614202e+12 cpu \n\n issueevent_contextstring issueevent_context \\\n0 SIGN OUT Triston Armstrong DEVELOPER Join GitS... NaN \n2 SAVE & NEXT NaN \n3 NaN NaN \n4 SIGN OUT Triston Armstrong DEVELOPER Join GitS... NaN \n5 NaN NaN \n6 NaN NaN \n7 https://app.gitstart.com/ NaN \n\n issueevent_payload customissue_name customissue_payload \\\n0 NaN NaN NaN \n2 NaN NaN NaN \n3 NaN NaN NaN \n4 NaN NaN NaN \n5 NaN NaN NaN \n6 NaN NaN NaN \n7 {\"Duration\":10581,\"Rate\":94} NaN NaN \n\n received_at batch_order_number \n0 1616761976450 2962 \n2 1616761976855 3003 \n3 1616761976460 2965 \n4 1616761976460 2966 \n5 1616761976464 2968 \n6 1616761976539 2972 \n7 1616761976661 2988 \n\n[7 rows x 49 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
sessionidconnectioninformation_downlinkconnectioninformation_typeconsolelog_levelconsolelog_valuecustomevent_messageidcustomevent_namecustomevent_payloadcustomevent_timestamperrorevent_message...issueevent_messageidissueevent_timestampissueevent_typeissueevent_contextstringissueevent_contextissueevent_payloadcustomissue_namecustomissue_payloadreceived_atbatch_order_number
04207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...5.680858e+091.614202e+12click_rageSIGN OUT Triston Armstrong DEVELOPER Join GitS...NaNNaNNaNNaN16167619764502962
24207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...5.680913e+091.614202e+12click_rageSAVE & NEXTNaNNaNNaNNaN16167619768553003
34207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaN16167619764602965
44207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...5.680859e+091.614202e+12click_rageSIGN OUT Triston Armstrong DEVELOPER Join GitS...NaNNaNNaNNaN16167619764602966
54207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaN16167619764642968
64207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaN16167619765392972
74207534060820504NaNNaNNaNNaNNaNNaNNaNNaNNaN...5.680898e+091.614202e+12cpuhttps://app.gitstart.com/NaN{\"Duration\":10581,\"Rate\":94}NaNNaN16167619766612988
\n

7 rows × 49 columns

\n
" + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "all_events[all_events.sessionid == all_events.iloc[0].sessionid].head(10)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "We mostly see NaNs and that's OK.\n", + "One row in session events files contains user's actions grouped by timestamp.\n", + "For example, if there was a mouse click event at a time $t$, only the columns corresponding to that click\n", + "(such as mouseclick_label, mouseclick_hesitationtime etc) will be filled and the rest will be NaNs.\n", + "The columns sessionid, received_at, batch_order_number will always be filled as the contain information about the\n", + "session unique identifier, the time at which the event was received by connectors worker and the order number\n", + "to ensure the chronological ordering is preserved when inserting in database in batches." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [ + { + "data": { + "text/plain": "['sessionid',\n 'connectioninformation_downlink',\n 'connectioninformation_type',\n 'consolelog_level',\n 'consolelog_value',\n 'customevent_messageid',\n 'customevent_name',\n 'customevent_payload',\n 'customevent_timestamp',\n 'errorevent_message',\n 'errorevent_messageid',\n 'errorevent_name',\n 'errorevent_payload',\n 'errorevent_source',\n 'errorevent_timestamp',\n 'jsexception_message',\n 'jsexception_name',\n 'jsexception_payload',\n 'metadata_key',\n 'metadata_value',\n 'mouseclick_id',\n 'mouseclick_hesitationtime',\n 'mouseclick_label',\n 'pageevent_firstcontentfulpaint',\n 'pageevent_firstpaint',\n 'pageevent_messageid',\n 'pageevent_referrer',\n 'pageevent_speedindex',\n 'pageevent_timestamp',\n 'pageevent_url',\n 'pagerendertiming_timetointeractive',\n 'pagerendertiming_visuallycomplete',\n 'rawcustomevent_name',\n 'rawcustomevent_payload',\n 'setviewportsize_height',\n 'setviewportsize_width',\n 'timestamp_timestamp',\n 'user_anonymous_id',\n 'user_id',\n 'issueevent_messageid',\n 'issueevent_timestamp',\n 'issueevent_type',\n 'issueevent_contextstring',\n 'issueevent_context',\n 'issueevent_payload',\n 'customissue_name',\n 'customissue_payload',\n 'received_at',\n 'batch_order_number']" + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's take a glance at all available features\n", + "list(all_events.columns)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 2. Feature engineering\n", + "\n", + "How are we going to predict buying users?\n", + "What kind of features identify them?\n", + "\n", + "- Did the user visit the website earlier?\n", + "- Was the website not illustrating images?\n", + "- Did users experience many issues on the website?\n", + "- What pages did he visit?\n", + "\n", + "All of those questions sound important.\n", + "In these section we will extract the answers to this questions from the datasets of events and sessions for each user.\n", + "We'll create a vector of numerical features and assign them to each user who visited the site.\n", + "Our goal is to see if the feature sets or buyers and is separable by some nonlinear function with a good precision. We'll be looking for this function using decision trees model.\n", + "Of course one can experiment further by applying any other algorithm." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Unfortunately, none of the paying clients received ids.\n", + "Hence we'll only be looking into the session features." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [], + "source": [ + "# Create a DataFrame for session features\n", + "labels = sessions_info['paid']\n", + "session_features = sessions_info.drop(['paid'], axis=1)\n", + "session_features.rename({'session_id': 'sessionid'}, axis=1, inplace=True)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Let's get to some meaningful features. For example, let's create a function\n", + "that will tell if a specific event happened during the session." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 10, + "outputs": [], + "source": [ + "def add_feature_about_event_presence(session_features, column, feature_name, dtype='int'):\n", + " temp_sessions = all_events[['sessionid', column]].dropna()\n", + " temp_sessions = temp_sessions.drop_duplicates(subset='sessionid', keep='last')\n", + " if temp_sessions.shape[0] == 0:\n", + " return session_features\n", + " session_features = session_features.merge(temp_sessions, how='left', on='sessionid')\n", + "\n", + " if dtype == 'int':\n", + " session_features.loc[session_features[column] > 0, feature_name] = 1\n", + " elif dtype == 'str':\n", + " session_features.loc[session_features[column] != '', feature_name] = 1\n", + " session_features[feature_name] = session_features[feature_name].fillna(0)\n", + " session_features = session_features.drop([column], axis=1)\n", + " return session_features" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [], + "source": [ + "for column, feature_name in [('errorevent_messageid', 'error_event'),\n", + " ('customevent_messageid', 'custom_event'),\n", + " ('jsexception_message', 'js_exception'),\n", + " ('customissue_name', 'custom_issue')\n", + " ]:\n", + " session_features = add_feature_about_event_presence(session_features, column, feature_name)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "At some point it's interesting to take a look\n", + "at the maximum values of some parameters during the session.\n", + "These features can be added with the function below:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 12, + "outputs": [], + "source": [ + "def add_max_val(session_features, column):\n", + " feature_df = all_events[['sessionid', column]].dropna()\n", + " feature_df_agg = feature_df.groupby('sessionid').agg('max').reset_index()\n", + " if feature_df_agg.shape[0] > 0:\n", + " session_features = session_features.merge(feature_df_agg, how='left', on='sessionid')\n", + " session_features[col] = session_features[col].fillna(0)\n", + " return session_features" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 13, + "outputs": [], + "source": [ + "max_feature_columns = ['pageevent_firstcontentfulpaint',\n", + " 'pageevent_firstpaint',\n", + " 'pageevent_speedindex',\n", + " 'pagerendertiming_timetointeractive',\n", + " 'pagerendertiming_visuallycomplete',\n", + " 'rawcustomevent_name',\n", + " 'rawcustomevent_payload',\n", + " 'setviewportsize_height',\n", + " 'setviewportsize_width']\n", + "\n", + "for col in max_feature_columns:\n", + " session_features = add_max_val(session_features, col)\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "We also should handle categorical variables, the ones that can take on one of a limited,\n", + "and usually fixed, number of possible values (such us user's browser)." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 14, + "outputs": [], + "source": [ + "def add_categorial_feature(session_features, column, feature_name):\n", + " categories = list(session_features[column].unique())\n", + " session_features[feature_name] = pd.Categorical(session_features[column], categories=categories).codes" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 15, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\david\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3361: DtypeWarning: Columns (1,2,3,4,5,8,9,10,11) have mixed types.Specify dtype option on import or set low_memory=False.\n", + " if (await self.run_code(code, result, async_=asy)):\n" + ] + } + ], + "source": [ + "# Get additional information from sessions table\n", + "sessions_table = load_sessions()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 16, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "54057\n" + ] + } + ], + "source": [ + "sessions_table = sessions_table.drop_duplicates(subset=['sessionid'], keep='last')\n", + "print(sessions_table.shape[0])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 17, + "outputs": [], + "source": [ + "for iss in ['click_rage', 'missing_resource', 'dead_click', 'js_exception', 'bad_request', 'cpu', 'memory']:\n", + " session_features[iss] = session_features['issue_types'].apply(lambda x: 1 if iss in x else 0)\n", + "session_features = session_features.drop(['issue_types'], axis=1)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [ + { + "data": { + "text/plain": " sessionid events_count pages_count error_event \\\n0 4323603410944837 2 1 0.0 \n1 4323604627948361 11 1 0.0 \n2 4323603204776517 1 1 0.0 \n3 4323548146402182 37 7 0.0 \n4 4323554393301661 10 3 0.0 \n5 4323594072990251 17 5 0.0 \n6 4323655897228014 1 1 0.0 \n7 4323630712718240 7 1 0.0 \n8 4323592422793765 11 1 0.0 \n9 4323618781102971 7 2 0.0 \n\n pageevent_firstcontentfulpaint pageevent_firstpaint pageevent_speedindex \\\n0 0.0 0.0 0.0 \n1 0.0 0.0 0.0 \n2 0.0 0.0 0.0 \n3 3940.0 3850.0 3825.0 \n4 0.0 0.0 0.0 \n5 0.0 0.0 166.0 \n6 0.0 0.0 0.0 \n7 0.0 0.0 0.0 \n8 2312.0 2251.0 2312.0 \n9 0.0 0.0 0.0 \n\n pagerendertiming_timetointeractive pagerendertiming_visuallycomplete \\\n0 0.0 0.0 \n1 0.0 0.0 \n2 0.0 0.0 \n3 0.0 0.0 \n4 0.0 0.0 \n5 0.0 0.0 \n6 0.0 0.0 \n7 0.0 0.0 \n8 0.0 0.0 \n9 0.0 0.0 \n\n setviewportsize_height setviewportsize_width click_rage \\\n0 0.0 0.0 0 \n1 0.0 0.0 1 \n2 0.0 0.0 0 \n3 0.0 0.0 1 \n4 0.0 0.0 0 \n5 0.0 0.0 0 \n6 0.0 0.0 0 \n7 0.0 0.0 0 \n8 0.0 0.0 0 \n9 0.0 0.0 1 \n\n missing_resource dead_click js_exception bad_request cpu memory \n0 0 0 0 0 0 0 \n1 0 0 0 0 0 0 \n2 0 0 0 0 1 0 \n3 1 0 0 0 1 0 \n4 0 0 0 0 0 0 \n5 0 0 0 0 1 0 \n6 0 0 0 0 0 0 \n7 0 0 0 0 0 0 \n8 0 1 0 0 0 0 \n9 0 0 0 0 0 0 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
sessionidevents_countpages_counterror_eventpageevent_firstcontentfulpaintpageevent_firstpaintpageevent_speedindexpagerendertiming_timetointeractivepagerendertiming_visuallycompletesetviewportsize_heightsetviewportsize_widthclick_ragemissing_resourcedead_clickjs_exceptionbad_requestcpumemory
04323603410944837210.00.00.00.00.00.00.00.00000000
143236046279483611110.00.00.00.00.00.00.00.01000000
24323603204776517110.00.00.00.00.00.00.00.00000010
343235481464021823770.03940.03850.03825.00.00.00.00.01100010
443235543933016611030.00.00.00.00.00.00.00.00000000
543235940729902511750.00.00.0166.00.00.00.00.00000010
64323655897228014110.00.00.00.00.00.00.00.00000000
74323630712718240710.00.00.00.00.00.00.00.00000000
843235924227937651110.02312.02251.02312.00.00.00.00.00010000
94323618781102971720.00.00.00.00.00.00.00.01000000
\n
" + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_features.head(10)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 19, + "outputs": [ + { + "data": { + "text/plain": "(57794, 18)" + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_features.shape" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 20, + "outputs": [], + "source": [ + "def add_one_hot_encoded_feature(origin_df, session_features, column):\n", + " df = origin_df[['sessionid', column]]\n", + " dummies = pd.get_dummies(df[column], prefix=column, dummy_na=True)\n", + " df = pd.concat([df, dummies], axis=1)\n", + " session_features = session_features.merge(df, how='left', on='sessionid')\n", + " session_features = session_features.drop([column], axis=1)\n", + " return session_features" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 21, + "outputs": [], + "source": [ + "for col in ['user_browser', 'user_country', 'user_device', 'connection_type']:\n", + " session_features = add_one_hot_encoded_feature(sessions_table, session_features, col)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 22, + "outputs": [ + { + "data": { + "text/plain": "(57794, 34)" + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_features.shape" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 23, + "outputs": [], + "source": [ + "columns_to_merge = ['connection_effective_bandwidth', 'session_start_timestamp',\n", + " 'session_duration', 'user_device_heap_size',\n", + " 'user_device_memory_size', 'avg_cpu', 'avg_fps', 'max_cpu',\n", + " 'max_fps', 'max_total_js_heap_size', 'max_used_js_heap_size',\n", + " 'js_exceptions_count', 'long_tasks_total_duration', 'long_tasks_max_duration',\n", + " 'long_tasks_count', 'inputs_count', 'clicks_count', 'sessionid'\n", + " ]\n", + "session_features = session_features.merge(sessions_table[columns_to_merge],\n", + " how='left',\n", + " on='sessionid')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 24, + "outputs": [ + { + "data": { + "text/plain": " sessionid events_count pages_count error_event \\\n0 4323603410944837 2 1 0.0 \n1 4323604627948361 11 1 0.0 \n2 4323603204776517 1 1 0.0 \n3 4323548146402182 37 7 0.0 \n4 4323554393301661 10 3 0.0 \n\n pageevent_firstcontentfulpaint pageevent_firstpaint pageevent_speedindex \\\n0 0.0 0.0 0.0 \n1 0.0 0.0 0.0 \n2 0.0 0.0 0.0 \n3 3940.0 3850.0 3825.0 \n4 0.0 0.0 0.0 \n\n pagerendertiming_timetointeractive pagerendertiming_visuallycomplete \\\n0 0.0 0.0 \n1 0.0 0.0 \n2 0.0 0.0 \n3 0.0 0.0 \n4 0.0 0.0 \n\n setviewportsize_height ... max_cpu max_fps max_total_js_heap_size \\\n0 0.0 ... 89.0 120.0 51399294.0 \n1 0.0 ... 0.0 0.0 0.0 \n2 0.0 ... 90.0 190.0 49323074.0 \n3 0.0 ... 68.0 60.0 140152925.0 \n4 0.0 ... 38.0 61.0 86162824.0 \n\n max_used_js_heap_size js_exceptions_count long_tasks_total_duration \\\n0 46629158.0 0.0 9161.0 \n1 0.0 0.0 0.0 \n2 47140794.0 0.0 407.0 \n3 121761837.0 0.0 0.0 \n4 82145777.0 0.0 0.0 \n\n long_tasks_max_duration long_tasks_count inputs_count clicks_count \n0 68.0 7.0 0.0 0.0 \n1 0.0 0.0 1.0 0.0 \n2 73.0 3.0 1.0 0.0 \n3 0.0 0.0 0.0 0.0 \n4 0.0 0.0 0.0 0.0 \n\n[5 rows x 51 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
sessionidevents_countpages_counterror_eventpageevent_firstcontentfulpaintpageevent_firstpaintpageevent_speedindexpagerendertiming_timetointeractivepagerendertiming_visuallycompletesetviewportsize_height...max_cpumax_fpsmax_total_js_heap_sizemax_used_js_heap_sizejs_exceptions_countlong_tasks_total_durationlong_tasks_max_durationlong_tasks_countinputs_countclicks_count
04323603410944837210.00.00.00.00.00.00.0...89.0120.051399294.046629158.00.09161.068.07.00.00.0
143236046279483611110.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.01.00.0
24323603204776517110.00.00.00.00.00.00.0...90.0190.049323074.047140794.00.0407.073.03.01.00.0
343235481464021823770.03940.03850.03825.00.00.00.0...68.060.0140152925.0121761837.00.00.00.00.00.00.0
443235543933016611030.00.00.00.00.00.00.0...38.061.086162824.082145777.00.00.00.00.00.00.0
\n

5 rows × 51 columns

\n
" + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_features = session_features.dropna(how='all', axis=0)\n", + "session_features = session_features.fillna(0)\n", + "session_features.head()\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 25, + "outputs": [ + { + "data": { + "text/plain": "(57794, 51)" + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_features.shape" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 25, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 3. Build model" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "Decision tree model is chosen because it is known to work great with heterogenous datasets and correlated features" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 26, + "outputs": [], + "source": [ + "import xgboost as xgb\n", + "import sklearn\n", + "from sklearn.model_selection import train_test_split" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 27, + "outputs": [], + "source": [ + "x_train, x_test, y_train, y_test = train_test_split(session_features.drop(['sessionid'], axis=1),\n", + " labels,\n", + " test_size=0.15,\n", + " random_state=42)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 28, + "outputs": [ + { + "data": { + "text/plain": "(6, 17)" + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Ensure that test set has paying clients\n", + "sum(y_test), sum(y_train)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 29, + "outputs": [ + { + "data": { + "text/plain": "0.0003460630241836984" + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Ratio of paying sessions will be denoted by EPSILON\n", + "EPSILON = y_train[y_train == 1].shape[0]/y_train.shape[0]\n", + "EPSILON" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 30, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\david\\appdata\\local\\programs\\python\\python38\\lib\\site-packages\\xgboost\\sklearn.py:888: UserWarning: The use of label encoder in XGBClassifier is deprecated and will be removed in a future release. To remove this warning, do the following: 1) Pass option use_label_encoder=False when constructing XGBClassifier object; and 2) Encode your labels (y) as integers starting with 0, i.e. 0, 1, 2, ..., [num_class - 1].\n", + " warnings.warn(label_encoder_deprecation_msg, UserWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[17:33:08] WARNING: C:/Users/Administrator/workspace/xgboost-win64_release_1.3.0/src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.\n" + ] + }, + { + "data": { + "text/plain": "XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,\n colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,\n importance_type='gain', interaction_constraints='',\n learning_rate=0.300000012, max_delta_step=0, max_depth=6,\n min_child_weight=1, missing=nan, monotone_constraints='()',\n n_estimators=100, n_jobs=8, num_parallel_tree=1, random_state=0,\n reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,\n tree_method='exact', validate_parameters=1, verbosity=None)" + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xgc0 = xgb.XGBClassifier()\n", + "xgc0.fit(x_train, y_train)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 31, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[17:33:09] WARNING: C:/Users/Administrator/workspace/xgboost-win64_release_1.3.0/src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.\n" + ] + }, + { + "data": { + "text/plain": "XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,\n colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,\n importance_type='gain', interaction_constraints='',\n learning_rate=0.300000012, max_delta_step=0, max_depth=6,\n min_child_weight=1, missing=nan, monotone_constraints='()',\n n_estimators=100, n_jobs=8, num_parallel_tree=1, random_state=0,\n reg_alpha=0, reg_lambda=1, scale_pos_weight=2889.6470588235293,\n subsample=1, tree_method='exact', validate_parameters=1,\n verbosity=None)" + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xgc = xgb.XGBClassifier(scale_pos_weight=1/EPSILON)\n", + "xgc.fit(x_train, y_train)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 4. Evaluate and choose the best model\n", + "\n", + "In this section we will built two models\n", + "and see at their performances using different metrics" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 32, + "outputs": [], + "source": [ + "from datetime import datetime\n", + "from xgboost import plot_importance\n", + "from sklearn.metrics import plot_roc_curve, recall_score, precision_score, accuracy_score, confusion_matrix\n", + "from sklearn.metrics import plot_confusion_matrix, precision_recall_curve, plot_precision_recall_curve\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 33, + "outputs": [], + "source": [ + "def report(models, x_test, y_test, y_pred=None, model_names=None):\n", + "\n", + " fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 14))\n", + "\n", + " for model, name in zip(models, model_names):\n", + "\n", + " y_out = model.predict(x_test)\n", + "\n", + " precision = precision_score(y_test, y_out)\n", + " recall = recall_score(y_test, y_out)\n", + " accuracy = accuracy_score(y_test, y_out)\n", + "\n", + " print(f'Model: {name}')\n", + " print(f'Precision: {precision}')\n", + " print(f'Recall: {recall}')\n", + " print(f'Accuracy: {accuracy}')\n", + " print('-------------------------')\n", + " print()\n", + "\n", + " roc_auc = plot_roc_curve(model, x_test, y_test, ax=ax1)\n", + " precision_recall = plot_precision_recall_curve(model, x_test, y_test, ax=ax2)\n", + "\n", + " date_time = datetime.now().strftime(\"%m_%d_%H_%M_%S\")\n", + " plt.savefig(f'report_{date_time}.png', dpi=300)\n", + " plt.show()\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 34, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: XGBClassifier\n", + "Precision: 0.8\n", + "Recall: 0.6666666666666666\n", + "Accuracy: 0.9996539792387543\n", + "-------------------------\n", + "\n", + "Model: Weighted XGBClassifier\n", + "Precision: 0.6666666666666666\n", + "Recall: 1.0\n", + "Accuracy: 0.9996539792387543\n", + "-------------------------\n", + "\n" + ] + }, + { + "data": { + "text/plain": "
", + "image/png": "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OPaYMWPYaqutGDJkCDvttBM/+9nPANhrr72YOnVqIfVPnTqVr3/96wAsXryYT3/60wwZMoQbb7yRL33pS9TX11d1/LFjxzbUDbBkyRJ69OjBaaedtly7Pn368M9//rNhecqUKRx44IENy3fccQd1dXUMGjSIHXbYgW9961tV1QXw3e9+l80335z1119/pe3+93//l/79+7PlllsyefLkhvV33nknW265Jf379+f8889vWH/UUUcxY8aMqusDw7UkSVrLbL755pxwwgkNYfG0007j+OOPp0+fPixatIgDDjiA448/nhdffJEnnniCH/3oR7z00ksN+48aNYpp06Yxffp01llnHW688UYArrzySu6++24ee+wxpk2bxr333ktmFl5/XV0dl156KQBPPvkkANOmTWPUqFH85Cc/YdCgQRUfa+nSpcstL1myhHHjxnH00Uc3rLv77rsZOHAgN998c8Xv55lnnuGrX/0qv/jFL6ivr2fq1Kn079+/4rpW5KCDDuKxxx5baZv6+npuuOEGpk+fzp133smJJ57I0qVLWbp0KSeddBJ33HEH9fX1TJgwoeEXkRNOOIELL7yw6vrA2UIkSVIHOvu26dT/bWGhxxz0se6cedDglbb5xje+wdChQxk7diwPPfQQl112GQDXX389w4YN4+CDD25ou80227DNNtt84BhLlizhrbfeYqONNgLgvPPOY8qUKXTv3h2A7t2784UvfOED+51wwgk8/vjjvPPOOxxxxBGcffbZQCnkT5w4kc6dO7PPPvswZswYbr75Zs4++2w6derEhhtuyIMPPsiUKVMYM2YM48aN45hjjmHu3LkMGTKEX//61xx33HGMGTOGuro67rrrLs4880wWL15Mv379uPbaa1l//fXp06cPo0aN4u677+bUU0/lqKOOaqjtvvvuY8cdd6Rz539FxAkTJnDyySdzxRVX8Mgjj7Dbbru1eA0uvPBCvvvd77LVVlsB0KlTJ0444YQW92vJrrvu2mKbW2+9laOOOop1112Xvn370r9//4ZA3r9/f7bYYgug1Ft96623MmjQID75yU9y7LHHsmTJkuXee2sYriVJ0lqnS5cuXHTRRYwcOZK77rqrYR7j6dOns+OOO6503xtvvJGHHnqIOXPmMHDgQA466CAWLlzIG2+80RDcVuYHP/gBG2+8MUuXLmXvvffm6aefpmfPntxyyy0899xzRASvv/46AOeccw6TJ0+mZ8+eDeuW2WyzzfjJT37CmDFjmDRp0nLb/vnPf3Luuedyzz33sN5663HBBRdw8cUXc8YZZwCwySab8Kc//ekDtT388MMMHTq0YXnRokXcc889XHXVVbz++utMmDChonD9zDPPVDQM5P777+cb3/jGB9Z/+MMf5g9/+EOL+zfn1VdfXS6E9+rVi1dffRUo/dWi8fo//vGPAHzoQx+if//+PPXUU8u9/9YwXEuSpA7TUg9zW7rjjjv46Ec/yjPPPMOIESOabXPooYcyY8YMBg4cyG9+8xugNCzksssuIzM56aSTuOiiizjxxBMrPu9NN93E1VdfzZIlS5gzZw719fUMGjSIrl27ctxxx3HggQc2jF3efffdOfbYYznyyCM57LDDKj7Ho48+Sn19PbvvvjsA7777LsOGDWvYPmrUqGb3mzNnDltvvXXD8qRJkxg+fDjdunXj8MMP5/vf/z5jx46lU6dOzc6ksaqzawwfPpxp06at0j5tZbPNNuNvf/tb1eHaMdeSJGmtM23aNO6++24effRRLrnkEubMmQPA4MGDl+vRveWWWxg/fnzDzY+NRQQHHXQQDz74IN27d2f99ddfbmx2c15++WXGjBnDvffey9NPP80BBxzAokWL6Ny5M4899hhHHHEEkyZNYuTIkUBpHPe5557LrFmzGDp0KPPmzavo/WUmI0aMYNq0aUybNo36+np++tOfNmxfb731mt2vW7duy83rPGHCBO655x769OnTcP777rsPKPV+z58/v6Hta6+9xqabbgqUPscnnniixTrvv/9+hgwZ8oGvSnrHV6Rnz57MmjWrYXn27Nn07NlzheuXWbRoEd26dWv1eZcxXEuSpLVKZnLCCScwduxYevfuzSmnnMK3v/1tAI4++mgefvhhJk6c2NC+8WwgTT300EP069cPgO985zucdNJJLFxYGkP+5ptvLjfrBsDChQtZb7312HDDDfn73//OHXfc0dB2wYIF7L///lxyySU89dRTALz44ovssssunHPOOfTo0WO5cLgyu+66Kw8//DAzZ84E4K233uKFF15ocb+tt966YZ+FCxfy+9//nr/+9a+88sorvPLKK1x++eVMmDABKM2A8vOf/xwo3Rj5i1/8guHDhwNwyimncN555zWc8/333+fKK6/8wPmW9Vw3/WrtkBCAgw8+mBtuuIHFixfz8ssvM2PGDHbeeWd22mknZsyYwcsvv8y7777LDTfcsNzY+hdeeKHZsfWrynAtSZLWKtdccw29e/duGApy4okn8uyzz/LAAw/QrVs3Jk2axJVXXskWW2zBsGHDOPfcc/ne977XsP+NN97IkCFD2G677XjyySc5/fTTgdKNisOHD2ennXZim2224ZOf/CQf+tDyUWv77bdnhx12YKuttuLoo49uGLbxxhtvcOCBB7LddtvxiU98gosvvhgohdRtt92WbbbZht12243tt9++ovfYo0cPxo8fz+jRo9luu+0YNmwYzz33XIv77bfffg3TDt5yyy186lOfYt11123Yfsghh3DbbbexePFiTj/9dGbOnNnwnvr3788xxxwDwHbbbcfYsWMZPXo0W2+9Ndtss02LvfqVOPXUU+nVqxdvv/02vXr14qyzzgJg4sSJDePJBw8ezJFHHsmgQYMYOXIkl19+OZ06daJz585cdtll7Lvvvmy99dYceeSRDB5cGpb097//nW7duvHv//7vVdcYbTFFTEeoq6vLouaPXBXTz/sEAIP/56F2P7ckSbXo2WefXW5cr1Yvhx56KBdeeCEDBgzo6FLazSWXXEL37t057rjjPrCtuZ/XiHgiM+uaO5Y915IkSWpw/vnnN4xBX1t85CMfaXbaxNZwthBJkiQ12HLLLdlyyy07uox29cUvfrGwY9lzLUmSJBXEcC1JkiQVpEPCdUSMjIjnI2JmRJzWzPZ1I+LG8vY/RkSfDihTkiRJWiXtHq4johNwObAfMAgYHRGDmjQ7Dpifmf2BS4AL2rdKSZIkadV1RM/1zsDMzHwpM98FbgAOadLmEOC68utfAXvHqj5PU5IkqRmzZs2ib9++DU9dnD9/Pn379uWVV14BYMaMGRx44IH069ePoUOHMnz48Ia5n8ePH0+PHj0YMmQIgwcP5ogjjljuITNjxoxhq622YsiQIey0004ND5HZa6+9KGrK4KlTp/L1r38dgMWLF/PpT3+aIUOGcOONN/KlL32J+vr6qo4/duzY5R5+s2TJEnr06MFppy0/2KBPnz7885//bFieMmVKw2PbofR4+bq6OgYNGsQOO+zAt771rarqAvjud7/L5ptvzvrrr7/Sdv/7v/9L//792XLLLZk8eXLD+jvvvJMtt9yS/v37c/755zesP+qoo5gxY0bV9UHHhOueQOPHC80ur2u2TWYuARYAm7RLdZIkaY22+eabc8IJJzSExdNOO43jjz+ePn36sGjRIg444ACOP/54XnzxRZ544gl+9KMfLfcAlFGjRjFt2jSmT5/OOuusw4033giUHlV+991389hjjzFt2jTuvfde2uJ5InV1dVx66aUAPPnkk0Dpce6jRo3iJz/5CYMGNR0QsGJLly5dbnnJkiWMGzeOo48+umHd3XffzcCBA7n55psrfj/PPPMMX/3qV/nFL35BfX09U6dOpX///hXXtSIHHXQQjz322Erb1NfXc8MNNzB9+nTuvPNOTjzxRJYuXcrSpUs56aSTuOOOO6ivr2fChAkNv4iccMIJXHjhhVXXBzU+FV9EHA8cD9C7d+8OqeGNjzgJviRJrXbHafB/fy72mP++Lex3/kqbfOMb32Do0KGMHTuWhx56iMsuuwyA66+/nmHDhi33WOxtttmm2cdiL1myhLfeeouNNtoIgPPOO48pU6bQvXt3ALp3797s3MknnHACjz/+OO+88w5HHHEEZ599NlAK+RMnTqRz587ss88+jBkzhptvvpmzzz6bTp06seGGG/Lggw8yZcoUxowZw7hx4zjmmGOYO3cuQ4YM4de//jXHHXccY8aMoa6ujrvuuoszzzyTxYsX069fP6699lrWX399+vTpw6hRo7j77rs59dRTOeqooxpqu++++9hxxx3p3PlfEXHChAmcfPLJXHHFFTzyyCPstttuLV6CCy+8kO9+97tstdVWAHTq1IkTTjihxf1asuuuu7bY5tZbb+Woo45i3XXXpW/fvvTv378hkPfv358tttgCKPVW33rrrQwaNIhPfvKTHHvssSxZsmS5994aHRGuXwU2b7Tcq7yuuTazI6IzsCEwr+mBMvNq4GooPaGxTaptwa4nXtMRp5UkSVXo0qULF110ESNHjuSuu+6iS5cuAEyfPp0dd9xxpfveeOONPPTQQ8yZM4eBAwdy0EEHsXDhQt54442G4LYyP/jBD9h4441ZunQpe++9N08//TQ9e/bklltu4bnnniMieP311wE455xzmDx5Mj179mxYt8xmm23GT37yE8aMGcOkSZOW2/bPf/6Tc889l3vuuYf11luPCy64gIsvvrjhEeGbbLIJf/rTnz5Q28MPP8zQoUMblhctWsQ999zDVVddxeuvv86ECRMqCtfPPPNMRcNA7r//fr7xjW98YP2HP/xh/vCHP7S4f3NeffXV5UJ4r169ePXVUtTcfPPNl1v/xz/+EYAPfehD9O/fn6eeemq5998aHRGuHwcGRERfSiH6KODoJm0mAl8AHgGOAO7LNeU57ZIk6V9a6GFuS3fccQcf/ehHeeaZZxgxYkSzbQ499FBmzJjBwIED+c1vfgOUhoVcdtllZCYnnXQSF110ESeeeGLF573pppu4+uqrWbJkCXPmzKG+vp5BgwbRtWtXjjvuOA488MCGscu77747xx57LEceeSSHHXZYxed49NFHqa+vZ/fddwfg3XffZdiwYQ3bR40a1ex+c+bMWe5R35MmTWL48OF069aNww8/nO9///uMHTuWTp060dztcKt6i9zw4cOZNm3aKu3TVjbbbDP+9re/VR2u233MdXkM9VeBycCzwE2ZOT0izomIZX+D+SmwSUTMBL4JfGC6PkmSpNaaNm0ad999N48++iiXXHJJw+O+Bw8evFyP7i233ML48eMbbn5sLCI46KCDePDBB+nevTvrr7/+cmOzm/Pyyy8zZswY7r33Xp5++mkOOOAAFi1aROfOnXnsscc44ogjmDRpEiNHjgRK47jPPfdcZs2axdChQ5k37wN/yG9WZjJixAimTZvGtGnTqK+v56c//WnD9vXWW6/Z/bp168aiRYsalidMmMA999xDnz59Gs5/3333AaXe7/nz5ze0fe2119h0002B0uf4xBNPtFjn/fffz5AhQz7wVUnv+Ir07NmTWbP+dXvf7Nmz6dmz5wrXL7No0SK6devW6vMu0yHzXGfm7Zk5MDP7ZeYPyuvOyMyJ5deLMvOzmdk/M3fOzJX/pEqSJFUoMznhhBMYO3YsvXv35pRTTuHb3/42AEcffTQPP/wwEydObGjfeDaQph566CH69esHwHe+8x1OOukkFi5cCMCbb7653KwbAAsXLmS99dZjww035O9//zt33HFHQ9sFCxaw//77c8kll/DUU08B8OKLL7LLLrtwzjnn0KNHj+XC4crsuuuuPPzww8ycOROAt956ixdeeKHF/bbeeuuGfRYuXMjvf/97/vrXv/LKK6/wyiuvcPnllzNhwgSgNAPKz3/+c6B0Y+QvfvELhg8fDsApp5zCeeed13DO999/nyuvvPID51vWc930q7VDQgAOPvhg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qXz9618HYPHixXz6059myJAh3HjjjXzpS1+ivr66SzJ27NiGugGWLFlCjx49OO205Wdt3Guvvdhyyy3Zfvvt2X333Xn++eerOi/Addddx4ABAxgwYADXXXdds22mTZvGrrvuypAhQ6irq+Oxxx4DYMGCBRx00EFsv/32DB48mGuvvRaAuXPnMnLkyKprkyRJNSwz14ivoUOHZlP19fUfWNfeLrjggvzyl7+cmZnHH398nnfeeZmZ+c477+SAAQPy1ltvbWj75z//Oa+99trMzLz22mvzpJNOatg2evToHDduXGZmXnHFFbnPPvvkggULMjNzwYIFOX78+MzM3HPPPfPxxx8v/H088sgjuffee7d6/yVLliy3/N577+W2226b7733XsO622+/PXfbbbfcYost8v33329Y3/g9XXXVVXnQQQe1uo7MzHnz5mXfvn1z3rx5+dprr2Xfvn3ztdde+0C7ESNG5O23356Zmb/73e9yzz33zMzMH/zgB3nqqadmZuY//vGP3GijjXLx4sWZmXnsscfmQw891Ox5V4efR0lSK5zZvfQllQFTcwWZtC1nC1mtnH3bdOr/trDQYw76WHfOPGjwStt84xvfYOjQoYwdO5aHHnqIyy67DIDrr7+eYcOGcfDBBze03Wabbdhmm20+cIwlS5bw1ltvsdFGGwFw3nnnMWXKFLp37w5A9+7d+cIXvvCB/U444QQef/xx3nnnHY444gjOPvtsAE477TQmTpxI586d2WeffRgzZgw333wzZ599Np06dWLDDTfkwQcfZMqUKYwZM4Zx48ZxzDHHMHfuXIYMGcKvf/1rjjvuOMaMGUNdXR133XUXZ555JosXL6Zfv35ce+21rL/++vTp04dRo0Zx9913c+qpp3LUUUc11Hbfffex44470rnzv34EJ0yYwMknn8wVV1zBI488wm677faB97THHnswduzYlX7mLZk8eTIjRoxg4403BmDEiBHceeedjB49erl2EcHChaWfmQULFvCxj32sYf0bb7xBZvLmm2+y8cYbN7yPz3zmM1x//fXsvvvuVdUoSZJq01oTrjtKly5duOiiixg5ciR33XVXwzzH06dPZ8cdd1zpvjfeeCMPPfQQc+bMYeDAgRx00EEsXLiQN954gy222KLFc//gBz9g4403ZunSpey99948/fTT9OzZk1tuuYXnnnuOiOD1118H4JxzzmHy5Mn07NmzYd0ym222GT/5yU8YM2YMkyZNWm7bP//5T84991zuuece1ltvPS644AIuvvhizjjjDAA22WQT/vSnP32gtocffpihQ4c2LC9atIh77rmHq666itdff50JEyY0G65vu+02tt122w+sv+iii7j++us/sH6PPfbg0ksvXW7dq6++yuab/+te2169evHqq03vtS0NW9l333359re/zfvvv88f/vAHAL761a9y8MEH87GPfYw33niDG2+8kQ99qDTCqq6uju9973sfOJYkSVo7rDXhuqUe5rZ0xx138NGPfpRnnnmGESNGNNvm0EMPZcaMGQwcOJDf/OY3AIwaNYrLLruMzOSkk07ioosu4sQTT6z4vDfddBNXX301S5YsYc6cOdTX1zNo0CC6du3Kcccdx4EHHsiBBx4IwO67786xxx7LkUceyWGHHVbxOR599FHq6+sbemrfffddhg0b1rB91KhRze43Z84ctt5664blSZMmMXz4cLp168bhhx/O97//fcaOHUunTp0A+NznPke3bt3o06cPP/rRjz5wvFNOOYVTTjml4rorccUVV3DJJZdw+OGHc9NNN3Hcccdxzz33MHnyZIYMGcJ9993Hiy++yIgRI/jkJz9J9+7d2Wyzzfjb3/5WaB2SJKl2dPQ812u8adOmcffdd/Poo49yySWXMGfOHAAGDx68XI/uLbfcwvjx4xtufmwsIjjooIN48MEH6d69O+uvvz4vvfTSSs/78ssvM2bMGO69916efvppDjjgABYtWkTnzp157LHHOOKII5g0aVLDDXhXXnkl5557LrNmzWLo0KHMmzevoveXmYwYMYJp06Yxbdo06uvr+elPf9qwfb311mt2v27dui037/OECRO455576NOnT8P577vvvobt119/PdOmTeO3v/3tcr3Oy1x00UUMGTLkA1/LbshsrGfPnsya9a/nG82ePZuePXt+oN11113X8IvGZz/72YYbGq+99loOO+wwIoL+/fvTt29fnnvuOaDUA9+tW7eVfmaSJGnNZbhuQ5nJCSecwNixY+nduzennHIK3/72twE4+uijefjhh5k48V+zEzaeDaSphx56iH79Ss/R+c53vsNJJ53UMB74zTffXG7WDYCFCxey3nrrseGGG/L3v/+dO+64o6HtggUL2H///bnkkkt46qmnAHjxxRfZZZddOOecc+jRo8dy4XNldt11Vx5++GFmzpwJwFtvvcULL7zQ4n5bb711wz4LFy7k97//PX/961955ZVXeOWVV7j88suZMGFCRTVAqed6WcBv/NV0SAjAvvvuy1133cX8+fOZP38+d911F/vuu+8H2n3sYx/jgQceAEpjxAcMGACUngZ67733AvD3v/+d559/vmGYzgsvvNDsuHlJkrR2WGuGhXSEa665ht69ezcMBTnxxBO59tpreeCBB9hzzz2ZNGkS3/zmN/nv//5v/u3f/o0NNthgufG6y8Zcv//++/Tq1Yvx48cDpRsV33zzTXbaaSe6dOlCly5d+Na3vrXcubfffnt22GEHttpqKzbffPOGYRtvvPEGhxxyCIsWLSIzufjii4FSOJ0xYwaZyd57783222/fECxXpkePHowfP57Ro0ezePFiAM4991wGDhy40v32228/Pv/5zwOlXvtPfepTrLvuug3bDznkEE499dSGYxZp44035vTTT2ennXYC4Iwzzmi4ufFLX/oSX/nKV6irq+Oaa67h5JNPZsmSJXTt2pWrr74agNNPP51jjz2WbbfdlszkggsuYNNNNwXg/vvv54ADDii8ZkmSVBuiNJtI7aurq8um8zs/++yzy43r1erl0EMP5cILL2zoEV4T7LHHHtx6660NM7s05s+jJNWoszYsf1/QsXVotRERT2RmXXPbHBaiDnP++ec3jEFfE8ydO5dvfvObzQZrSZK0dnBYiDrMlltuyZZbbtnRZRSmR48efOYzn+noMiRJUgey51qSJEkqiOFakiRJKojhWpIkSSqI4VqSJEkqiOG6Dc2aNYu+ffs2PHVx/vz59O3bl1deeQWAGTNmcOCBB9KvXz+GDh3K8OHDefDBBwEYP348PXr0YMiQIQwePJgjjjhiuYfMjBkzhq222oohQ4aw0047NTxEZq+99qLplIStNXXq1IYnHC5evJhPf/rTDBkyhBtvvJEvfelL1NfXV3X8sWPHLvfwmyVLltCjRw9OO+205drttddebLnllmy//fbsvvvuPP/881WdF0pPXxwwYAADBgzguuuua7bNtGnT2HXXXRkyZAh1dXUNT2gEmDJlSsO12XPPPYHSo9/32GMPlixZUnV9kiSpRmXmGvE1dOjQbKq+vv4D69rbBRdckF/+8pczM/P444/P8847LzMz33nnnRwwYEDeeuutDW3//Oc/57XXXpuZmddee22edNJJDdtGjx6d48aNy8zMK664IvfZZ59csGBBZmYuWLAgx48fn5mZe+65Zz7++OOFv49HHnkk995771bvv2TJkuWW33vvvdx2223zvffea1h3++2352677ZZbbLFFvv/++w3rG7+nq666Kg866KBW15GZOW/evOzbt2/OmzcvX3vttezbt2++9tprH2g3YsSIvP322zMz83e/+13uueeemZk5f/783HrrrfMvf/lLZmb+/e9/b9jnrLPOyl/84hfNnnd1+HmUJLXCmd1LX1IZMDVXkEnXnqn47jgN/u/PxR7z37eF/c5faZNvfOMbDB06lLFjx/LQQw9x2WWXAXD99dczbNgwDj744Ia222yzTbOPzl6yZAlvvfVWw/zJ5513HlOmTKF79+4AdO/enS984Qsf2O+EE07g8ccf55133uGII47g7LPPBuC0005j4sSJdO7cmX322YcxY8Zw8803c/bZZ9OpUyc23HBDHnzwQaZMmcKYMWMYN24cxxxzDHPnzmXIkCH8+te/5rjjjmPMmDHU1dVx1113ceaZZ7J48WL69evHtddey/rrr0+fPn0YNWoUd999N6eeeipHHXVUQ2333XcfO+64I507/+tHcMKECZx88slcccUVPPLII+y2224feE977LEHY8eOXeln3pLJkyczYsSIhqcyjhgxgjvvvJPRo0cv1y4iGh4xv2DBAj72sY8B8Mtf/pLDDjuM3r17A7DZZps17POZz3yG73znO3zuc5+rqkZJ0urjz+vuAMC2HVyHasPaE647SJcuXbjooosYOXIkd911F126dAFg+vTp7Ljjjivdd9njz+fMmcPAgQM56KCDWLhwIW+88QZbbLFFi+f+wQ9+wMYbb8zSpUvZe++9efrpp+nZsye33HILzz33HBHB66+/DsA555zD5MmT6dmzZ8O6ZTbbbDN+8pOfMGbMGCZNmrTctn/+85+ce+653HPPPay33npccMEFXHzxxZxxxhkAbLLJJvzpT3/6QG0PP/wwQ4cObVhetGgR99xzD1dddRWvv/46EyZMaDZc33bbbWy77Qf/83bRRRdx/fXXf2D9HnvswaWXXrrculdffZXNN9+8YblXr168+uqrH9h37Nix7Lvvvnz729/m/fff5w9/+AMAL7zwAu+99x577bUXb7zxBieffDL/8R//AZR+QXr88cc/cCxJUu3a9jtTOroE1ZC1J1y30MPclu644w4++tGP8swzzzBixIhm2xx66KHMmDGDgQMH8pvf/AaAUaNGcdlll5GZnHTSSVx00UWceOKJFZ/3pptu4uqrr2bJkiXMmTOH+vp6Bg0aRNeuXTnuuOM48MADOfDAAwHYfffdOfbYYznyyCM57LDDKj7Ho48+Sn19PbvvvjtQGnc8bNiwhu2jRo1qdr85c+Ys9yjwSZMmMXz4cLp168bhhx/O97//fcaOHUunTp0A+NznPke3bt3o06cPP/rRjz5wvFNOOYVTTjml4rorccUVV3DJJZdw+OGHc9NNN3Hcccdxzz33sGTJEp544gnuvfde3nnnHYYNG8auu+7KwIED6dSpE+ussw5vvPEGG2ywQaH1SJKk1Z83NLaxadOmcffdd/Poo49yySWXNDzue/Dgwcv16N5yyy2MHz++4ebHxiKCgw46iAcffJDu3buz/vrr89JLL630vC+//DJjxozh3nvv5emnn+aAAw5g0aJFdO7cmccee4wjjjiCSZMmMXLkSACuvPJKzj33XGbNmsXQoUOZN29eRe8vMxkxYgTTpk1j2rRp1NfX89Of/rRh+3rrrdfsft26dWPRokUNyxMmTOCee+6hT58+Dee/7777GrZff/31TJs2jd/+9rfL9Tovc9FFFzFkyJAPfC27IbOxnj17MmvWrIbl2bNn07Nnzw+0u+666xp+0fjsZz/bcENjr1692HfffVlvvfXYdNNN2WOPPXjqqaca9lu8eDFdu3Zd4WcmSZLWXIbrNpSZnHDCCYwdO5bevXtzyimn8O1vfxuAo48+mocffpiJEyc2tG88G0hTDz30EP369QPgO9/5DieddFLDeOA333xzuVk3ABYuXMh6663HhhtuyN///nfuuOOOhrYLFixg//3355JLLmkIhS+++CK77LIL55xzDj169FgufK7MrrvuysMPP8zMmTMBeOutt3jhhRda3G/rrbdu2GfhwoX8/ve/569//SuvvPIKr7zyCpdffjkTJkyoqAYo9VwvC/iNv5oOCQHYd999ueuuu5g/fz7z58/nrrvuYt999/1Au4997GM88MADQGmM+IABAwA45JBDeOihh1iyZAlvv/02f/zjHxt64efNm8emm27aMPxHkiStXdaeYSEd4JprrqF3794NQ0FOPPFErr32Wh544AH23HNPJk2axDe/+U3++7//m3/7t39jgw024Hvf+17D/svGXL///vv06tWL8ePHA6UbFd9880122mknunTpQpcuXfjWt7613Lm33357dthhB7baais233zzhmEbb7zxBocccgiLFi0iM7n44ouBUjidMWMGmcnee+/N9ttv3xAsV6ZHjx6MHz+e0aNHs3jxYgDOPfdcBg4cuNL99ttvPz7/+c8DpV77T33qU6y77roN2w855BBOPfXUhmMWaeONN+b0009np512AuCMM85ouLnxS1/6El/5yleoq6vjmmuu4eSTT2bJkiV07dqVq6++Gij9YjBy5Ei22247PvShD/GlL32p4UbU+++/nwMOOKDwmiVJUm2I0mwita+uri6bzu/87LPPLjeuV6uXQw89lAsvvLChR3hNcNhhh3H++ec3+8uFP4+SJK0ZIuKJzKxrbpvDQtRhzj///IYx6GuCd999l8985jMt9tpLkqQ1l8NC1GG23HJLttxyy44uozDrrLNOw5R8kiRp7bTG91yvKcNeVNv8OZQkae2wRofrrl27Mm/ePIONOlRmMm/ePKfnkyRpLbBGDwvp1asXs2fPZu7cuR1ditZyXbt2pVevXh1dhiRJamNrdLju0qULffv27egyJEmStJZYo4eFSJIkSe3JcC1JkiQVxHAtSZIkFWSNeUJjRMwF/tJBp98U+GcHnVvtx+u85vMarx28zmsHr/PaoaOu88czs0dzG9aYcN2RImLqih6BqTWH13nN5zVeO3id1w5e57XD6nidHRYiSZIkFcRwLUmSJBXEcF2Mqzu6ALULr/Oaz2u8dvA6rx28zmuH1e46O+ZakiRJKog915IkSVJBDNerICJGRsTzETEzIk5rZvu6EXFjefsfI6JPB5SpKlRwjb8ZEfUR8XRE3BsRH++IOlWdlq5zo3aHR0RGxGp1J7oqU8l1jogjy/+mp0fEL9u7RlWvgv9u946I+yPiyfJ/u/fviDrVehExLiL+ERHPrGB7RMSl5Z+BpyNix/ausTHDdYUiohNwObAfMAgYHRGDmjQ7Dpifmf2BS4AL2rdKVaPCa/wkUJeZ2wG/Ai5s3ypVrQqvMxGxAXAy8Mf2rVBFqOQ6R8QA4DvA7pk5GPjv9q5T1anw3/P3gJsycwfgKODH7VulCjAeGLmS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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "report([xgc0, xgc], x_test, y_test, model_names=['XGBClassifier', 'Weighted XGBClassifier'])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "For the best model let's see the features that influenced the decision the most:\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 35, + "outputs": [ + { + "data": { + "text/plain": "" + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_importance(xgc0)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 35, + "outputs": [], + "source": [ + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/ee/connectors/data_analysis_cookbook/credentials.yml.example b/ee/connectors/data_analysis_cookbook/credentials.yml.example new file mode 100644 index 000000000..e9b7caedd --- /dev/null +++ b/ee/connectors/data_analysis_cookbook/credentials.yml.example @@ -0,0 +1,6 @@ +pg: + user: user + password: ****** + database: db_name + host: '127.0.0.1' + port: 8080 \ No newline at end of file diff --git a/ee/connectors/data_analysis_cookbook/explorational_analysis.ipynb b/ee/connectors/data_analysis_cookbook/explorational_analysis.ipynb new file mode 100644 index 000000000..6a118e458 --- /dev/null +++ b/ee/connectors/data_analysis_cookbook/explorational_analysis.ipynb @@ -0,0 +1,560 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import psycopg2\n", + "from IPython.display import display\n", + "import yaml" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "conf = yaml.load(\n", + " open(\"credentials.yml\"), Loader=yaml.FullLoader)['pg']\n", + "\n", + "# Create a connection to the database\n", + "conn = psycopg2.connect(\n", + " host=conf['host'],\n", + " port=conf['port'],\n", + " database=conf['database'],\n", + " user=conf['user'],\n", + " password=conf['password']\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A simple query can be executed either with native psycopg's framework or instanvia pandas.\n", + "As an example let's get a total number of sessions in the database" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " n_sessions\n", + "0 8961277\n" + ] + } + ], + "source": [ + "q = \"select count(*) as n_sessions from connector_user_sessions\"\n", + "df = pd.read_sql(q, conn)\n", + "print(df)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Distributions\n", + "\n", + "One of the basic usages of the data would be to view the data distributions\n", + "and to learn the main statistical properties such as mean, median, variance etc\n", + "which eventually lead to understanding your users better.\n", + "Visualization examples in this document include:\n", + "- Session durations\n", + "- User locations\n", + "- Website load (seasonality)\n", + "- Histogram of issue counts\n", + "- Hesitation time distribution\n", + "- URL visits graph" + ] + }, + { + "cell_type": "markdown", + "source": [ + "##### Session duration" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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zwFG1G03vGqTpHVWVwfwLcBx1fxxwM117ROqqyF2A96o66qQpHVSp74Tbk1ExI+BMcCHI2I28G3g+8D1EXEC8BxwRLX5bcA44GngLeBvGlWXJDWLhgVwZh7dwap929k2gUmNqkWSmpF3wklSIQawJBViAEtSIQawJBViAEtSIQawJBViAEtSIQawJBViAEtSIQawpNUyatQopk6dWrqMdYIBLDWZp556ir59+3LMMcd0uE1mcvrpp7PJJpuwySabcPrpp1O7ox/uu+8+WlpalpsightvvHGt1Ddr1izGjBmzVvbV0xnAUpOZNGkSn/rUp1a6zSWXXMLPf/5zZsyYwcyZM5kyZQoXX3wxAHvvvTcLFy5sm2699VZaWlo44IAVvyFMpRnA6rZGjhzJueeey/bbb0///v054YQTmDt3LmPHjmXAgAF87nOf47XXXmvb/sEHH2SPPfZg4MCB7LDDDsv9GX355ZfzyU9+kgEDBvCxj32sLcwApk6dyvDhwznvvPPYdNNNGTp0KJdffnlD3tN1113HwIED2Xff941ZtZzJkydz2mmnMXz4cIYNG8Zpp53GFVdc0eG2hx9+OP379293/fHHH8/JJ5/M2LFjaWlpYc899+Tll1/m1FNPZdCgQXziE5/g97//fdv2I0eO5K677gLgrLPO4ogjjmDChAkMGDCAUaNGMW3atDV78z2QAaxu7cYbb+TOO+/kySefZMqUKYwdO5bvfe97zJs3j2XLlnHBBRcAMGfOHMaPH883v/lNXn31VX7wgx9w2GGH0fq1Vptuuim33norCxYs4PLLL+erX/0qDz/8cNvrvPzyy7zxxhvMmTOHyy67jEmTJi0X7vVOPvlkBg4c2O60/fbbd/heFixYwJlnnsn555+/yvc9a9Ysdthhh7blHXbYgVmzZr1vu0WLFnHDDTdw3HHHvW9dveuvv57vfOc7zJ8/nw022IDdd9+dnXfemfnz53P44Yfzta99rcPn3nLLLRx11FG8/vrrHHTQQZxyyimrrF81BrC6tS996UsMGTKEYcOGsffee/PpT3+anXbaib59+3LooYe2nbldffXVjBs3jnHjxrHeeuux3377MXr0aG677TYAxo8fz5ZbbklEsM8++7D//vtz3333tb1Onz59OPPMM+nTpw/jxo2jpaWFJ554ot2aLrzwQl5//fV2p5kzZ3b4Xr71rW9xwgknMHz48FW+74ULF7LRRhu1LW+00UYsXLiwrR+41U033cSHP/xh9tlnn5Xu79BDD2WXXXZpO259+/ZlwoQJ9OrViyOPPHK5M+AV7bXXXowbN45evXpx7LHHMmPGjFXWr5qGjQcsdYUhQ4a0zffr1+99ywsXLgTgueee46c//SlTpkxpW//uu+/y2c9+FoDbb7+ds88+myeffJJly5bx1ltvsd1227Vtu8kmm9C793u/LhtuuGHbvteG6dOnc9ddd6006Oq1tLSwYMGCtuUFCxa0fdhWb/LkyUyYMOF97Svq7HFsz0c+8pG2+Q033JC3336bJUuWLHe81D6PkHqEESNGcOyxx3LppZe+b90777zDYYcdxpVXXsnBBx9Mnz59OOSQQ953NtlZJ510EldffXW76zbffPN2uwqmTp3Ks88+y2abbQbUznCXLl3KY489tlxXSKtRo0YxY8YMdt11VwBmzJjBqFGjltvmhRdeYOrUqcv1Z6u52AWhHuGYY45hypQp3HHHHSxdupS3336bqVOnMnv2bBYvXsw777zD4MGD6d27N7fffju//OUv1/i1fvjDHy53FUL91F74AkycOJE//vGPTJ8+nenTp3PSSScxfvx47rjjjna3nzBhAueffz5z5szhxRdf5LzzzuP4449fbpurrrqKPfbYgy233HKN34saywBWjzBixAhuvvlmvve97zF48GBGjBjBueeey7JlyxgwYAAXXHABRxxxBIMGDeLaa6/loIMO6tL6NtxwQz7ykY+0TS0tLfTt25fBgwcD713b2+rEE0/kwAMPZLvttmPbbbdl/PjxnHjiicvt88orr1zlh28qK9b0z6xmMHr06FztS17GjGHqvffyw4sf6HCTM/vVvp7uf//pXwH4yYl7rPGfo5IEtNsJ7xmwJBViAEvtOOuss9puBX7++edpaWlh6dKlhavqfhw3YuUMYGkVNttsMxYuXEivXr1Kl9Lm1Vdf5dBDD6V///5svvnmXHvttR1uu7JxI6D2AeDHP/5x1ltvvQ7vpltTjhuxcgaw1A1NmjSJ9ddfn7lz53LNNdfwxS9+scMrLFY2bgTU7qK78MIL2XnnnbuqfFUMYHVba3MsiGeeeYZ99tmHAQMGsN9++zF//vy2dc8++ywRwZIlS4Dy40YsWrSIG2+8kXPOOYeWlhb22msvDjro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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "q = 'select session_duration from connector_user_sessions limit 10000'\n", + "durations = pd.read_sql(q, conn)\n", + "\n", + "# translate duration to seconds\n", + "durations['session_duration'] = durations['session_duration'] / (1000 * 60)\n", + "sns.displot(durations, x=\"session_duration\", bins=23)\n", + "x_mean =durations['session_duration'].mean()\n", + "x_median =durations['session_duration'].median()\n", + "\n", + "plt.axvline(x_mean, c='orange')\n", + "plt.axvline(x_median, c='red')\n", + "plt.text(50, 80, f\"mean = {x_mean:.2f} min\", size=12)\n", + "plt.text(50, 70, f\"median = {x_median:.2f} min\", size=12)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "source": [ + "##### User locations" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [], + "source": [ + "q = 'select count(*) as n_users, user_country from connector_user_sessions group by user_country '\n", + "countries = pd.read_sql(q, conn)\n", + "countries = countries[countries['n_users'] > 900]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " n_users user_country latitude longitude\n", + "0 1567 AU -27.0 133.0\n", + "1 31726 BR -10.0 -55.0\n", + "2 5181 CA 60.0 -95.0\n", + "3 1183 CH 47.0 8.0\n", + "4 1013 CO 4.0 -72.0\n" + ] + } + ], + "source": [ + "coordinates = pd.read_csv('coordinates_and_codes.csv')\n", + "countries_with_coords = pd.merge(countries, coordinates, left_on='user_country', right_on='alpha-2_code')\n", + "countries_with_coords.drop(['alpha-2_code'], axis=1, inplace=True)\n", + "print(countries_with_coords.head())" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [ + { + "data": { + "text/plain": "", + "text/html": "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import folium\n", + "\n", + "m = folium.Map(location=[40.18130, 44.5089], zoom_start=1, prefer_canvas=True)\n", + "\n", + "def plotDot(point):\n", + " '''input: series that contains a numeric named latitude and a numeric named longitude\n", + " this function creates a CircleMarker and adds it to your this_map'''\n", + " folium.Marker(location=(point.latitude, point.longitude),\n", + " # radius=point.n_users,\n", + " color=\"#3186cc\",\n", + " popup=point.n_users,\n", + " fill=True,\n", + " fill_color=\"#3186cc\").add_to(m)\n", + "countries_with_coords.apply(plotDot, axis=1)\n", + "m.save('users_map.html')\n", + "\n", + "display(m)\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "##### Website load" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "day = 86400 * 1000\n", + "\n", + "q = 'select session_start_timestamp from connector_user_sessions where session_start_timestamp is not null limit 10000'\n", + "starts = pd.read_sql(q, conn)\n", + "starts['session_start_timestamp'] = starts['session_start_timestamp'].apply(lambda x: (x % day)* 24 / day)\n", + "sns.displot(starts, x=\"session_start_timestamp\", kind=\"kde\", bw_adjust=.2, fill=True)\n", + "plt.xlim(0, 24)\n", + "plt.title(\"Website load distribution during the day\")\n", + "plt.show()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "##### Issue counts" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "q = 'select issues_count from connector_user_sessions limit 10000'\n", + "issues = pd.read_sql(q, conn)\n", + "issues = issues.fillna(0)\n", + "sns.histplot(issues[issues > 2])\n", + "plt.title(\"Distribution of the number of issues across sessions\")\n", + "plt.show()\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "##### Hesitation time distribution" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 10, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(43, 1)\n" + ] + } + ], + "source": [ + "q = \"select mouseclick_hesitationtime from connector_events where mouseclick_label = 'PAY' \" \\\n", + " \"and mouseclick_hesitationtime is not null limit 10000\"\n", + "\n", + "hesitation = pd.read_sql(q, conn)\n", + "print(hesitation.shape)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "sns.displot(hesitation, x=\"mouseclick_hesitationtime\", kind=\"kde\", bw_adjust=.2, fill=True)\n", + "plt.title(\"Hesitation time distribution for a click on the button PAY\")\n", + "plt.show()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Path illustration" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "outputs": [ + { + "data": { + "text/plain": " sessionid pageevent_url\n9036 4169875804784252 231\n134 4167791269614996 229\n7115 4169433581028977 136\n24174 4176629796934961 107\n18942 4172534204991174 104", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
sessionidpageevent_url
90364169875804784252231
1344167791269614996229
71154169433581028977136
241744176629796934961107
189424172534204991174104
\n
" + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Select some session events. For illustration purposes, we stick to a fixed number of rows,\n", + "# however it would be more accurate to load all events for each session\n", + "q = 'select pageevent_url, sessionid from connector_events ' \\\n", + " 'where pageevent_url is not null limit 100000'\n", + "urls = pd.read_sql(q, conn)\n", + "\n", + "# Calculate the number of UNIQUE urls per session\n", + "urls_count = urls.groupby('sessionid').agg('nunique').reset_index()\n", + "\n", + "# Select the session with the maximum number of UNIQUE urls\n", + "urls_count = urls_count.sort_values(by='pageevent_url', ascending=False)\n", + "urls_count.head()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 16, + "outputs": [], + "source": [ + "# Get all url visits in correct order\n", + "# (to visualize the full path, not only unique values matter)\n", + "sess_id = urls_count.iloc[0].sessionid\n", + "sess_id = 4592792577630589\n", + "q = f'select pageevent_url from connector_events ' \\\n", + " f'where sessionid = {sess_id} ' \\\n", + " f'and pageevent_url is not null limit 1000'\n", + "\n", + "session_urls = pd.read_sql(q, conn)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 17, + "outputs": [ + { + "data": { + "text/plain": " pageevent_url\n0 https://atlas.cradle.global/briefs/list\n1 https://atlas.cradle.global/briefs/view/6adf82...\n2 https://atlas.cradle.global/briefs/view/6adf82...\n3 https://atlas.cradle.global/briefs/list\n4 https://atlas.cradle.global/briefs/view/e2b0f0...", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
pageevent_url
0https://atlas.cradle.global/briefs/list
1https://atlas.cradle.global/briefs/view/6adf82...
2https://atlas.cradle.global/briefs/view/6adf82...
3https://atlas.cradle.global/briefs/list
4https://atlas.cradle.global/briefs/view/e2b0f0...
\n
" + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We consider URLs with different query parameters to be the same\n", + "# by cutting off the part after question mark:\n", + "\n", + "session_urls['pageevent_url'] = session_urls['pageevent_url'].apply(lambda x: x.split('?')[0])\n", + "session_urls.head()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For the \"largest\" session draw a graph of page visits\n", + "import networkx as nx\n", + "\n", + "G = nx.DiGraph(directed=True)\n", + "\n", + "for ind in range(session_urls.pageevent_url.shape[0] - 1):\n", + " G.add_edges_from([(session_urls.pageevent_url[ind], session_urls.pageevent_url[ind + 1])])\n", + "\n", + "options = {\n", + " 'node_color': 'blue',\n", + " 'node_size': 50,\n", + " 'width': 1,\n", + " 'alpha': 0.5,\n", + " 'arrowstyle': '-|>',\n", + " 'arrowsize': 12,\n", + "}\n", + "\n", + "nx.draw_networkx(G, arrows=True, with_labels=False, **options)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Analysis of such graphs may be useful in user profiling.\n", + "The graph of page visits can provide essential information to clusterize users by their behavior\n", + "even if they don't actions on the website.\n", + "\n", + "\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} \ No newline at end of file From c689f81ccd3a5c002d182414192695e90f6e3ce4 Mon Sep 17 00:00:00 2001 From: Rajesh Rajendran Date: Tue, 22 Jun 2021 17:16:26 +0000 Subject: [PATCH 05/24] feat(install): build and run images locally. (#46) --- scripts/helm/local_run.md | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 scripts/helm/local_run.md diff --git a/scripts/helm/local_run.md b/scripts/helm/local_run.md new file mode 100644 index 000000000..3c2ed9751 --- /dev/null +++ b/scripts/helm/local_run.md @@ -0,0 +1,56 @@ +## How to build and run an application from local + +### For workers + +Workers are the application which handle core functionalities. + +- List of workers are + - alerts + - assets + - db + - ender + - http + - integrations + - sink + - storage + +- Build: + ``` + cd openreplay/backend + # IMAGE_TAG= DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build.sh + # For example, + IMAGE_TAG=v1.0.0 DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build.sh assets + ``` +- Deploy: + ``` + cd openreplay/scripts/helm + bash openreplay-cli --install + ``` +## For api + +All apis are handled by application called, chalice, which is a python3 application. + +- Build: + ``` + cd openreplay/api/ + # IMAGE_TAG= DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build.sh + # For example, + IMAGE_TAG=v1.0.0 DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build.sh + ``` +- Deploy: + ``` + cd openreplay/scripts/helm + bash openreplay-cli --install chalice + ``` + +## For frontend + +Frontend is mainly JS components. When we're installing it, it's built and then installed. So you don't have to run a separate build for frontend. + +Note: if you want to see how it gets build, please refer, `openreplay/frontend/build.sh` + +- Build and Deploy: + ``` + cd openreplay/scripts/helm + bash openreplay-cli --install frontend + ``` From 74b1d37e54837ae8f7c8a37b3e3d38f6815c7bd8 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Wed, 23 Jun 2021 11:51:54 +0200 Subject: [PATCH 06/24] feat (backend-http): CACHE_ASSETS env flag + logs on assets error --- backend/Dockerfile | 7 ++----- backend/Dockerfile-all | 1 + backend/pkg/url/assets/css.go | 18 ++++++++++++++++-- backend/pkg/url/assets/url.go | 4 ++-- backend/services/assets/main.go | 2 ++ backend/services/http/assets.go | 18 +++++++++++++++++- backend/services/http/handlers.go | 12 ++++-------- backend/services/http/main.go | 3 +++ 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index af8d4ff71..91702986d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -4,15 +4,11 @@ RUN apk add --no-cache git openssh openssl-dev pkgconf gcc g++ make libc-dev bas WORKDIR /root -COPY go.mod . -COPY go.sum . +COPY . . RUN go mod download -COPY . . - ARG SERVICE_NAME - RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openreplay/backend/services/$SERVICE_NAME FROM alpine @@ -39,6 +35,7 @@ ENV TZ=UTC \ AWS_REGION_WEB=eu-central-1 \ AWS_REGION_IOS=eu-west-1 \ AWS_REGION_ASSETS=eu-central-1 \ + CACHE_ASSETS=true \ ASSETS_SIZE_LIMIT=6291456 diff --git a/backend/Dockerfile-all b/backend/Dockerfile-all index f9e451fb6..d56dcbf23 100644 --- a/backend/Dockerfile-all +++ b/backend/Dockerfile-all @@ -35,6 +35,7 @@ ENV TZ=UTC \ AWS_REGION_WEB=eu-central-1 \ AWS_REGION_IOS=eu-west-1 \ AWS_REGION_ASSETS=eu-central-1 \ + CACHE_ASSETS=true \ ASSETS_SIZE_LIMIT=6291456 RUN mkdir $FS_DIR diff --git a/backend/pkg/url/assets/css.go b/backend/pkg/url/assets/css.go index 728239837..d06d62797 100644 --- a/backend/pkg/url/assets/css.go +++ b/backend/pkg/url/assets/css.go @@ -51,13 +51,27 @@ func ExtractURLsFromCSS(css string) []string { return urls } -func (r *Rewriter) RewriteCSS(sessionID uint64, baseurl string, css string) string { +func rewriteLinks(css string, rewrite func(rawurl string) string) string { for _, idx := range cssUrlsIndex(css) { f := idx[0] t := idx[1] rawurl, q := unquote(css[f:t]) // why exactly quote back? - css = css[:f] + q + r.RewriteURL(sessionID, baseurl, rawurl) + q + css[t:] + css = css[:f] + q + rewrite(rawurl) + q + css[t:] } + return css +} + +func ResolveCSS(baseURL string, css string) string { + css = rewriteLinks(css, func(rawurl string) string { + return ResolveURL(baseURL, rawurl) + }) + return strings.Replace(css, ":hover", ".-asayer-hover", -1) +} + +func (r *Rewriter) RewriteCSS(sessionID uint64, baseurl string, css string) string { + css = rewriteLinks(css, func(rawurl string) string { + return r.RewriteURL(sessionID, baseurl, rawurl) + }) return strings.Replace(css, ":hover", ".-asayer-hover", -1) } diff --git a/backend/pkg/url/assets/url.go b/backend/pkg/url/assets/url.go index 9f6c2c267..4b1572ba7 100644 --- a/backend/pkg/url/assets/url.go +++ b/backend/pkg/url/assets/url.go @@ -50,7 +50,7 @@ func GetFullCachableURL(baseURL string, relativeURL string) (string, bool) { } -const ASAYER_QUERY_START = "ASAYER_QUERY_ESCtRT" +const OPENREPLAY_QUERY_START = "OPENREPLAY_QUERY" func getCachePath(rawurl string) string { u, _ := url.Parse(rawurl) @@ -59,7 +59,7 @@ func getCachePath(rawurl string) string { if (s[len(s) - 1] != '/') { s += "/" } - s += ASAYER_QUERY_START + url.PathEscape(u.RawQuery) + s += OPENREPLAY_QUERY_START + url.PathEscape(u.RawQuery) } return s } diff --git a/backend/services/assets/main.go b/backend/services/assets/main.go index 0193be7bb..da7a92bbe 100644 --- a/backend/services/assets/main.go +++ b/backend/services/assets/main.go @@ -64,6 +64,8 @@ func main() { log.Printf("Caught signal %v: terminating\n", sig) consumer.Close() os.Exit(0) + case err := <-cacher.Errors: + log.Printf("Error while caching: %v", err) case <-tick: cacher.UpdateTimeouts() default: diff --git a/backend/services/http/assets.go b/backend/services/http/assets.go index 637aa5a5b..6a1e57243 100644 --- a/backend/services/http/assets.go +++ b/backend/services/http/assets.go @@ -6,7 +6,7 @@ import ( ) func sendAssetForCache(sessionID uint64, baseURL string, relativeURL string) { - if fullURL, cachable := assets.GetFullCachableURL(baseURL, relativeURL); cachable { + if fullURL, cacheable := assets.GetFullCachableURL(baseURL, relativeURL); cacheable { producer.Produce(topicTrigger, sessionID, messages.Encode(&messages.AssetCache{ URL: fullURL, })) @@ -17,4 +17,20 @@ func sendAssetsForCacheFromCSS(sessionID uint64, baseURL string, css string) { for _, u := range assets.ExtractURLsFromCSS(css) { // TODO: in one shot with rewriting sendAssetForCache(sessionID, baseURL, u) } +} + +func handleURL(sessionID uint64, baseURL string, url string) string { + if cacheAssets { + sendAssetForCache(sessionID, baseURL, url) + return rewriter.RewriteURL(sessionID, baseURL, url) + } + return assets.ResolveURL(baseURL, url) +} + +func handleCSS(sessionID uint64, baseURL string, css string) string { + if cacheAssets { + sendAssetsForCacheFromCSS(sessionID, baseURL, css) + return rewriter.RewriteCSS(sessionID, baseURL, css) + } + return assets.ResolveCSS(baseURL, css) } \ No newline at end of file diff --git a/backend/services/http/handlers.go b/backend/services/http/handlers.go index c17a46286..b3527ac3f 100644 --- a/backend/services/http/handlers.go +++ b/backend/services/http/handlers.go @@ -174,32 +174,28 @@ func pushMessagesSeparatelyHandler(w http.ResponseWriter, r *http.Request) { switch m := msg.(type) { case *SetNodeAttributeURLBased: if m.Name == "src" || m.Name == "href" { - sendAssetForCache(sessionData.ID, m.BaseURL, m.Value) msg = &SetNodeAttribute{ ID: m.ID, Name: m.Name, - Value: rewriter.RewriteURL(sessionData.ID, m.BaseURL, m.Value), + Value: handleURL(sessionData.ID, m.BaseURL, m.Value), } } else if m.Name == "style" { - sendAssetsForCacheFromCSS(sessionData.ID, m.BaseURL, m.Value) msg = &SetNodeAttribute{ ID: m.ID, Name: m.Name, - Value: rewriter.RewriteCSS(sessionData.ID, m.BaseURL, m.Value), + Value: handleCSS(sessionData.ID, m.BaseURL, m.Value), } } case *SetCSSDataURLBased: - sendAssetsForCacheFromCSS(sessionData.ID, m.BaseURL, m.Data) msg = &SetCSSData{ ID: m.ID, - Data: rewriter.RewriteCSS(sessionData.ID, m.BaseURL, m.Data), + Data: handleCSS(sessionData.ID, m.BaseURL, m.Data), } case *CSSInsertRuleURLBased: - sendAssetsForCacheFromCSS(sessionData.ID, m.BaseURL, m.Rule) msg = &CSSInsertRule{ ID: m.ID, Index: m.Index, - Rule: rewriter.RewriteCSS(sessionData.ID, m.BaseURL, m.Rule), + Rule: handleCSS(sessionData.ID, m.BaseURL, m.Rule), } } diff --git a/backend/services/http/main.go b/backend/services/http/main.go index b3e3b6d9d..d978bca80 100644 --- a/backend/services/http/main.go +++ b/backend/services/http/main.go @@ -35,6 +35,7 @@ var topicRaw string var topicTrigger string var topicAnalytics string // var kafkaTopicEvents string +var cacheAssets bool func main() { log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) @@ -52,6 +53,8 @@ func main() { uaParser = uaparser.NewUAParser(env.String("UAPARSER_FILE")) geoIP = geoip.NewGeoIP(env.String("MAXMINDDB_FILE")) flaker = flakeid.NewFlaker(env.WorkerID()) + cacheAssets = env.Bool("CACHE_ASSETS") + HTTP_PORT := env.String("HTTP_PORT") server := &http.Server{ From d13fa0d7e71c3b8122f97217a1d2c462f92dc0d4 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Wed, 23 Jun 2021 15:53:17 +0200 Subject: [PATCH 07/24] fs (tracker): script dir --- tracker/tracker/{checkver.cjs => scripts/checkver.js} | 2 +- tracker/tracker/{ => scripts}/compile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tracker/tracker/{checkver.cjs => scripts/checkver.js} (83%) rename tracker/tracker/{ => scripts}/compile.js (96%) diff --git a/tracker/tracker/checkver.cjs b/tracker/tracker/scripts/checkver.js similarity index 83% rename from tracker/tracker/checkver.cjs rename to tracker/tracker/scripts/checkver.js index 7ffdc5d91..b636857f4 100644 --- a/tracker/tracker/checkver.cjs +++ b/tracker/tracker/scripts/checkver.js @@ -1,5 +1,5 @@ const semver = require("semver"); -const { engines } = require("./package"); +const { engines } = require("../package"); const version = engines.node; if (!semver.satisfies(process.version, version)) { console.error(`Required node version ${version}, got ${process.version}.`) diff --git a/tracker/tracker/compile.js b/tracker/tracker/scripts/compile.js similarity index 96% rename from tracker/tracker/compile.js rename to tracker/tracker/scripts/compile.js index 2b6dfa215..6e7bd43a1 100644 --- a/tracker/tracker/compile.js +++ b/tracker/tracker/scripts/compile.js @@ -1,6 +1,6 @@ import { promises as fs } from 'fs'; import replaceInFiles from 'replace-in-files'; -import packageConfig from './package.json'; +import packageConfig from '../package.json'; async function main() { const webworker = await fs.readFile('build/webworker.js', 'utf8'); From 1699697385655bf6f5f411de8fdbfec18f1823ec Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Wed, 23 Jun 2021 16:07:48 +0200 Subject: [PATCH 08/24] fix (tracker): 3.0.5: wbworker beacon size; handle overflow --- tracker/tracker/package-lock.json | 10 ++-- tracker/tracker/package.json | 8 +-- .../tracker/src/main/modules/performance.ts | 20 ++++--- .../types.ts => messages/webworker.ts} | 4 +- tracker/tracker/src/webworker/index.ts | 56 +++++++++++-------- tracker/tracker/src/webworker/transformer.js | 21 ------- 6 files changed, 59 insertions(+), 60 deletions(-) rename tracker/tracker/src/{webworker/types.ts => messages/webworker.ts} (57%) delete mode 100644 tracker/tracker/src/webworker/transformer.js diff --git a/tracker/tracker/package-lock.json b/tracker/tracker/package-lock.json index e08453206..e478e35c6 100644 --- a/tracker/tracker/package-lock.json +++ b/tracker/tracker/package-lock.json @@ -1,6 +1,6 @@ { - "name": "@asayerio/tracker", - "version": "5.6.5", + "name": "@openreplay/tracker", + "version": "3.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4513,9 +4513,9 @@ "dev": true }, "typescript": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", - "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "dev": true }, "unc-path-regex": { diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 001789f90..4765ac2c5 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.0.4", + "version": "3.0.5", "keywords": [ "logging", "replay" @@ -18,9 +18,9 @@ "clean": "rm -Rf build && rm -Rf lib && rm -Rf cjs", "tsc": "tsc -b src/main && tsc -b src/webworker && tsc --project src/main/tsconfig-cjs.json", "rollup": "rollup --config rollup.config.js", - "compile": "node --experimental-modules --experimental-json-modules compile.js", + "compile": "node --experimental-modules --experimental-json-modules scripts/compile.js", "build": "npm run clean && npm run tsc && npm run rollup && npm run compile", - "prepare": "node checkver.cjs && npm run build" + "prepare": "node scripts/checkver.cjs && npm run build" }, "devDependencies": { "@babel/core": "^7.10.2", @@ -38,7 +38,7 @@ "rollup": "^2.17.0", "rollup-plugin-terser": "^6.1.0", "semver": "^6.3.0", - "typescript": "^3.9.5" + "typescript": "^4.3.4" }, "dependencies": { "error-stack-parser": "^2.0.6" diff --git a/tracker/tracker/src/main/modules/performance.ts b/tracker/tracker/src/main/modules/performance.ts index cefa6044b..2a4edd149 100644 --- a/tracker/tracker/src/main/modules/performance.ts +++ b/tracker/tracker/src/main/modules/performance.ts @@ -2,13 +2,19 @@ import App from '../app'; import { IN_BROWSER } from '../utils'; import { PerformanceTrack } from '../../messages'; -const perf: { - memory: { - jsHeapSizeLimit?: number; - totalJSHeapSize?: number; - usedJSHeapSize?: number; - }; -} = IN_BROWSER && 'memory' in performance ? performance : { memory: {} }; + +type Perf = { + memory: { + totalJSHeapSize?: number, + usedJSHeapSize?: number, + jsHeapSizeLimit?: number, + } +} + +const perf: Perf = IN_BROWSER && 'memory' in performance // works in Chrome only + ? performance as any + : { memory: {} } + export const deviceMemory = IN_BROWSER ? ((navigator as any).deviceMemory || 0) * 1024 : 0; export const jsHeapSizeLimit = perf.memory.jsHeapSizeLimit || 0; diff --git a/tracker/tracker/src/webworker/types.ts b/tracker/tracker/src/messages/webworker.ts similarity index 57% rename from tracker/tracker/src/webworker/types.ts rename to tracker/tracker/src/messages/webworker.ts index 22f55fad1..e5eb8eef7 100644 --- a/tracker/tracker/src/webworker/types.ts +++ b/tracker/tracker/src/messages/webworker.ts @@ -1,6 +1,8 @@ +// TODO: "common" folder instead of "messages". (better file structure) export interface Options { connAttemptCount?: number; connAttemptGap?: number; + beaconSize?: number; } type Settings = { @@ -11,4 +13,4 @@ type Settings = { timeAdjustment?: number; } & Partial; -export type MessageData = null | "stop" | Settings | Array<{ _id: number }>; \ No newline at end of file +export type WorkerMessageData = null | "stop" | Settings | Array<{ _id: number }>; \ No newline at end of file diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 409a44b4e..271c13f2b 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -2,13 +2,15 @@ import { classes, BatchMeta, Timestamp, SetPageVisibility, CreateDocument } from import Message from '../messages/message'; import Writer from '../messages/writer'; -import type { MessageData } from './types'; +import type { WorkerMessageData } from '../messages/webworker'; + -// TODO: what if on message overflows? (maybe one option) -const MAX_BATCH_SIZE = 4 * 1e5; // Max 400kB const SEND_INTERVAL = 20 * 1000; +const BEACON_SIZE_LIMIT = 1e6 // Limit is set in the backend/services/http +let beaconSize = 4 * 1e5; // Default 400kB -const writer: Writer = new Writer(MAX_BATCH_SIZE); + +let writer: Writer = new Writer(beaconSize); let ingestPoint: string = ""; let token: string = ""; @@ -31,9 +33,12 @@ let busy = false; let attemptsCount = 0; let ATTEMPT_TIMEOUT = 8000; let MAX_ATTEMPTS_COUNT = 10; + +// TODO?: exploit https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon function sendBatch(batch: Uint8Array):void { const req = new XMLHttpRequest(); - req.open("POST", ingestPoint + "/v1/web/i"); // TODO opaque request? + // TODO: async=false (3d param) instead of sendQueue array ? + req.open("POST", ingestPoint + "/v1/web/i", false); // TODO opaque request? req.setRequestHeader("Authorization", "Bearer " + token); // req.setRequestHeader("Content-Type", ""); req.onreadystatechange = function() { @@ -41,7 +46,7 @@ function sendBatch(batch: Uint8Array):void { if (this.status == 0) { return; // happens simultaneously with onerror TODO: clear codeflow } - if (this.status >= 400) { + if (this.status >= 400) { // TODO: test workflow. After 400+ it calls /start for some reason reset(); sendQueue.length = 0; if (this.status === 403) { // Unauthorised (Token expired) @@ -69,7 +74,7 @@ function sendBatch(batch: Uint8Array):void { attemptsCount++; setTimeout(() => sendBatch(batch), ATTEMPT_TIMEOUT); } - req.send(batch); + req.send(batch.buffer); } function send(): void { @@ -100,7 +105,7 @@ function hasTimestamp(msg: any): msg is { timestamp: number } { return typeof msg === 'object' && typeof msg.timestamp === 'number'; } -self.onmessage = ({ data }: MessageEvent) => { +self.onmessage = ({ data }: MessageEvent) => { if (data === null) { send(); return; @@ -118,6 +123,7 @@ self.onmessage = ({ data }: MessageEvent) => { timeAdjustment = data.timeAdjustment || timeAdjustment; MAX_ATTEMPTS_COUNT = data.connAttemptCount || MAX_ATTEMPTS_COUNT; ATTEMPT_TIMEOUT = data.connAttemptGap || ATTEMPT_TIMEOUT; + beaconSize = Math.min(BEACON_SIZE_LIMIT, data.beaconSize || beaconSize); if (writer.isEmpty()) { writeBatchMeta(); } @@ -126,7 +132,7 @@ self.onmessage = ({ data }: MessageEvent) => { } return; } - data.forEach((data: any) => { + data.forEach((data) => { const message: Message = new (classes.get(data._id))(); Object.assign(message, data); @@ -140,20 +146,26 @@ self.onmessage = ({ data }: MessageEvent) => { } } - writer.checkpoint(); - nextIndex++; - if (message.encode(writer)) { - isEmpty = false; - } else { + writer.checkpoint(); // TODO: incapsulate in writer + if (!message.encode(writer)) { send(); - if (message.encode(writer)) { - isEmpty = false; - } else { - // MAX_BATCH_SIZE overflow by one message - // TODO: correct handle - nextIndex--; - return; - } + // writer.reset(); // TODO: sematically clear code + if (!message.encode(writer)) { // Try to encode within empty state + // MBTODO: tempWriter for one message? + while (!message.encode(writer)) { + if (beaconSize === BEACON_SIZE_LIMIT) { + console.warn("OpenReplay: beacon size overflow."); + writer.reset(); + writeBatchMeta(); + return + } + beaconSize = Math.min(beaconSize*2, BEACON_SIZE_LIMIT); + writer = new Writer(beaconSize); + writeBatchMeta(); + } + } }; + nextIndex++; // TODO: incapsulate in writer + isEmpty = false; }); }; diff --git a/tracker/tracker/src/webworker/transformer.js b/tracker/tracker/src/webworker/transformer.js deleted file mode 100644 index cf80d681b..000000000 --- a/tracker/tracker/src/webworker/transformer.js +++ /dev/null @@ -1,21 +0,0 @@ -import Message from '../messages/message'; - - - - -class MessageTransformer { - private urlRewriter?: URLRewriter - - constructor() { - - } - - transform(m: Message): Message { - if (m instanceof SetNodeAttribute) { - if (m.name == "src" || m.name == "href") { - sendAssetForCache - } - } - } - -} \ No newline at end of file From f973a4758f02a9a5fc8be647bd854f267ff12a52 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Wed, 23 Jun 2021 16:12:00 +0200 Subject: [PATCH 09/24] fix(tracker): scripts --- tracker/tracker/scripts/{checkver.js => checkver.cjs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tracker/tracker/scripts/{checkver.js => checkver.cjs} (100%) diff --git a/tracker/tracker/scripts/checkver.js b/tracker/tracker/scripts/checkver.cjs similarity index 100% rename from tracker/tracker/scripts/checkver.js rename to tracker/tracker/scripts/checkver.cjs From 1d212bb25fca78d359d1cc6b9fb61cd2d8c08e53 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Wed, 23 Jun 2021 16:17:42 +0200 Subject: [PATCH 10/24] upd(frontend): env tracker version --- frontend/env.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/env.js b/frontend/env.js index 9dca7ccd8..aeb8d1d5f 100644 --- a/frontend/env.js +++ b/frontend/env.js @@ -20,8 +20,18 @@ const oss = { MINIO_USE_SSL: process.env.MINIO_USE_SSL, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - TRACKER_VERSION: '3.0.3', // trackerInfo.version, + TRACKER_VERSION: '3.0.5', // trackerInfo.version, } + +const local = { + ...oss, + API_EDP: 'https://sacha.openreplay.com/api',//'https://do.openreplay.com/api', + ASSETS_HOST: 'https://sacha.openreplay.com/assets',//'https://do.openreplay.com/assets', + SOURCEMAP: false, + PRODUCTION: false, +} + module.exports = { oss, + local, }; \ No newline at end of file From c818e25ff062a70cff46fe639de5def192e6d6fd Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Wed, 23 Jun 2021 21:17:31 +0200 Subject: [PATCH 11/24] Assist (#51) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script Co-authored-by: Rajesh Rajendran --- .github/workflows/utilities.yaml | 64 ++ api/.chalice/config.json | 2 +- api/Dockerfile | 8 - api/chalicelib/blueprints/bp_core.py | 9 +- api/chalicelib/core/assist.py | 42 + api/entrypoint.sh | 3 - api/sourcemaps_reader/.gitignore | 11 - api/sourcemaps_reader/README.md | 15 - api/sourcemaps_reader/package-lock.json | 109 --- api/sourcemaps_reader/package.json | 16 - api/sourcemaps_reader/server.js | 38 - scripts/helm/app/chalice.yaml | 1 + scripts/helm/app/utilities.yaml | 28 + .../nginx-ingress/templates/configmap.yaml | 12 + .../roles/openreplay/templates/utilities.yaml | 17 + utilities/.gitignore | 5 + utilities/Dockerfile | 15 + utilities/build.sh | 30 + utilities/package-lock.json | 779 ++++++++++++++++++ utilities/package.json | 26 + utilities/server.js | 33 + utilities/servers/peerjs-server.js | 64 ++ .../servers/sourcemaps-handler.js | 24 +- utilities/servers/sourcemaps-server.js | 30 + 24 files changed, 1157 insertions(+), 224 deletions(-) create mode 100644 .github/workflows/utilities.yaml create mode 100644 api/chalicelib/core/assist.py delete mode 100644 api/sourcemaps_reader/.gitignore delete mode 100644 api/sourcemaps_reader/README.md delete mode 100644 api/sourcemaps_reader/package-lock.json delete mode 100644 api/sourcemaps_reader/package.json delete mode 100644 api/sourcemaps_reader/server.js create mode 100644 scripts/helm/app/utilities.yaml create mode 100644 scripts/helm/roles/openreplay/templates/utilities.yaml create mode 100644 utilities/.gitignore create mode 100644 utilities/Dockerfile create mode 100644 utilities/build.sh create mode 100644 utilities/package-lock.json create mode 100644 utilities/package.json create mode 100644 utilities/server.js create mode 100644 utilities/servers/peerjs-server.js rename api/sourcemaps_reader/handler.js => utilities/servers/sourcemaps-handler.js (85%) create mode 100644 utilities/servers/sourcemaps-server.js diff --git a/.github/workflows/utilities.yaml b/.github/workflows/utilities.yaml new file mode 100644 index 000000000..dd3278197 --- /dev/null +++ b/.github/workflows/utilities.yaml @@ -0,0 +1,64 @@ +# This action will push the chalice changes to aws +on: + push: + branches: + - dev + paths: + - utilities/** + +name: Build and Deploy Utilities + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Building and Pusing api image + id: build-image + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.sha }} + ENVIRONMENT: staging + run: | + cd utilities + PUSH_IMAGE=1 bash build.sh + - name: Deploy to kubernetes + run: | + cd scripts/helm/ + sed -i "s#minio_access_key.*#minio_access_key: \"${{ secrets.OSS_MINIO_ACCESS_KEY }}\" #g" vars.yaml + sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\" #g" vars.yaml + sed -i "s#domain_name.*#domain_name: \"foss.openreplay.com\" #g" vars.yaml + sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml + sed -i "s/tag:.*/tag: \"$IMAGE_TAG\"/g" app/chalice.yaml + bash kube-install.sh --app utilities + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.sha }} + ENVIRONMENT: staging + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + # IMAGE_TAG: ${{ github.sha }} + # ENVIRONMENT: staging + # diff --git a/api/.chalice/config.json b/api/.chalice/config.json index 7c91eda84..060607b45 100644 --- a/api/.chalice/config.json +++ b/api/.chalice/config.json @@ -30,7 +30,7 @@ "sessions_bucket": "mobs", "sessions_region": "us-east-1", "put_S3_TTL": "20", - "sourcemaps_reader": "http://127.0.0.1:3000/", + "sourcemaps_reader": "http://utilities.app.svc.cluster.local:9000/sourcemaps", "sourcemaps_bucket": "sourcemaps", "js_cache_bucket": "sessions-assets", "async_Token": "", diff --git a/api/Dockerfile b/api/Dockerfile index 84d1b88f5..4dbfe4f7f 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -4,14 +4,6 @@ WORKDIR /work COPY . . RUN pip install -r requirements.txt -t ./vendor --upgrade RUN pip install chalice==1.22.2 -# Installing Nodejs -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \ - apt install -y nodejs && \ - apt remove --purge -y curl && \ - rm -rf /var/lib/apt/lists/* && \ - cd sourcemaps_reader && \ - npm install # Add Tini # Startup daemon diff --git a/api/chalicelib/blueprints/bp_core.py b/api/chalicelib/blueprints/bp_core.py index c12a2f412..83502b637 100644 --- a/api/chalicelib/blueprints/bp_core.py +++ b/api/chalicelib/blueprints/bp_core.py @@ -10,7 +10,8 @@ from chalicelib.core import log_tool_rollbar, sourcemaps, events, sessions_assig log_tool_elasticsearch, log_tool_datadog, \ log_tool_stackdriver, reset_password, sessions_favorite_viewed, \ log_tool_cloudwatch, log_tool_sentry, log_tool_sumologic, log_tools, errors, sessions, \ - log_tool_newrelic, announcements, log_tool_bugsnag, weekly_report, integration_jira_cloud, integration_github + log_tool_newrelic, announcements, log_tool_bugsnag, weekly_report, integration_jira_cloud, integration_github, \ + assist from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import email_helper @@ -875,3 +876,9 @@ def all_issue_types(context): @app.route('/{projectId}/flows', methods=['GET', 'PUT', 'POST', 'DELETE']) def removed_endpoints(projectId=None, context=None): return Response(body={"errors": ["Endpoint no longer available"]}, status_code=410) + + +@app.route('/{projectId}/assist/sessions', methods=['GET']) +def sessions_live(projectId, context): + data = assist.get_live_sessions(projectId) + return {'data': data} diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py new file mode 100644 index 000000000..9f26ea07e --- /dev/null +++ b/api/chalicelib/core/assist.py @@ -0,0 +1,42 @@ +from chalicelib.utils import pg_client, helper +from chalicelib.core import projects +import requests + +SESSION_PROJECTION_COLS = """s.project_id, + s.session_id::text AS session_id, + s.user_uuid, + s.user_id, + s.user_agent, + s.user_os, + s.user_browser, + s.user_device, + s.user_device_type, + s.user_country, + s.start_ts, + s.user_anonymous_id, + s.platform + """ + + +def get_live_sessions(project_id): + project_key = projects.get_project_key(project_id) + connected_peers = requests.get(f"http://127.0.0.1:9000/peers/{project_key}") + if connected_peers.status_code != 200: + print("!! issue with the peer-server") + print(connected_peers.text) + return [] + connected_peers = connected_peers.json().get("data", []) + if len(connected_peers) == 0: + return [] + connected_peers = tuple(connected_peers) + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""\ + SELECT {SESSION_PROJECTION_COLS} + FROM public.sessions AS s + WHERE s.project_id = %(project_id)s + AND session_id IN %(connected_peers)s + AND duration IS NULL;""", + {"project_id": project_id, "connected_peers": connected_peers}) + cur.execute(query) + results = cur.fetchall() + return helper.list_to_camel_case(results) diff --git a/api/entrypoint.sh b/api/entrypoint.sh index 3c3d12fd5..4a8c790c8 100755 --- a/api/entrypoint.sh +++ b/api/entrypoint.sh @@ -1,6 +1,3 @@ #!/bin/bash -cd sourcemaps_reader -nohup node server.js &> /tmp/sourcemaps_reader.log & -cd .. python env_handler.py chalice local --no-autoreload --host 0.0.0.0 --stage ${ENTERPRISE_BUILD} diff --git a/api/sourcemaps_reader/.gitignore b/api/sourcemaps_reader/.gitignore deleted file mode 100644 index f642462b1..000000000 --- a/api/sourcemaps_reader/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# package directories -node_modules -jspm_packages - -# Serverless directories -.serverless/*.zip - - -node_modules/ -.idea -test.js \ No newline at end of file diff --git a/api/sourcemaps_reader/README.md b/api/sourcemaps_reader/README.md deleted file mode 100644 index d543da338..000000000 --- a/api/sourcemaps_reader/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# sourcemap-reader -Source Map Reader - -# For SAAS: -to run local; put your test values in handler then run `node handler.js` -to deploy `sls deploy --stage [staging|prod|dev]` - -# Requirements: -- nodeJS 12 or greater - -# Install for OS: -``` -npm install -node server.js -``` \ No newline at end of file diff --git a/api/sourcemaps_reader/package-lock.json b/api/sourcemaps_reader/package-lock.json deleted file mode 100644 index 719a55bb3..000000000 --- a/api/sourcemaps_reader/package-lock.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "name": "sourcemap-reader", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "aws-sdk": { - "version": "2.654.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.654.0.tgz", - "integrity": "sha512-RAx/SJ74zAqBW1wyRxiHNflmrv50i35pu8kPxfMIJ418TJzIMt+LKgn55rTJgyUdUzKi+MC9XMY4n7IDtwj3HA==", - "requires": { - "buffer": "4.9.1", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - }, - "dependencies": { - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - } - } - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - } - } -} diff --git a/api/sourcemaps_reader/package.json b/api/sourcemaps_reader/package.json deleted file mode 100644 index ed169326e..000000000 --- a/api/sourcemaps_reader/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "sourcemap-reader", - "version": "1.0.0", - "description": "", - "main": "handler.js", - "dependencies": { - "aws-sdk": "^2.654.0", - "source-map": "^0.7.3" - }, - "devDependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Kraiem taha yassine ", - "license": "ISC" -} diff --git a/api/sourcemaps_reader/server.js b/api/sourcemaps_reader/server.js deleted file mode 100644 index 2a1c4dcf6..000000000 --- a/api/sourcemaps_reader/server.js +++ /dev/null @@ -1,38 +0,0 @@ -const http = require('http'); -const handler = require('./handler'); -const hostname = '127.0.0.1'; -const port = 3000; - -const server = http.createServer((req, res) => { - if (req.method === 'POST') { - let data = ''; - req.on('data', chunk => { - data += chunk; - }); - req.on('end', function () { - data = JSON.parse(data); - console.log("Starting parser for: " + data.key); - // process.env = {...process.env, ...data.bucket_config}; - handler.sourcemapReader(data) - .then((results) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(results)); - }) - .catch((e) => { - console.error("Something went wrong"); - console.error(e); - res.statusCode(500); - res.end(e); - }); - }) - } else { - res.statusCode = 405; - res.setHeader('Content-Type', 'text/plain'); - res.end('Method Not Allowed'); - } -}); - -server.listen(port, hostname, () => { - console.log(`Server running at http://${hostname}:${port}/`); -}); \ No newline at end of file diff --git a/scripts/helm/app/chalice.yaml b/scripts/helm/app/chalice.yaml index a53846acd..e0c409669 100644 --- a/scripts/helm/app/chalice.yaml +++ b/scripts/helm/app/chalice.yaml @@ -61,6 +61,7 @@ env: S3_HOST: 'http://minio.db.svc.cluster.local:9000' S3_KEY: minios3AccessKeyS3cr3t S3_SECRET: m1n10s3CretK3yPassw0rd + sourcemaps_reader: 'http://utilities.app.svc.cluster.local:9000/sourcemaps' # Enable logging for python app # Ref: https://stackoverflow.com/questions/43969743/logs-in-kubernetes-pod-not-showing-up PYTHONUNBUFFERED: '0' diff --git a/scripts/helm/app/utilities.yaml b/scripts/helm/app/utilities.yaml new file mode 100644 index 000000000..e229fae2c --- /dev/null +++ b/scripts/helm/app/utilities.yaml @@ -0,0 +1,28 @@ +namespace: app +image: + repository: rg.fr-par.scw.cloud/foss + name: utilities + pullPolicy: IfNotPresent + tag: latest + +imagePullSecrets: + - name: aws-registry + +service: + type: ClusterIP + port: 9000 + +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 1m + memory: 1Mi +env: + AWS_DEFAULT_REGION: us-east-1 + # Override with your https://domain_name + # eg: https://openreplay.mycompany.com + S3_HOST: 'http://minio.db.svc.cluster.local:9000' + S3_KEY: minios3AccessKeyS3cr3t + S3_SECRET: m1n10s3CretK3yPassw0rd \ No newline at end of file diff --git a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml index fad59aa89..dd61f33cc 100644 --- a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml +++ b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml @@ -68,6 +68,18 @@ data: proxy_set_header Host $host; proxy_pass http://chalice-openreplay.app.svc.cluster.local:8000; } + location /assist/ { + rewrite ^/assist/(.*) /$1 break; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_pass http://utilities-openreplay.app.svc.cluster.local:9000; + } + location /assist/peers/ { + deny all; + return 404; + } location /assets/ { rewrite ^/assets/(.*) /sessions-assets/$1 break; proxy_http_version 1.1; diff --git a/scripts/helm/roles/openreplay/templates/utilities.yaml b/scripts/helm/roles/openreplay/templates/utilities.yaml new file mode 100644 index 000000000..3ae1efca8 --- /dev/null +++ b/scripts/helm/roles/openreplay/templates/utilities.yaml @@ -0,0 +1,17 @@ +{% if docker_registry_url is defined and docker_registry_url %} +image: + repository: {{ docker_registry_url }} + tag: {{ image_tag }} +{% endif %} + +{% if not (docker_registry_username is defined and docker_registry_username and docker_registry_password is defined and docker_registry_password) %} +imagePullSecrets: [] +{% endif %} +env: + S3_KEY: "{{ minio_access_key }}" + S3_SECRET: "{{ minio_secret_key }}" + S3_HOST: "https://{{ domain_name }}" + jwt_secret: "{{ jwt_secret_key }}" +{% if env is defined and env.chalice is defined and env.chalice%} + {{ env.chalice | to_nice_yaml | trim | indent(2) }} +{% endif %} diff --git a/utilities/.gitignore b/utilities/.gitignore new file mode 100644 index 000000000..fffbe974b --- /dev/null +++ b/utilities/.gitignore @@ -0,0 +1,5 @@ +.idea +node_modules +npm-debug.log +.cache +test.html \ No newline at end of file diff --git a/utilities/Dockerfile b/utilities/Dockerfile new file mode 100644 index 000000000..3867ed94a --- /dev/null +++ b/utilities/Dockerfile @@ -0,0 +1,15 @@ +FROM node:12.22-stretch +WORKDIR /work +COPY . . +RUN npm install + + +# Add Tini +# Startup daemon +ENV TINI_VERSION v0.19.0 +ARG envarg +ENV ENTERPRISE_BUILD ${envarg} +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini +RUN chmod +x /tini +ENTRYPOINT ["/tini", "--"] +CMD npm start \ No newline at end of file diff --git a/utilities/build.sh b/utilities/build.sh new file mode 100644 index 000000000..fcd2d1198 --- /dev/null +++ b/utilities/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Script to build api module +# flags to accept: +# Default will be OSS build. + +# Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh + +git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +check_prereq() { + which docker || { + echo "Docker not installed, please install docker." + exit=1 + } + [[ exit -eq 1 ]] && exit 1 +} + +function build_api(){ + # Copy enterprise code + [[ $1 == "ee" ]] && { + cp -rf ../ee/utilities/* ./ + } + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/chalice:${git_sha1} . + [[ $PUSH_IMAGE -eq 1 ]] && { + docker push ${DOCKER_REPO:-'local'}/utilities:${git_sha1} + } +} + +check_prereq +build_api $1 diff --git a/utilities/package-lock.json b/utilities/package-lock.json new file mode 100644 index 000000000..3de98ac06 --- /dev/null +++ b/utilities/package-lock.json @@ -0,0 +1,779 @@ +{ + "name": "utilities_server", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.34", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", + "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" + }, + "@types/express": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", + "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "@types/node": { + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", + "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" + }, + "@types/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-8mbDgtc8xpxDDem5Gwj76stBDJX35KQ3YBoayxlqUQcL5BZUthiqP/VQ4PQnLHqM4PmlbyO74t98eJpURO+gPA==", + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "aws-sdk": { + "version": "2.932.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.932.0.tgz", + "integrity": "sha512-U6MWUtFD0npWa+ReVEgm0fCIM0fMOYahFp14GLv8fC+BWOTvh5Iwt/gF8NrLomx42bBjA1Abaw6yhmiaSJDQHQ==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" + }, + "mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "requires": { + "mime-db": "1.48.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "peer": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/peer/-/peer-0.6.1.tgz", + "integrity": "sha512-zPJSPoZvo+83sPJNrW8o93QTktx7dKk67965RRDDNAIelWw1ZwE6ZmmhsvRrdNRlK0knQb3rR8GBdZlbWzCYJw==", + "requires": { + "@types/cors": "^2.8.6", + "@types/express": "^4.17.3", + "@types/ws": "^7.2.3", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "express": "^4.17.1", + "uuid": "^3.4.0", + "ws": "^7.2.3", + "yargs": "^15.3.1" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "ws": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", + "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/utilities/package.json b/utilities/package.json new file mode 100644 index 000000000..d0cfcdbcc --- /dev/null +++ b/utilities/package.json @@ -0,0 +1,26 @@ +{ + "name": "utilities_server", + "version": "1.0.0", + "description": "assist server to get live sessions & sourcemaps reader to get stack trace", + "main": "peerjs-server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/openreplay/openreplay.git" + }, + "author": "KRAIEM Taha Yassine ", + "license": "MIT", + "bugs": { + "url": "https://github.com/openreplay/openreplay/issues" + }, + "homepage": "https://github.com/openreplay/openreplay#readme", + "dependencies": { + "aws-sdk": "^2.654.0", + "express": "^4.17.1", + "peer": "^0.6.1", + "source-map": "^0.7.3" + } +} diff --git a/utilities/server.js b/utilities/server.js new file mode 100644 index 000000000..81a751d03 --- /dev/null +++ b/utilities/server.js @@ -0,0 +1,33 @@ +var sourcemapsReaderServer = require('./servers/sourcemaps-server'); +var {peerRouter, peerConnection, peerDisconnect} = require('./servers/peerjs-server'); +var express = require('express'); +const {ExpressPeerServer} = require('peer'); + +const HOST = '0.0.0.0'; +const PORT = 9000; + + +var app = express(); +app.use((req, res, next) => { + console.log(new Date().toTimeString(), req.method, req.originalUrl); + next(); +}); + +app.use('/sourcemaps', sourcemapsReaderServer); +app.use('/assist', peerRouter); + +const server = app.listen(PORT, HOST, () => { + console.log(`App listening on http://${HOST}:${PORT}`); + console.log('Press Ctrl+C to quit.'); +}); +const peerServer = ExpressPeerServer(server, { + debug: true, + path: '/', + proxied: true, + allow_discovery: true +}); +peerServer.on('connection', peerConnection); +peerServer.on('disconnect', peerDisconnect); +app.use('/', peerServer); +app.enable('trust proxy'); +module.exports = server; diff --git a/utilities/servers/peerjs-server.js b/utilities/servers/peerjs-server.js new file mode 100644 index 000000000..df08e8407 --- /dev/null +++ b/utilities/servers/peerjs-server.js @@ -0,0 +1,64 @@ +var express = require('express'); +var peerRouter = express.Router(); + + +const extractPeerId = (peerId) => { + let splited = peerId.split("-"); + if (splited.length !== 2) { + console.error(`cannot split peerId: ${client.id}`); + return {}; + } + return {projectKey: splited[0], sessionId: splited[1]}; +}; +const connectedPeers = {}; + +const peerConnection = (client) => { + console.log(`initiating ${client.id}`); + const {projectKey, sessionId} = extractPeerId(client.id); + if (projectKey === undefined || sessionId === undefined) { + return; + } + connectedPeers[projectKey] = connectedPeers[projectKey] || []; + if (connectedPeers[projectKey].indexOf(sessionId) === -1) { + console.log(`new connexion ${client.id}`); + connectedPeers[projectKey].push(sessionId); + } else { + console.log(`reconnecting peer ${client.id}`); + } + + +}; +const peerDisconnect = (client) => { + console.log(`disconnect ${client.id}`); + const {projectKey, sessionId} = extractPeerId(client.id); + if (projectKey === undefined || sessionId === undefined) { + return; + } + const i = (connectedPeers[projectKey] || []).indexOf(sessionId); + if (i === -1) { + console.log(`session not found ${client.id}`); + } else { + connectedPeers[projectKey].splice(i, 1); + } +} + + +peerRouter.get('/peers', function (req, res) { + console.log("looking for all available sessions"); + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": connectedPeers})); +}); +peerRouter.get('/peers/:projectKey', function (req, res) { + console.log(`looking for available sessions for ${req.params.projectKey}`); + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": connectedPeers[req.params.projectKey] || []})); +}); + + +module.exports = { + peerRouter, + peerConnection, + peerDisconnect +}; \ No newline at end of file diff --git a/api/sourcemaps_reader/handler.js b/utilities/servers/sourcemaps-handler.js similarity index 85% rename from api/sourcemaps_reader/handler.js rename to utilities/servers/sourcemaps-handler.js index 117808cae..98c2186dc 100644 --- a/api/sourcemaps_reader/handler.js +++ b/utilities/servers/sourcemaps-handler.js @@ -1,7 +1,7 @@ 'use strict'; const sourceMap = require('source-map'); const AWS = require('aws-sdk'); -const sourceMapVersion = require('./package.json').dependencies["source-map"]; +const sourceMapVersion = require('../package.json').dependencies["source-map"]; const URL = require('url'); const getVersion = version => version.replace(/[\^\$\=\~]/, ""); @@ -88,24 +88,4 @@ module.exports.sourcemapReader = async event => { }); }); }); -}; - - -// let v = { -// 'key': '1725/99f96f044fa7e941dbb15d7d68b20549', -// 'positions': [{'line': 1, 'column': 943}], -// 'padding': 5, -// 'bucket': 'asayer-sourcemaps' -// }; -// let v = { -// 'key': '1/65d8d3866bb8c92f3db612cb330f270c', -// 'positions': [{'line': 1, 'column': 0}], -// 'padding': 5, -// 'bucket': 'asayer-sourcemaps-staging' -// }; -// module.exports.sourcemapReader(v).then((r) => { -// // console.log(r); -// const fs = require('fs'); -// let data = JSON.stringify(r); -// fs.writeFileSync('results.json', data); -// }); \ No newline at end of file +}; \ No newline at end of file diff --git a/utilities/servers/sourcemaps-server.js b/utilities/servers/sourcemaps-server.js new file mode 100644 index 000000000..a1dd501a0 --- /dev/null +++ b/utilities/servers/sourcemaps-server.js @@ -0,0 +1,30 @@ +var express = require('express'); +var handler = require('./sourcemaps-handler'); +var router = express.Router(); + +router.post('/', (req, res) => { + let data = ''; + req.on('data', chunk => { + data += chunk; + }); + req.on('end', function () { + data = JSON.parse(data); + console.log("Starting parser for: " + data.key); + // process.env = {...process.env, ...data.bucket_config}; + handler.sourcemapReader(data) + .then((results) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(results)); + }) + .catch((e) => { + console.error("Something went wrong"); + console.error(e); + res.statusCode(500); + res.end(e); + }); + }) + +}); + +module.exports = router; \ No newline at end of file From ff57efd48fcc6127cece22511320015ecc7e04eb Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Wed, 23 Jun 2021 21:23:21 +0200 Subject: [PATCH 12/24] Assist (#52) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name Co-authored-by: Rajesh Rajendran --- utilities/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/build.sh b/utilities/build.sh index fcd2d1198..27a0ea08f 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -20,7 +20,7 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/utilities/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/chalice:${git_sha1} . + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:${git_sha1} . [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/utilities:${git_sha1} } From 48f1978657b2bad212efec1d0a8f7e887efc707a Mon Sep 17 00:00:00 2001 From: Rajesh Rajendran Date: Thu, 24 Jun 2021 10:49:22 +0530 Subject: [PATCH 13/24] fix(ci): fix image tag --- .github/workflows/api-ee.yaml | 2 +- .github/workflows/api.yaml | 2 +- .github/workflows/utilities.yaml | 4 ++-- .github/workflows/workers.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index 58559aa8b..cfd3ca07e 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -47,7 +47,7 @@ jobs: sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.EE_MINIO_SECRET_KEY }}\" #g" vars.yaml sed -i "s#domain_name.*#domain_name: \"foss.openreplay.com\" #g" vars.yaml sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml - sed -i "s/tag:.*/tag: \"$IMAGE_TAG\"/g" vars.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml bash kube-install.sh --app chalice env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index ca8a5dc8c..0d467295f 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -47,7 +47,7 @@ jobs: sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\" #g" vars.yaml sed -i "s#domain_name.*#domain_name: \"foss.openreplay.com\" #g" vars.yaml sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml - sed -i "s/tag:.*/tag: \"$IMAGE_TAG\"/g" app/chalice.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml bash kube-install.sh --app chalice env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} diff --git a/.github/workflows/utilities.yaml b/.github/workflows/utilities.yaml index dd3278197..603c09126 100644 --- a/.github/workflows/utilities.yaml +++ b/.github/workflows/utilities.yaml @@ -1,4 +1,4 @@ -# This action will push the chalice changes to aws +# This action will push the utilities changes to aws on: push: branches: @@ -47,7 +47,7 @@ jobs: sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\" #g" vars.yaml sed -i "s#domain_name.*#domain_name: \"foss.openreplay.com\" #g" vars.yaml sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml - sed -i "s/tag:.*/tag: \"$IMAGE_TAG\"/g" app/chalice.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml bash kube-install.sh --app utilities env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index f9316a7a8..37d87bfb6 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -70,7 +70,7 @@ jobs: sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml for image in $(cat ../../backend/images_to_build.txt); do - sed -i "s/tag:.*/tag: \"$IMAGE_TAG\"/g" app/${image}.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml # Deploy command bash kube-install.sh --app $image done From 5160a32f25fc42f5a83a5e0914ebd62d6af1abea Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Thu, 24 Jun 2021 12:59:29 +0200 Subject: [PATCH 14/24] Assist (#54) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest Co-authored-by: Rajesh Rajendran --- utilities/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utilities/build.sh b/utilities/build.sh index 27a0ea08f..99be144fe 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -23,6 +23,8 @@ function build_api(){ docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:${git_sha1} . [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/utilities:${git_sha1} + docker tag ${DOCKER_REPO:-'local'}/utilities:${git_sha1} ${DOCKER_REPO:-'local'}/utilities:latest + docker push ${DOCKER_REPO:-'local'}/utilities:latest } } From 5056cd6fceb8033dbff4742ee1ee91e8a97eda16 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 25 Jun 2021 11:30:51 +0200 Subject: [PATCH 15/24] feat(nginx): removed /peers block --- .../helm/nginx-ingress/nginx-ingress/templates/configmap.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml index dd61f33cc..fff5ac641 100644 --- a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml +++ b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml @@ -76,10 +76,6 @@ data: proxy_set_header Host $host; proxy_pass http://utilities-openreplay.app.svc.cluster.local:9000; } - location /assist/peers/ { - deny all; - return 404; - } location /assets/ { rewrite ^/assets/(.*) /sessions-assets/$1 break; proxy_http_version 1.1; From 171dca5eaacc06d9883e6d05afb154fcbdfde132 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 25 Jun 2021 12:24:05 +0200 Subject: [PATCH 16/24] Assist (#56) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest Co-authored-by: Rajesh Rajendran --- api/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/build.sh b/api/build.sh index af75324b0..3f1504342 100644 --- a/api/build.sh +++ b/api/build.sh @@ -27,6 +27,8 @@ function build_api(){ docker build -f ./Dockerfile --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/chalice:${git_sha1} . [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/chalice:${git_sha1} + docker tag ${DOCKER_REPO:-'local'}/chalice:${git_sha1} ${DOCKER_REPO:-'local'}/chalice:latest + docker push ${DOCKER_REPO:-'local'}/chalice:latest } } From d4d62323dd2ff255a0ae8b0a03e26d0457923757 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 25 Jun 2021 12:39:42 +0200 Subject: [PATCH 17/24] Assist (#57) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host Co-authored-by: Rajesh Rajendran --- api/.chalice/config.json | 1 + api/chalicelib/core/assist.py | 3 ++- scripts/helm/app/chalice.yaml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/api/.chalice/config.json b/api/.chalice/config.json index 060607b45..42382be52 100644 --- a/api/.chalice/config.json +++ b/api/.chalice/config.json @@ -33,6 +33,7 @@ "sourcemaps_reader": "http://utilities.app.svc.cluster.local:9000/sourcemaps", "sourcemaps_bucket": "sourcemaps", "js_cache_bucket": "sessions-assets", + "peers": "http://utilities.app.svc.cluster.local:9000/peers", "async_Token": "", "EMAIL_HOST": "", "EMAIL_PORT": "587", diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 9f26ea07e..485d1d624 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -1,6 +1,7 @@ from chalicelib.utils import pg_client, helper from chalicelib.core import projects import requests +from chalicelib.utils.helper import environ SESSION_PROJECTION_COLS = """s.project_id, s.session_id::text AS session_id, @@ -20,7 +21,7 @@ SESSION_PROJECTION_COLS = """s.project_id, def get_live_sessions(project_id): project_key = projects.get_project_key(project_id) - connected_peers = requests.get(f"http://127.0.0.1:9000/peers/{project_key}") + connected_peers = requests.get(environ["peers"] + f"/{project_key}") if connected_peers.status_code != 200: print("!! issue with the peer-server") print(connected_peers.text) diff --git a/scripts/helm/app/chalice.yaml b/scripts/helm/app/chalice.yaml index e0c409669..328ad17e3 100644 --- a/scripts/helm/app/chalice.yaml +++ b/scripts/helm/app/chalice.yaml @@ -62,6 +62,7 @@ env: S3_KEY: minios3AccessKeyS3cr3t S3_SECRET: m1n10s3CretK3yPassw0rd sourcemaps_reader: 'http://utilities.app.svc.cluster.local:9000/sourcemaps' + peers: 'http://utilities.app.svc.cluster.local:9000/peers' # Enable logging for python app # Ref: https://stackoverflow.com/questions/43969743/logs-in-kubernetes-pod-not-showing-up PYTHONUNBUFFERED: '0' From b0fea9bebf39cdc0bda7c02e304b123a202fe676 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 25 Jun 2021 14:52:15 +0200 Subject: [PATCH 18/24] Assist (#58) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL Co-authored-by: Rajesh Rajendran --- api/.chalice/config.json | 4 ++-- scripts/helm/app/chalice.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/.chalice/config.json b/api/.chalice/config.json index 42382be52..05e9c8442 100644 --- a/api/.chalice/config.json +++ b/api/.chalice/config.json @@ -30,10 +30,10 @@ "sessions_bucket": "mobs", "sessions_region": "us-east-1", "put_S3_TTL": "20", - "sourcemaps_reader": "http://utilities.app.svc.cluster.local:9000/sourcemaps", + "sourcemaps_reader": "http://utilities-openreplay.app.svc.cluster.local:9000/sourcemaps", "sourcemaps_bucket": "sourcemaps", "js_cache_bucket": "sessions-assets", - "peers": "http://utilities.app.svc.cluster.local:9000/peers", + "peers": "http://utilities-openreplay.app.svc.cluster.local:9000/peers", "async_Token": "", "EMAIL_HOST": "", "EMAIL_PORT": "587", diff --git a/scripts/helm/app/chalice.yaml b/scripts/helm/app/chalice.yaml index 328ad17e3..ece9a511a 100644 --- a/scripts/helm/app/chalice.yaml +++ b/scripts/helm/app/chalice.yaml @@ -61,8 +61,8 @@ env: S3_HOST: 'http://minio.db.svc.cluster.local:9000' S3_KEY: minios3AccessKeyS3cr3t S3_SECRET: m1n10s3CretK3yPassw0rd - sourcemaps_reader: 'http://utilities.app.svc.cluster.local:9000/sourcemaps' - peers: 'http://utilities.app.svc.cluster.local:9000/peers' + sourcemaps_reader: 'http://utilities-openreplay.app.svc.cluster.local:9000/sourcemaps' + peers: 'http://utilities-openreplay.app.svc.cluster.local:9000/peers' # Enable logging for python app # Ref: https://stackoverflow.com/questions/43969743/logs-in-kubernetes-pod-not-showing-up PYTHONUNBUFFERED: '0' From c5b69cf5054bd32d1ef989ae28d534690d130d0f Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 28 Jun 2021 12:16:17 +0200 Subject: [PATCH 19/24] Assist (#59) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL * feat(api): assist logs Co-authored-by: Rajesh Rajendran --- api/chalicelib/core/assist.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 485d1d624..0caf0b627 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -21,12 +21,16 @@ SESSION_PROJECTION_COLS = """s.project_id, def get_live_sessions(project_id): project_key = projects.get_project_key(project_id) + print("requesting the list of connected peers") connected_peers = requests.get(environ["peers"] + f"/{project_key}") if connected_peers.status_code != 200: print("!! issue with the peer-server") print(connected_peers.text) return [] + print("response") + print(connected_peers.json()) connected_peers = connected_peers.json().get("data", []) + if len(connected_peers) == 0: return [] connected_peers = tuple(connected_peers) @@ -38,6 +42,7 @@ def get_live_sessions(project_id): AND session_id IN %(connected_peers)s AND duration IS NULL;""", {"project_id": project_id, "connected_peers": connected_peers}) + print(query) cur.execute(query) results = cur.fetchall() return helper.list_to_camel_case(results) From 83abde63d35058163b16d5c0a6d7fd4c2e17cd44 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 28 Jun 2021 16:17:47 +0200 Subject: [PATCH 20/24] Assist (#60) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL * feat(api): assist logs * feat(api): assist: fixed peerjs URL Co-authored-by: Rajesh Rajendran --- api/.chalice/config.json | 2 +- ee/api/.chalice/config.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/.chalice/config.json b/api/.chalice/config.json index 05e9c8442..48358a422 100644 --- a/api/.chalice/config.json +++ b/api/.chalice/config.json @@ -33,7 +33,7 @@ "sourcemaps_reader": "http://utilities-openreplay.app.svc.cluster.local:9000/sourcemaps", "sourcemaps_bucket": "sourcemaps", "js_cache_bucket": "sessions-assets", - "peers": "http://utilities-openreplay.app.svc.cluster.local:9000/peers", + "peers": "http://utilities-openreplay.app.svc.cluster.local:9000/assist/peers", "async_Token": "", "EMAIL_HOST": "", "EMAIL_PORT": "587", diff --git a/ee/api/.chalice/config.json b/ee/api/.chalice/config.json index 6b4d2cc20..b00e87d4a 100644 --- a/ee/api/.chalice/config.json +++ b/ee/api/.chalice/config.json @@ -33,8 +33,9 @@ "sessions_bucket": "mobs", "sessions_region": "us-east-1", "put_S3_TTL": "20", - "sourcemaps_reader": "http://127.0.0.1:3000/", + "sourcemaps_reader": "http://utilities-openreplay.app.svc.cluster.local:9000/sourcemaps", "sourcemaps_bucket": "sourcemaps", + "peers": "http://utilities-openreplay.app.svc.cluster.local:9000/assist/peers", "js_cache_bucket": "sessions-assets", "async_Token": "", "EMAIL_HOST": "", From 3f1228e57623f51e0a29b329ae4ae8cbc655fb81 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 28 Jun 2021 16:26:36 +0200 Subject: [PATCH 21/24] Assist (#61) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL * feat(api): assist logs * feat(api): assist: fixed peerjs URL * feat(api): log peers URL Co-authored-by: Rajesh Rajendran --- api/chalicelib/core/assist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 0caf0b627..7f50647e9 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -22,6 +22,7 @@ SESSION_PROJECTION_COLS = """s.project_id, def get_live_sessions(project_id): project_key = projects.get_project_key(project_id) print("requesting the list of connected peers") + print(environ["peers"] + f"/{project_key}") connected_peers = requests.get(environ["peers"] + f"/{project_key}") if connected_peers.status_code != 200: print("!! issue with the peer-server") From a8102be92d56e414bdc540400ae10ccb03a557a9 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 28 Jun 2021 16:36:23 +0200 Subject: [PATCH 22/24] Assist (#62) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL * feat(api): assist logs * feat(api): assist: fixed peerjs URL * feat(api): log peers URL * feat(api): assit: get all durations, even if it is not null Co-authored-by: Rajesh Rajendran --- api/chalicelib/core/assist.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 7f50647e9..50e1b0655 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -21,15 +21,11 @@ SESSION_PROJECTION_COLS = """s.project_id, def get_live_sessions(project_id): project_key = projects.get_project_key(project_id) - print("requesting the list of connected peers") - print(environ["peers"] + f"/{project_key}") connected_peers = requests.get(environ["peers"] + f"/{project_key}") if connected_peers.status_code != 200: print("!! issue with the peer-server") print(connected_peers.text) return [] - print("response") - print(connected_peers.json()) connected_peers = connected_peers.json().get("data", []) if len(connected_peers) == 0: @@ -40,10 +36,8 @@ def get_live_sessions(project_id): SELECT {SESSION_PROJECTION_COLS} FROM public.sessions AS s WHERE s.project_id = %(project_id)s - AND session_id IN %(connected_peers)s - AND duration IS NULL;""", + AND session_id IN %(connected_peers)s;""", {"project_id": project_id, "connected_peers": connected_peers}) - print(query) cur.execute(query) results = cur.fetchall() return helper.list_to_camel_case(results) From 502307b84128c67be144f5b443921eecb955d5bb Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 28 Jun 2021 16:49:50 +0200 Subject: [PATCH 23/24] Assist (#63) * feat(api): assist peerJS server * feat(api): install assist_server dependencies and start it with the API * feat(api): assist: list live sessions * feat(nginx): expose assist_server and block peers listing * feat(api): merged sourcemaps reader and assist-server feat(api): change image definition feat(api): changed service start command feat(utilities): created full server & image definition feat(nginx): reset chalice configuration * feat(utilities): utilities.yaml * feat(nginx): utilities URL * feat(utilities): utilities template * feat(ci): Adding utilities GH action. Signed-off-by: Rajesh Rajendran * feat(utilities): build script * feat(utilities): build script fix image name * feat(utilities): tag and push image as latest * feat(api): tag and push image as latest * feat(api): extract peers host * feat(api): fixed utilities URL * feat(api): assist logs * feat(api): assist: fixed peerjs URL * feat(api): log peers URL * feat(api): assit: get all durations, even if it is not null * feat(api): assit: include peerId in response Co-authored-by: Rajesh Rajendran --- api/chalicelib/core/assist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 50e1b0655..be5431990 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -33,11 +33,11 @@ def get_live_sessions(project_id): connected_peers = tuple(connected_peers) with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""\ - SELECT {SESSION_PROJECTION_COLS} + SELECT {SESSION_PROJECTION_COLS}, %(project_key)s||'-'|| session_id AS peer_id FROM public.sessions AS s WHERE s.project_id = %(project_id)s AND session_id IN %(connected_peers)s;""", - {"project_id": project_id, "connected_peers": connected_peers}) + {"project_id": project_id, "connected_peers": connected_peers, "project_key":project_key}) cur.execute(query) results = cur.fetchall() return helper.list_to_camel_case(results) From d1f44f871c0b4303435010b00d9421019cd0863c Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Mon, 28 Jun 2021 19:48:00 +0200 Subject: [PATCH 24/24] fix(sourcemap-uploader): rename conflicting argument --- sourcemap-uploader/cli.js | 3 ++- sourcemap-uploader/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sourcemap-uploader/cli.js b/sourcemap-uploader/cli.js index f465d1711..fa284f1a4 100755 --- a/sourcemap-uploader/cli.js +++ b/sourcemap-uploader/cli.js @@ -21,7 +21,8 @@ parser.addArgument(['-p', '-i', '--project-key'], { // -i is depricated parser.addArgument(['-s', '--server'], { help: 'OpenReplay API server URL for upload', }); -parser.addArgument(['-v', '--verbose'], { +// Should be verbose, but conflicting on npm compilation into bin +parser.addArgument(['-l', '--logs'], { help: 'Log requests information', action: 'storeTrue', }); diff --git a/sourcemap-uploader/package.json b/sourcemap-uploader/package.json index 76b9470bb..439ef44d8 100644 --- a/sourcemap-uploader/package.json +++ b/sourcemap-uploader/package.json @@ -1,6 +1,6 @@ { "name": "@openreplay/sourcemap-uploader", - "version": "3.0.3", + "version": "3.0.4", "description": "NPM module to upload your JS sourcemaps files to OpenReplay", "bin": "cli.js", "main": "index.js",