Email Notification Add-on Installer

⚠️ Only Seeq Administrators can run this installer to completion, since only Administrators can install Add-ons.

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

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.

At the end, if you have encountered no errors: * in the folder you’ve specified using the path_to_notifications_folder, you should find all files listed as source_files below * the Tools tab in Workbench should have a tool grouping named Add-ons * there should be a tool named Email Notification Scheduler among the Add-ons.

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.

See the Add-on Tools KB article for further details of Add-on tools.

Install Requirements

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.

pip install ipyvuetify

Installation Folder

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.

path_to_notifications_folder = 'Email Notifications'

How to handle existing files

If the following parameter is changed to True, any existing files in the target folder will be overwritten. You may want to do this if you wish to discard changes to the installed notebooks or if you wish to upgrade the notebooks with the versions found in this folder (e.g., after upgrading the Data Lab server).

overwrite_existing_files = False

How to handle existing versions of Scheduler in Add-on Tools

If the following parameter is True, any already-installed tools with the same name as shown in the name field of the tool_with_permissions dictionary below will be removed before the new tool is installed. By default, the name of the scheduler is Email Notification Scheduler. Be careful! If you change this parameter to True, you will be replacing the existing version of the Add-on Tool with the version found at the path_to_notifications_folder in this project for all users of the Add-on Tool. You may want to change the name field in the tool_with_permissions dictionary instead.

remove_existing_versions = False

Tool Configuration

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.

import urllib.parse as urlparse

target_path_encoded = urlparse.quote(path_to_notifications_folder)
project_id = spy.utils.get_data_lab_project_id()
project_url = spy.utils.get_data_lab_project_url()
notebook_name = 'Email Notification Scheduler.ipynb'
query_parameters = '?workbookId={workbookId}&worksheetId={worksheetId}&workstepId={workstepId}&seeqVersion={seeqVersion}'
install_url = f'{project_url}/addon/{path_to_notifications_folder}/{notebook_name}/{query_parameters}'
tool_with_permissions = {
    'name': 'Email Notification Scheduler',
    'description': 'Data Lab Notebook-based Email Notification Scheduling tool',
    'iconClass': 'fa fa-envelope',
    'targetUrl': install_url,
    'linkType': 'window',
    'windowDetails': 'toolbar=0,location=0,scrollbars=1,statusbar=0,menubar=0,resizable=1,height=700,width=600',
    'sortKey' : 'e',
    'reuseWindow': 'false', # but not relevant, since linkType is 'tab'
    'permissions': {
        'groups': [],
        'users': [spy.user.email]
    }
}
print('The following parameters will be used to define the add-on:\n')
tool_with_permissions

⚠️ Advanced Configuration and Automated Installation Beyond This Point

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.

import os
import requests
import shutil
from datetime import datetime, timezone
from pathlib import Path
from seeq import sdk
try:
    spy_version = seeq.__version__
except:
    spy_version = spy.__version__
print(f'Seeq PyPI package version: {spy_version}')

Check whether the required source files and target folder exist

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.

home_path = os.environ['HOME']
all_required_paths_exist = False
source_files = [
    'Email Notification Scheduler.ipynb',
    'Email Notifier.ipynb',
    'Email Unsubscriber.ipynb',
    'Seeq Data Lab.jpg'
]
source_paths = [
    f'{os.getcwd()}/{source_file}' for source_file in source_files
]
target_folder_path = Path(home_path, path_to_notifications_folder)
os.makedirs(target_folder_path, exist_ok=True)
target_folder_exists = os.path.exists(target_folder_path)
all_source_paths_exist = all(iter([os.path.exists(source_path) for source_path in source_paths]))
status_message = ''
if not all_source_paths_exist:
    files_string = "\n".join(source_paths)
    status_message += f'Not all source files exist.  Check for the presence of the following files ' \
                      f'in the folder that contains this notebook:\n{source_files}'
if not target_folder_exists:
    status_message += f'Target folder not found.  Add a folder at {path_to_notifications_folder} relative to ' \
                      f'the root of the Data Lab Project'
if status_message:
    print(status_message)
else:
    print('All required paths exist. Installation may proceed.')
target_paths = []
if all_source_paths_exist:
    existing_files_not_overwritten = False
    for source_path in source_paths:
        source_file = source_path.split('/').pop()
        target_path = Path(home_path, path_to_notifications_folder, source_file)
        target_paths.append(target_path)
        if overwrite_existing_files or not target_path.exists():
            shutil.copyfile(source_path, target_path)
        else:
            existing_files_not_overwritten = True
    if existing_files_not_overwritten:
        print(f'Warning!  One or more files were not overwritten.  Change overwrite_existing_files to True '
              f'or delete the files in the target folder to ensure the latest versions.')
    all_target_files_exist = all(iter([os.path.exists(target_path) for target_path in target_paths]))
    print(f'{"All" if all_target_files_exist else "Not all"} target files exist. '
          f'Installation may{" " if all_target_files_exist else " not "}proceed.')
else:
    all_target_files_exist = False
    print('Please check results of previous step')

Configuration update and tool installation

The following cell enables the Add-on Tools and ScheduledNotebooks features and adds the Email Notifications Scheduler to the Add-on Tools.

# Adapted from the Notebook Add-on Tool Management UI-TEST.ipynb, available at
# https://seeq.atlassian.net/wiki/spaces/SQ/pages/961675391/Add-on+Tools
def create_add_on_tool(tool_with_permissions):
    # Create add-on tool
    tool = tool_with_permissions.copy()
    tool.pop("permissions")
    tool_id = sdk.SystemApi(spy.client).create_add_on_tool(body = tool).id

    items_api = sdk.ItemsApi(spy.client)

    # assign group permissions to add-on tool and data lab project
    groups = tool_with_permissions["permissions"]["groups"]
    for group_name in groups:
        group = sdk.UserGroupsApi(spy.client).get_user_groups(name_search=group_name)
        if group:
            ace_input = { 'identityId': group.items[0].id, 'permissions': { 'read': True } }
            # Add permissions to add-on tool item
            items_api.add_access_control_entry(id=tool_id, body=ace_input)
            # Add permissions to data lab project if target URL references one
            ace_input['permissions']['write'] = True # Data lab project also needs write permission
            items_api.add_access_control_entry(id=project_id, body=ace_input)

    # assign user permissions to add-on tool and data lab project
    users = tool_with_permissions["permissions"]["users"]
    for user_name in users:
        user = sdk.UsersApi(spy.client).get_users(username_search=user_name)
        if user:
            ace_input = { 'identityId': user.users[0].id, 'permissions': { 'read': True } }
            items_api.add_access_control_entry(id=tool_id, body=ace_input)
            # Add permissions to data lab project if target URL references one
            ace_input['permissions']['write'] = True # Data lab project also needs write permission
            items_api.add_access_control_entry(id=project_id, body=ace_input)


system_api = sdk.SystemApi(spy.client)
if all_target_files_exist:
    if not spy.user.is_admin:
        raise RuntimeError('Only Administrators can install Add-on Tools')

    if int(spy_version.split('.')[0]) >= 54:
        configuration_output = system_api.get_configuration_options(limit=5000)
    else:
        configuration_output = system_api.get_configuration_options()

    add_on_tools_already_enabled = next((option.value for option in configuration_output.configuration_options
                                         if option.path == 'Features/AddOnTools/Enabled'), False)
    scheduled_notebooks_already_enabled = next((option.value for option in configuration_output.configuration_options
                                         if option.path == 'Features/DataLab/ScheduledNotebooks/Enabled'), False)

    configuration_options_update = []
    if not add_on_tools_already_enabled:
        configuration_options_update.append(
            sdk.ConfigurationOptionInputV1(
                note = f'Set to true by Email Notifications Installer user {spy.user.email} {datetime.now(timezone.utc)}',
                path = 'Features/AddOnTools/Enabled',
                value = True
            )
        )
    if not scheduled_notebooks_already_enabled:
        configuration_options_update.append(
            sdk.ConfigurationOptionInputV1(
                note = f'Set to true by Email Notifications Installer user {spy.user.email} {datetime.now(timezone.utc)}',
                path = 'Features/DataLab/ScheduledNotebooks/Enabled',
                value = True
            )
        )

    if configuration_options_update:
        config_options = sdk.ConfigurationInputV1(configuration_options = configuration_options_update)
        system_api.set_configuration_options(body=config_options)

    existing_tools_output = system_api.get_add_on_tools()
    existing_tools = [add_on_tool for add_on_tool in existing_tools_output.add_on_tools
                              if add_on_tool.name == tool_with_permissions['name']]
    if len(existing_tools) > 0:
        if not remove_existing_versions:
            raise RuntimeError(f'One or more tools exist with name {tool_with_permissions["name"]}, '
                               f'and remove_existing_versions is False; Cannot create add-on tool')
        else:
            # Delete existing tools
            for existing_tool in existing_tools:
                system_api.delete_add_on_tool(id=existing_tool.id)
            print(f'Removed {len(existing_tools)} existing tools with name {tool_with_permissions["name"]}')

    # Create new tool
    create_add_on_tool(tool_with_permissions)
    print(f'Success!  Check Workbench for the {tool_with_permissions["name"]} tool in the Add-on Tools collection')
else:
    print('Not all target files exist; cannot complete installation.')