From 74f157837229ead20e6ca8cb5c8602300f02b614 Mon Sep 17 00:00:00 2001 From: Michael Gummelt Date: Wed, 29 Apr 2015 11:33:53 -0700 Subject: [PATCH] analytics identity --- cli/dcoscli/analytics.py | 52 +++++++++++++++++--- cli/dcoscli/config/main.py | 12 ++++- cli/dcoscli/constants.py | 2 +- cli/tests/data/analytics/dcos_reporting.toml | 2 +- cli/tests/integrations/cli/common.py | 27 ++++++++++ cli/tests/integrations/cli/test_analytics.py | 52 ++++++++++++++------ 6 files changed, 121 insertions(+), 26 deletions(-) diff --git a/cli/dcoscli/analytics.py b/cli/dcoscli/analytics.py index db76555..d4791db 100644 --- a/cli/dcoscli/analytics.py +++ b/cli/dcoscli/analytics.py @@ -50,25 +50,63 @@ def wait_and_track(subproc): return exit_code -def _send_segment_event(event, properties): +def _segment_track(event, conf, properties): """ - Send a segment event + Send a segment.io 'track' event :param event: name of event :type event: string + :param conf: dcos config file + :type conf: Toml :param properties: event properties :type properties: dict :rtype: None """ - data = {'anonymousId': session_id, - 'event': event, + data = {'event': event, 'properties': properties} + if 'core.email' in conf: + data['userId'] = conf['core.email'] + else: + data['anonymousId'] = session_id + + _segment_request('track', data) + + +def segment_identify(conf): + """ + Send a segment.io 'identify' event + + :param conf: dcos config file + :type conf: Toml + :rtype: None + """ + + if 'core.email' in conf: + data = {'userId': conf.get('core.email')} + else: + data = {'anonymousId': session_id} + + _segment_request('identify', data) + + +def _segment_request(path, data): + """ + Send a segment.io HTTP request + + :param path: URL path + :type path: str + :param data: json POST data + :type data: dict + :rtype: None + """ + key = SEGMENT_IO_WRITE_KEY_PROD if _is_prod() else \ SEGMENT_IO_WRITE_KEY_DEV + try: - requests.post(SEGMENT_URL, + requests.post('{}/{}'.format(SEGMENT_URL, path), json=data, auth=HTTPBasicAuth(key, ''), timeout=1) @@ -135,7 +173,7 @@ def _segment_track_cli(pool, conf): """ props = _base_properties(conf) - pool.submit(_send_segment_event, SEGMENT_IO_CLI_EVENT, props) + pool.submit(_segment_track, SEGMENT_IO_CLI_EVENT, conf, props) def _segment_track_err(pool, conf, err, exit_code): @@ -156,7 +194,7 @@ def _segment_track_err(pool, conf, err, exit_code): props = _base_properties(conf) props['err'] = err props['exit_code'] = exit_code - pool.submit(_send_segment_event, SEGMENT_IO_CLI_ERROR_EVENT, props) + pool.submit(_segment_track, SEGMENT_IO_CLI_ERROR_EVENT, conf, props) def _rollbar_track_err(conf, err, exit_code): diff --git a/cli/dcoscli/config/main.py b/cli/dcoscli/config/main.py index 81bd27d..055fd92 100644 --- a/cli/dcoscli/config/main.py +++ b/cli/dcoscli/config/main.py @@ -30,8 +30,9 @@ import docopt import pkg_resources import six import toml -from dcos.api import (cmds, config, constants, emitting, errors, jsonitem, - options, subcommand, util) +from dcos.api import (cmds, config, constants, emitting, errors, http, + jsonitem, options, subcommand, util) +from dcoscli.analytics import segment_identify emitter = emitting.FlatEmitter() @@ -46,6 +47,8 @@ def main(): __doc__, version='dcos-config version {}'.format(dcoscli.version)) + http.silence_requests_warnings() + returncode, err = cmds.execute(_cmds(), args) if err is not None: emitter.publish(err) @@ -132,6 +135,11 @@ def _set(name, value): return 1 toml_config[name] = python_value + + if (name == 'core.reporting' and python_value is True) or \ + (name == 'core.email'): + segment_identify(toml_config) + _save_config_file(config_path, toml_config) return 0 diff --git a/cli/dcoscli/constants.py b/cli/dcoscli/constants.py index afc8767..8313e42 100644 --- a/cli/dcoscli/constants.py +++ b/cli/dcoscli/constants.py @@ -4,6 +4,6 @@ SEGMENT_IO_WRITE_KEY_PROD = '51ybGTeFEFU1xo6u10XMDrr6kATFyRyh' SEGMENT_IO_WRITE_KEY_DEV = '39uhSEOoRHMw6cMR6st9tYXDbAL3JSaP' SEGMENT_IO_CLI_EVENT = 'dcos-cli' SEGMENT_IO_CLI_ERROR_EVENT = 'dcos-cli-error' -SEGMENT_URL = 'https://api.segment.io/v1/track' +SEGMENT_URL = 'https://api.segment.io/v1' DCOS_PRODUCTION_ENV = 'DCOS_PRODUCTION' diff --git a/cli/tests/data/analytics/dcos_reporting.toml b/cli/tests/data/analytics/dcos_reporting.toml index 590a344..637ea68 100644 --- a/cli/tests/data/analytics/dcos_reporting.toml +++ b/cli/tests/data/analytics/dcos_reporting.toml @@ -1,3 +1,3 @@ [core] -reporting = true email = "test@mail.com" +reporting = true diff --git a/cli/tests/integrations/cli/common.py b/cli/tests/integrations/cli/common.py index 2d8d50f..e4e66c6 100644 --- a/cli/tests/integrations/cli/common.py +++ b/cli/tests/integrations/cli/common.py @@ -60,3 +60,30 @@ def assert_command(cmd, assert returncode_ == returncode assert stdout_ == stdout assert stderr_ == stderr + + +def mock_called_some_args(mock, *args, **kwargs): + """Convience method for some mock assertions. Returns True if the + arguments to one of the calls of `mock` contains `args` and + `kwargs`. + + :param mock: the mock to check + :type mock: mock.Mock + :returns: True if the arguments to one of the calls for `mock` + contains `args` and `kwargs`. + :rtype: bool + """ + + for call in mock.call_args_list: + call_args, call_kwargs = call + + if any(arg not in call_args for arg in args): + continue + + if any(k not in call_kwargs or call_kwargs[k] != v + for k, v in kwargs.items()): + continue + + return True + + return False diff --git a/cli/tests/integrations/cli/test_analytics.py b/cli/tests/integrations/cli/test_analytics.py index 339ed9a..d2ec35c 100644 --- a/cli/tests/integrations/cli/test_analytics.py +++ b/cli/tests/integrations/cli/test_analytics.py @@ -6,15 +6,18 @@ import requests import rollbar from dcos.api import constants, util from dcoscli.analytics import _base_properties +from dcoscli.config.main import main as config_main from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY, SEGMENT_IO_CLI_ERROR_EVENT, SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_DEV, SEGMENT_IO_WRITE_KEY_PROD, SEGMENT_URL) from dcoscli.main import main +from common import mock_called_some_args from mock import patch ANON_ID = 0 +USER_ID = 'test@mail.com' def _mock(fn): @@ -38,7 +41,6 @@ def test_no_exc(): ''' - # args args = [util.which('dcos')] env = _env_reporting() @@ -46,14 +48,13 @@ def test_no_exc(): assert main() == 0 # segment.io - args, kwargs = requests.post.call_args - assert args == (SEGMENT_URL,) - - props = _base_properties() - assert kwargs['json'] == {'anonymousId': ANON_ID, - 'event': SEGMENT_IO_CLI_EVENT, - 'properties': props} - assert kwargs['timeout'] == 1 + data = {'userId': USER_ID, + 'event': SEGMENT_IO_CLI_EVENT, + 'properties': _base_properties()} + assert mock_called_some_args(requests.post, + '{}/track'.format(SEGMENT_URL), + json=data, + timeout=1) # rollbar assert rollbar.report_message.call_count == 0 @@ -66,7 +67,6 @@ def test_exc(): ''' - # args args = [util.which('dcos')] env = _env_reporting() @@ -77,15 +77,19 @@ def test_exc(): assert main() == 1 # segment.io - _, kwargs = requests.post.call_args_list[1] - props = _base_properties() props['err'] = 'Traceback' props['exit_code'] = 1 - assert kwargs['json'] == {'anonymousId': ANON_ID, - 'event': SEGMENT_IO_CLI_ERROR_EVENT, - 'properties': props} + data = {'userId': USER_ID, + 'event': SEGMENT_IO_CLI_ERROR_EVENT, + 'properties': props} + assert mock_called_some_args(requests.post, + '{}/track'.format(SEGMENT_URL), + json=data, + timeout=1) + + # rollbar props = _base_properties() props['exit_code'] = 1 rollbar.report_message.assert_called_with('Traceback', 'error', @@ -150,6 +154,24 @@ def test_production_setting_false(): rollbar.init.assert_called_with(ROLLBAR_SERVER_POST_KEY, 'dev') +@_mock +def test_config_set(): + '''Tests that a `dcos config set core.email ` makes a + segment.io identify call''' + + args = [util.which('dcos'), 'config', 'set', 'core.email', 'test@mail.com'] + env = _env_reporting() + + with patch('sys.argv', args), patch.dict(os.environ, env): + assert config_main() == 0 + + # segment.io + assert mock_called_some_args(requests.post, + '{}/identify'.format(SEGMENT_URL), + json={'userId': 'test@mail.com'}, + timeout=1) + + def _env_reporting(): path = os.path.join('tests', 'data', 'analytics', 'dcos_reporting.toml') return {constants.DCOS_CONFIG_ENV: path}