Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LIBRARIES-2434 [Analytics Python CLI E2E Testing Tool] #511

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ build
.vscode/
.idea/
.python-version
.venv
.DS_Store
4 changes: 4 additions & 0 deletions e2e-test/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
APP_NAME='e2e-test'
DEBUG_MODE = False
SEND_EVENTS = True
LOG_FORMAT='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
95 changes: 95 additions & 0 deletions e2e-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

# Analytics Python CLI

This tool is created for the purpose of E2E Testing.

## Dependencies

| Module | Version |
|--|--|
| python | 3.9 |
| click | 8.1.8 |
| python-dotenv | 1.0.1 |
| python-dateutil | 2.8.2 |
| requests | 2.32.3 |
| PyJWT | 2.10.1 |
| backoff | 2.2.1 |

## Installation

1. Change the working directory
```bash
$ cd e2e-test
```
2. Create a virtual environment inside the working directory
```bash
$ python3 -m venv .venv
```
3. Enable the virtual environment
```bash
$ source .venv/bin/activate
```
4. Install dependencies
```bash
$ pip install -r requirements.txt
```
5. Install the script as a module
```bash
$ pip install --editable .
```

## Usage Examples with Sample Payloads

| Command Option | Type | Description | Required |
|--|--|--|--|
| --writeKey | String | Segment Write Key | Yes |
| --apiHost | String | Custom Host | No |
| --payload | JSON | Event Payload | Yes |

### Example: Passing Multiple Events as JSON Array

```bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --apiHost='' --payload='"[{\"anonymousId\":\"507f191e810c89729de960ea\",\"channel\":\"browser\",\"context\":{\"app\":\"ecommerce\"},\"integrations\":{\"All\":false,\"Mixpanel\":true,\"Salesforce\":true},\"messageId\":\"022ty90c-bbac-11e4-8dfc-aa07a5b093q8\",\"traits\":{\"name\":\"Clark Kent\",\"email\":\"[email protected]\",\"plan\":\"premium\",\"logins\":5,\"address\":{\"street\":\"6th St\",\"city\":\"San Francisco\",\"state\":\"CA\",\"postalCode\":\"94103\",\"country\":\"USA\"}},\"type\":\"identify\",\"userId\":\"AiUGstSDIg\",\"version\":\"2.0\"},{\"messageId\":\"122bb9ui-bbac-11e4-8dfc-aa07z5b098ip\",\"userId\":\"AiUGstSDIg\",\"type\":\"track\",\"event\":\"Course Clicked\",\"context\":{\"page\":{\"path\":\"/academy/\",\"referrer\":\"\",\"search\":\"\",\"title\":\"Analytics Academy\",\"url\":\"https://segment.com/academy/\"}},\"integrations\":{},\"properties\":{\"title\":\"Intro to Analytics\"}}]"'
```

### Example: Passing Individual Events

#### 1. Identify

``` bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c89729de960ea\",\"channel\":\"browser\",\"context\":{\"app\":\"ecommerce\"},\"integrations\":{\"All\":false,\"Mixpanel\":true,\"Salesforce\":true},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07a5b093q8\",\"traits\":{\"name\":\"Clark Kent\",\"email\":\"[email protected]\",\"plan\":\"premium\",\"logins\":5,\"address\":{\"street\":\"6th St\",\"city\":\"San Francisco\",\"state\":\"CA\",\"postalCode\":\"94103\",\"country\":\"USA\"}},\"type\":\"identify\",\"userId\":\"97980cfea0062\",\"version\":\"2.0\"}"'
```

#### 2. Track

``` bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"messageId\":\"122bb90c-bbac-11e4-8dfc-aa07z5b098ip\",\"userId\":\"AiUGstSDIg\",\"type\":\"track\",\"event\":\"Course Clicked\",\"context\":{\"page\":{\"path\":\"/academy/\",\"referrer\":\"\",\"search\":\"\",\"title\":\"Analytics Academy\",\"url\":\"https://segment.com/academy/\"}},\"integrations\":{},\"properties\":{\"title\":\"Intro to Analytics\"}}"'
```

#### 3. Page

``` bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de860ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e8-8dfc-aa07a5b090ol\",\"name\":\"Home\",\"properties\":{\"title\":\"Welcome | Initech\",\"url\":\"http://www.example.com\"},\"type\":\"page\",\"userId\":\"97980cfea0067\"}"'
```

#### 4. Screen

``` bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de860ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e8-8dfc-aa07a5b090ol\",\"name\":\"Registration\",\"properties\":{\"title\":\"Welcome | Initech\",\"url\":\"http://www.example.com\"},\"type\":\"Screen\",\"userId\":\"97980cfea0067\"}"'
```

#### 5. Alias

``` bash
$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de800ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07u5b093lk\",\"previousId\":\"12345-239239-239239-23923\",\"type\":\"alias\",\"userId\":\"507f191e81\",\"version\":\"1.9\"}"'
```

#### 6. Group

``` bash
% e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de800ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07u5b093lk\",\"previousId\":\"12345-239239-239239-23923\",\"type\":\"alias\",\"userId\":\"507f191e81\",\"version\":\"1.9\"}"'
```

## Configuration Options

A few configuration options are available in the ```.env``` file.
Empty file added e2e-test/__init__.py
Empty file.
1 change: 1 addition & 0 deletions e2e-test/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 6 additions & 0 deletions e2e-test/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
click==8.1.8
python-dotenv==1.0.1
python-dateutil==2.8.2
requests==2.32.3
PyJWT==2.10.1
backoff==2.2.1
16 changes: 16 additions & 0 deletions e2e-test/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from setuptools import setup, find_packages

setup(
name='e2e-test',
version='0.1.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
'click', 'python-dotenv', 'python-dateutil', 'requests', 'PyJWT', 'backoff'
],
entry_points={
'console_scripts': [
'e2e-test:run = src.cli:run',
],
},
)
Empty file added e2e-test/src/__init__.py
Empty file.
102 changes: 102 additions & 0 deletions e2e-test/src/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import click
from dotenv import load_dotenv
import os
import sys
import logging
import json
from collections.abc import Iterable

load_dotenv()

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

import segment.analytics as analytics # noqa: E402 (ignore autopep8)


@click.command()
@click.option('--writeKey', type=str, help='Segment write key')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd also like to have an --apihost option

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@click.option('--apiHost', type=str, help='Custom host')
@click.option('--payload', type=str, help='A JSON string that specifies the event payload.')
def run(writekey, payload, apihost=None):
analytics.write_key = writekey

if apihost is not None:
analytics.host = apihost # Set custom host

analytics.debug = os.getenv('DEBUG_MODE')
analytics.send = os.getenv('SEND_EVENTS')
logger = log_config()

try:
# Decode the JSON payload
decodedJson = json.loads(payload)
dataObject = json.loads(decodedJson)

# To ensure for loop do not raise exception when individual JSON is passed
# we are converting the payload to a List having a single Dictionary as its item

if not isinstance(dataObject, Iterable): # Check if dataObject is non-iterable
dataObject = [dataObject]
elif isinstance(dataObject, dict): # Check if dataObject is a dictionary
dataObject = [dataObject]

# Iterate over each item in the payload JSON
for data in dataObject:

specType = data.get('type') if data.get('type') is not None else None
messageId = data.get('messageId') if data.get('messageId') is not None else None
userId = data.get('userId') if data.get('userId') is not None else ''
eventName = data.get('event') if data.get('event') is not None else None
traits = data.get('traits') if data.get('traits') is not None else None
properties = data.get('properties') if data.get('properties') is not None else None
context = data.get('context') if data.get('context') is not None else None
integrations = data.get('integrations') if data.get('integrations') is not None else None
groupId = data.get('groupId') if data.get('groupId') is not None else None
pageOrScreenName = data.get('name') if data.get('name') is not None else None
pageOrScreenCategory = data.get('category') if data.get('category') is not None else None
timestamp = data.get('timestamp') if data.get('timestamp') is not None else None
anonymousId = data.get('anonymousId') if data.get('anonymousId') is not None else ''
previousId = data.get('previousId') if data.get('previousId') is not None else None

if specType == 'identify':
analytics.identify(userId, traits, context, timestamp, anonymousId, integrations, messageId)
elif specType == 'track':
analytics.track(userId, eventName, properties, context, timestamp, anonymousId, integrations, messageId)
elif specType == 'page':
analytics.page(userId, pageOrScreenCategory, pageOrScreenName, properties,
context, timestamp, anonymousId, integrations, messageId)
elif specType == 'screen':
analytics.screen(userId, pageOrScreenCategory, pageOrScreenName, properties,
context, timestamp, anonymousId, integrations, messageId)
elif specType == 'alias':
analytics.alias(previousId, userId, context, timestamp, integrations, messageId)
elif specType == 'group':
analytics.group(userId, groupId, traits, context, timestamp, anonymousId, integrations, messageId)
else:
raise Exception
except Exception as e:
logger.exception(e)
finally:
analytics.flush()


def log_config():
# Create a logger object
logger = logging.getLogger(os.getenv('APP_NAME'))
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)

# Define the log message format
formatter = logging.Formatter(os.getenv('LOG_FORMAT'))
handler.setFormatter(formatter)

# Attach the handler to the logger
logger.addHandler(handler)

return logger


if __name__ == '__main__':
run()