{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5627e45a",
   "metadata": {},
   "source": [
    "# Email Notification Add-on Installer\n",
    "⚠️ Only Seeq Administrators can run this installer to completion, since only Administrators can install Add-ons.\n",
    "\n",
    "If you are using Seeq's SaaS product, instead of this Data Lab-based notification mechanism, it is recommended that you utilize the built in notification capabilities in Seeq R60+ as described in the following Seeq Knowledge Base article: https://telemetry.seeq.com/support-link/kb/latest/cloud/notifications-on-conditions\n",
    "\n",
    "This notebook will walk you through the steps needed to install the Email Notification Scheduler as a Data Lab Tool in Workbench. If you are fine with the defaults and have never installed the scheduler before, you should be able to leave everything as is, running all cells in order.\n",
    "\n",
    "At the end, if you have encountered no errors:\n",
    "* in the folder you've specified using the `path_to_notifications_folder`, you should find all files listed as `source_files` below \n",
    "* the Tools tab in Workbench should have a tool grouping named `Add-ons`\n",
    "* there should be a tool named `Email Notification Scheduler` among the `Add-ons`.\n",
    "\n",
    "There is ONE MORE STEP that must be done to complete installation - after running all steps in this installer, one must complete the SMTP server and account configuration in `Email Notifier.ipynb` within the target folder (`Email Notifications` if `path_to_notifications_folder` is not changed below).  Please note that anyone that has access to this project will be able to see the account information provided there.  If using the gmail SMTP server, it is preferred to use an app password rather than the actual password for the account, and the port should be set to 587 to use STARTTLS.  You can find out more about gmail app passwords in the [Google Account Help](https://support.google.com/accounts/answer/185833?hl=en#zippy=%2Cwhy-you-may-need-an-app-password).\n",
    "\n",
    "See the [Add-on Tools KB article](https://telemetry.seeq.com/support-link/wiki/spaces/KB/pages/961675391) for further details of Add-on tools."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31bf1a36",
   "metadata": {},
   "source": [
    "### Install Requirements\n",
    "\n",
    "Some of the examples in the Email Notification Scheduler and Email Unsubscriber notebooks use the ipyvuetify library. We make sure it is installed in the below cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "afef948c",
   "metadata": {},
   "outputs": [],
   "source": [
    "pip install ipyvuetify"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b62259b",
   "metadata": {},
   "source": [
    "### Installation Folder\n",
    "\n",
    "The path to the Notifications folder is set in the next cell.  It should be specified relative to the root of the project and should use forward slashes ( / ) for path separators.  The folder should be created before proceeding with installation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "befe857d",
   "metadata": {},
   "outputs": [],
   "source": [
    "path_to_notifications_folder = 'Email Notifications'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e15878a",
   "metadata": {},
   "source": [
    "### How to handle existing files\n",
    "If the following parameter is changed to `True`, any existing files in the target folder will be overwritten.  You\n",
    "may want to do this if you wish to discard changes to the installed notebooks or if you wish to upgrade the notebooks\n",
    " with the versions found in this folder (e.g., after upgrading the Data Lab server)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4be8ba82",
   "metadata": {},
   "outputs": [],
   "source": [
    "overwrite_existing_files = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30fefd04",
   "metadata": {},
   "source": [
    "### How to handle existing versions of Scheduler in Add-on Tools\n",
    "If the following parameter is `True`, any already-installed tools with the same name as shown in the `name` field of\n",
    "the `tool_with_permissions` dictionary below will be removed before the new tool is installed.  By default, the\n",
    "`name` of the scheduler is `Email Notification Scheduler`.  Be careful!  If you change this parameter to `True`, you\n",
    "will be replacing the existing version of the Add-on Tool with the version found at the\n",
    "`path_to_notifications_folder` in this project for _all users_ of the Add-on Tool.  You may want to change the `name`\n",
    " field in the `tool_with_permissions` dictionary instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efe6be3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "remove_existing_versions = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8c27cb0",
   "metadata": {},
   "source": [
    "### Tool Configuration\n",
    "Check the output of the following cell to confirm the desired configuration for the Add-on Tool.  The resulting JSON object will be used to create or update the existing tool."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a0eaef5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.parse as urlparse\n",
    "\n",
    "target_path_encoded = urlparse.quote(path_to_notifications_folder)\n",
    "project_id = spy.utils.get_data_lab_project_id()\n",
    "project_url = spy.utils.get_data_lab_project_url()\n",
    "notebook_name = 'Email Notification Scheduler.ipynb'\n",
    "query_parameters = '?workbookId={workbookId}&worksheetId={worksheetId}&workstepId={workstepId}&seeqVersion={seeqVersion}'\n",
    "install_url = f'{project_url}/addon/{path_to_notifications_folder}/{notebook_name}/{query_parameters}'\n",
    "tool_with_permissions = {\n",
    "    'name': 'Email Notification Scheduler',\n",
    "    'description': 'Data Lab Notebook-based Email Notification Scheduling tool',\n",
    "    'iconClass': 'fa fa-envelope',\n",
    "    'targetUrl': install_url,\n",
    "    'linkType': 'window',\n",
    "    'windowDetails': 'toolbar=0,location=0,scrollbars=1,statusbar=0,menubar=0,resizable=1,height=700,width=600',\n",
    "    'sortKey' : 'e',\n",
    "    'reuseWindow': 'false', # but not relevant, since linkType is 'tab'\n",
    "    'permissions': {\n",
    "        'groups': [],\n",
    "        'users': [spy.user.email]\n",
    "    }\n",
    "}\n",
    "print('The following parameters will be used to define the add-on:\\n')\n",
    "tool_with_permissions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ee52e66",
   "metadata": {},
   "source": [
    "## ⚠️ Advanced Configuration and Automated Installation Beyond This Point\n",
    "If you are just running this notebook to install or update the Email Notifications Scheduler in a typical fashion, you can just run the remaining cells as is, checking the output of each to confirm the expected results.  Contact support if any problems are encountered."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe06909b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import requests\n",
    "import shutil\n",
    "from datetime import datetime, timezone\n",
    "from pathlib import Path\n",
    "from seeq import sdk\n",
    "try:\n",
    "    spy_version = seeq.__version__\n",
    "except:\n",
    "    spy_version = spy.__version__\n",
    "print(f'Seeq PyPI package version: {spy_version}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d52c3831",
   "metadata": {},
   "source": [
    "### Check whether the required source files and target folder exist\n",
    "The source files should be in the same folder as this installer.  The target folder for the installation should already exist by the time this installer is run."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4502791",
   "metadata": {},
   "outputs": [],
   "source": [
    "home_path = os.environ['HOME']\n",
    "all_required_paths_exist = False\n",
    "source_files = [\n",
    "    'Email Notification Scheduler.ipynb',\n",
    "    'Email Notifier.ipynb',\n",
    "    'Email Unsubscriber.ipynb',\n",
    "    'Seeq Data Lab.jpg'\n",
    "]\n",
    "source_paths = [\n",
    "    f'{os.getcwd()}/{source_file}' for source_file in source_files\n",
    "]\n",
    "target_folder_path = Path(home_path, path_to_notifications_folder)\n",
    "os.makedirs(target_folder_path, exist_ok=True)\n",
    "target_folder_exists = os.path.exists(target_folder_path)\n",
    "all_source_paths_exist = all(iter([os.path.exists(source_path) for source_path in source_paths]))\n",
    "status_message = ''\n",
    "if not all_source_paths_exist:\n",
    "    files_string = \"\\n\".join(source_paths)\n",
    "    status_message += f'Not all source files exist.  Check for the presence of the following files ' \\\n",
    "                      f'in the folder that contains this notebook:\\n{source_files}'\n",
    "if not target_folder_exists:\n",
    "    status_message += f'Target folder not found.  Add a folder at {path_to_notifications_folder} relative to ' \\\n",
    "                      f'the root of the Data Lab Project'\n",
    "if status_message:\n",
    "    print(status_message)\n",
    "else:\n",
    "    print('All required paths exist. Installation may proceed.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ea87a15",
   "metadata": {},
   "outputs": [],
   "source": [
    "target_paths = []\n",
    "if all_source_paths_exist:\n",
    "    existing_files_not_overwritten = False\n",
    "    for source_path in source_paths:\n",
    "        source_file = source_path.split('/').pop()\n",
    "        target_path = Path(home_path, path_to_notifications_folder, source_file)\n",
    "        target_paths.append(target_path)\n",
    "        if overwrite_existing_files or not target_path.exists():\n",
    "            shutil.copyfile(source_path, target_path)\n",
    "        else:\n",
    "            existing_files_not_overwritten = True\n",
    "    if existing_files_not_overwritten:\n",
    "        print(f'Warning!  One or more files were not overwritten.  Change overwrite_existing_files to True '\n",
    "              f'or delete the files in the target folder to ensure the latest versions.')\n",
    "    all_target_files_exist = all(iter([os.path.exists(target_path) for target_path in target_paths]))\n",
    "    print(f'{\"All\" if all_target_files_exist else \"Not all\"} target files exist. '\n",
    "          f'Installation may{\" \" if all_target_files_exist else \" not \"}proceed.')\n",
    "else:\n",
    "    all_target_files_exist = False\n",
    "    print('Please check results of previous step')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e258fd12",
   "metadata": {},
   "source": [
    "### Configuration update and tool installation\n",
    "The following cell enables the Add-on Tools and ScheduledNotebooks features and adds the Email Notifications Scheduler to the Add-on Tools."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "467b97c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Adapted from the Notebook Add-on Tool Management UI-TEST.ipynb, available at\n",
    "# https://seeq.atlassian.net/wiki/spaces/SQ/pages/961675391/Add-on+Tools\n",
    "def create_add_on_tool(tool_with_permissions):\n",
    "    # Create add-on tool\n",
    "    tool = tool_with_permissions.copy()\n",
    "    tool.pop(\"permissions\")\n",
    "    tool_id = sdk.SystemApi(spy.client).create_add_on_tool(body = tool).id\n",
    "        \n",
    "    items_api = sdk.ItemsApi(spy.client)\n",
    "    \n",
    "    # assign group permissions to add-on tool and data lab project\n",
    "    groups = tool_with_permissions[\"permissions\"][\"groups\"]\n",
    "    for group_name in groups:\n",
    "        group = sdk.UserGroupsApi(spy.client).get_user_groups(name_search=group_name)\n",
    "        if group:\n",
    "            ace_input = { 'identityId': group.items[0].id, 'permissions': { 'read': True } }\n",
    "            # Add permissions to add-on tool item\n",
    "            items_api.add_access_control_entry(id=tool_id, body=ace_input)\n",
    "            # Add permissions to data lab project if target URL references one\n",
    "            ace_input['permissions']['write'] = True # Data lab project also needs write permission\n",
    "            items_api.add_access_control_entry(id=project_id, body=ace_input)\n",
    "\n",
    "    # assign user permissions to add-on tool and data lab project\n",
    "    users = tool_with_permissions[\"permissions\"][\"users\"]\n",
    "    for user_name in users:\n",
    "        user = sdk.UsersApi(spy.client).get_users(username_search=user_name)\n",
    "        if user:\n",
    "            ace_input = { 'identityId': user.users[0].id, 'permissions': { 'read': True } }\n",
    "            items_api.add_access_control_entry(id=tool_id, body=ace_input)\n",
    "            # Add permissions to data lab project if target URL references one\n",
    "            ace_input['permissions']['write'] = True # Data lab project also needs write permission\n",
    "            items_api.add_access_control_entry(id=project_id, body=ace_input)\n",
    "\n",
    "            \n",
    "system_api = sdk.SystemApi(spy.client)\n",
    "if all_target_files_exist:\n",
    "    if not spy.user.is_admin:\n",
    "        raise RuntimeError('Only Administrators can install Add-on Tools')\n",
    "\n",
    "    if int(spy_version.split('.')[0]) >= 54:\n",
    "        configuration_output = system_api.get_configuration_options(limit=5000)\n",
    "    else:\n",
    "        configuration_output = system_api.get_configuration_options()\n",
    "\n",
    "    add_on_tools_already_enabled = next((option.value for option in configuration_output.configuration_options\n",
    "                                         if option.path == 'Features/AddOnTools/Enabled'), False)\n",
    "    scheduled_notebooks_already_enabled = next((option.value for option in configuration_output.configuration_options\n",
    "                                         if option.path == 'Features/DataLab/ScheduledNotebooks/Enabled'), False)\n",
    "\n",
    "    configuration_options_update = []\n",
    "    if not add_on_tools_already_enabled:\n",
    "        configuration_options_update.append(\n",
    "            sdk.ConfigurationOptionInputV1(\n",
    "                note = f'Set to true by Email Notifications Installer user {spy.user.email} {datetime.now(timezone.utc)}',\n",
    "                path = 'Features/AddOnTools/Enabled',\n",
    "                value = True\n",
    "            )\n",
    "        )\n",
    "    if not scheduled_notebooks_already_enabled:\n",
    "        configuration_options_update.append(\n",
    "            sdk.ConfigurationOptionInputV1(\n",
    "                note = f'Set to true by Email Notifications Installer user {spy.user.email} {datetime.now(timezone.utc)}',\n",
    "                path = 'Features/DataLab/ScheduledNotebooks/Enabled',\n",
    "                value = True\n",
    "            )\n",
    "        )\n",
    "        \n",
    "    if configuration_options_update:\n",
    "        config_options = sdk.ConfigurationInputV1(configuration_options = configuration_options_update)\n",
    "        system_api.set_configuration_options(body=config_options)\n",
    "        \n",
    "    existing_tools_output = system_api.get_add_on_tools()\n",
    "    existing_tools = [add_on_tool for add_on_tool in existing_tools_output.add_on_tools\n",
    "                              if add_on_tool.name == tool_with_permissions['name']]\n",
    "    if len(existing_tools) > 0:\n",
    "        if not remove_existing_versions:\n",
    "            raise RuntimeError(f'One or more tools exist with name {tool_with_permissions[\"name\"]}, '\n",
    "                               f'and remove_existing_versions is False; Cannot create add-on tool')\n",
    "        else:\n",
    "            # Delete existing tools\n",
    "            for existing_tool in existing_tools:\n",
    "                system_api.delete_add_on_tool(id=existing_tool.id)\n",
    "            print(f'Removed {len(existing_tools)} existing tools with name {tool_with_permissions[\"name\"]}')\n",
    "\n",
    "    # Create new tool\n",
    "    create_add_on_tool(tool_with_permissions)\n",
    "    print(f'Success!  Check Workbench for the {tool_with_permissions[\"name\"]} tool in the Add-on Tools collection')\n",
    "else:\n",
    "    print('Not all target files exist; cannot complete installation.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df04eb9a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.11",
   "language": "python",
   "name": "python311"
  },
  "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.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}