Skip to content

Commit

Permalink
🎁 octavia-cli: improve telemetry (#12072)
Browse files Browse the repository at this point in the history
  • Loading branch information
alafanechere authored Apr 21, 2022
1 parent 9d9507b commit 1f2fdae
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 32 deletions.
17 changes: 9 additions & 8 deletions octavia-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,17 +352,18 @@ $ octavia apply
## Telemetry
This CLI has some telemetry tooling to send Airbyte some data about the usage of this tool.
We use this data to measure the tool's adoption and detect common errors users encounter to improve it.
We will use this data to improve the CLI and measure its adoption.
The telemetry sends data about:
* Which command was run (not the arguments or options used).
* Success or failure of the command run and the error type.
* The workspace id. It is unique to each Airbyte instance.
* Success or failure of the command run and the error type (not the error payload).
* The current Airbyte workspace id if the user has not set the *anonymous data collection* on their Airbyte instance.
You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment to `false` or using the `--disable-telemetry` flag.
You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment variable to `False` or using the `--disable-telemetry` flag.
## Changelog
| Version | Date | Description | PR |
|---------|------------|------------------|----------------------------------------------------------|
| 0.35.68 | 2022-04-12 | Add telemetry | [#11896](https://github.com/airbytehq/airbyte/issues/11896)|
| 0.35.61 | 2022-04-07 | Alpha release | [EPIC](https://github.com/airbytehq/airbyte/issues/10704)|
| Version | Date | Description | PR |
|---------|------------|-------------------|----------------------------------------------------------|
| 0.36.2 | 2022-04-15 | Improve telemetry | [#12072](https://github.com/airbytehq/airbyte/issues/11896)|
| 0.35.68 | 2022-04-12 | Add telemetry | [#11896](https://github.com/airbytehq/airbyte/issues/11896)|
| 0.35.61 | 2022-04-07 | Alpha release | [EPIC](https://github.com/airbytehq/airbyte/issues/10704)|
24 changes: 21 additions & 3 deletions octavia-cli/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ delete_previous_alias() {


pull_image() {
echo "🐙 - Pulling image for octavia ${VERSION}"
docker pull airbyte/octavia-cli:${VERSION} > /dev/null 2>&1
echo "🐙 - 🎉 octavia ${VERSION} image was pulled"
}

add_octavia_comment_to_profile() {
printf "\n# OCTAVIA CLI\n" >> ${DETECTED_PROFILE}
printf "\n# OCTAVIA CLI ${VERSION}\n" >> ${DETECTED_PROFILE}
}

create_octavia_env_file() {
Expand All @@ -53,21 +55,36 @@ create_octavia_env_file() {
echo "🐙 - 💾 The octavia env file was created at ${OCTAVIA_ENV_FILE}"
}

enable_telemetry() {
echo "export OCTAVIA_ENABLE_TELEMETRY=$1" >> ${DETECTED_PROFILE}
echo "OCTAVIA_ENABLE_TELEMETRY=$1" >> ${OCTAVIA_ENV_FILE}
}

add_alias() {
echo 'alias octavia="docker run -i --rm -v \$(pwd):/home/octavia-project --network host --env-file \${OCTAVIA_ENV_FILE} --user \$(id -u):\$(id -g) airbyte/octavia-cli:'${VERSION}'"' >> ${DETECTED_PROFILE}
echo "🐙 - 🎉 octavia alias was added to ${DETECTED_PROFILE}, please open a new terminal window or run source ${DETECTED_PROFILE}"
echo "🐙 - 🎉 octavia alias was added to ${DETECTED_PROFILE}!"
echo "🐙 - Please open a new terminal window or run source ${DETECTED_PROFILE}"
}

install() {
pull_image
add_alias
}

telemetry_consent() {
read -p "❓ - Allow Airbyte to collect telemetry to improve the CLI? (Y/n)" -n 1 -r </dev/tty
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
enable_telemetry "True"
else
enable_telemetry "False"
fi
}

update_or_install() {
if grep -q "^alias octavia=*" ${DETECTED_PROFILE}; then
read -p "❓ - You already have an octavia alias in your profile. Do you want to update? (Y/n)" -n 1 -r </dev/tty
echo # (optional) move to a new line
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
delete_previous_alias
Expand All @@ -76,6 +93,7 @@ update_or_install() {
else
add_octavia_comment_to_profile
create_octavia_env_file
telemetry_consent
install
fi
}
Expand Down
12 changes: 10 additions & 2 deletions octavia-cli/octavia_cli/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import click
import pkg_resources
from airbyte_api_client.api import workspace_api
from airbyte_api_client.model.workspace_id_request_body import WorkspaceIdRequestBody

from .apply import commands as apply_commands
from .check_context import check_api_health, check_is_initialized, check_workspace_exists
Expand Down Expand Up @@ -42,7 +43,8 @@ def set_context_object(ctx: click.Context, airbyte_url: str, workspace_id: str,
ctx.obj["TELEMETRY_CLIENT"] = telemetry_client
api_client = get_api_client(airbyte_url)
ctx.obj["WORKSPACE_ID"] = get_workspace_id(api_client, workspace_id)
api_client.user_agent = build_user_agent(ctx.obj["OCTAVIA_VERSION"], ctx.obj["WORKSPACE_ID"])
ctx.obj["ANONYMOUS_DATA_COLLECTION"] = get_anonymous_data_collection(api_client, ctx.obj["WORKSPACE_ID"])
api_client.user_agent = build_user_agent(ctx.obj["OCTAVIA_VERSION"])
ctx.obj["API_CLIENT"] = api_client
ctx.obj["PROJECT_IS_INITIALIZED"] = check_is_initialized()
except Exception as e:
Expand All @@ -63,7 +65,7 @@ def set_context_object(ctx: click.Context, airbyte_url: str, workspace_id: str,
"--enable-telemetry/--disable-telemetry",
envvar="OCTAVIA_ENABLE_TELEMETRY",
default=True,
help="Enable or disable usage tracking for telemetry.",
help="Enable or disable telemetry for product improvement.",
)
@click.pass_context
def octavia(ctx: click.Context, airbyte_url: str, workspace_id: str, enable_telemetry: bool) -> None:
Expand Down Expand Up @@ -94,6 +96,12 @@ def get_workspace_id(api_client, user_defined_workspace_id):
return api_response.workspaces[0]["workspaceId"]


def get_anonymous_data_collection(api_client, workspace_id):
api_instance = workspace_api.WorkspaceApi(api_client)
api_response = api_instance.get_workspace(WorkspaceIdRequestBody(workspace_id), _check_return_type=False)
return api_response.anonymous_data_collection


def add_commands_to_octavia():
for command in AVAILABLE_COMMANDS:
octavia.add_command(command)
Expand Down
11 changes: 5 additions & 6 deletions octavia-cli/octavia_cli/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@
import click


def build_user_agent(octavia_version: str, workspace_id: str) -> str:
"""Build user-agent for the API client according to octavia version and workspace id.
def build_user_agent(octavia_version: str) -> str:
"""Build user-agent for the API client according to octavia version.
Args:
octavia_version (str): Current octavia version.
workspace_id (str): Current workspace id.
Returns:
str: the user-agent string.
"""
return f"octavia-cli/{octavia_version}/{workspace_id}"
return f"octavia-cli/{octavia_version}"


class TelemetryClient:
Expand Down Expand Up @@ -75,15 +74,15 @@ def send_command_telemetry(self, ctx: click.Context, error: Optional[Exception]
error (Optional[Exception], optional): The error that was raised. Defaults to None.
extra_info_name (Optional[str], optional): Extra info name if the context was not built yet. Defaults to None.
"""
user_id = ctx.obj.get("WORKSPACE_ID")
user_id = ctx.obj.get("WORKSPACE_ID") if ctx.obj.get("ANONYMOUS_DATA_COLLECTION", True) is False else None
anonymous_id = None if user_id else str(uuid.uuid1())

segment_context = {"app": {"name": "octavia-cli", "version": ctx.obj.get("OCTAVIA_VERSION")}}
segment_properties = {
"success": error is None,
"error_type": error.__class__.__name__ if error is not None else None,
"project_is_initialized": ctx.obj.get("PROJECT_IS_INITIALIZED"),
"airbyte_role": os.getenv("AIRBYTE_ROLE"),
"airbyter": os.getenv("AIRBYTE_ROLE") == "airbyter",
}
command_name = self._create_command_name(ctx, extra_info_name)
self.segment_client.track(
Expand Down
18 changes: 17 additions & 1 deletion octavia-cli/unit_tests/test_entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import click
import pkg_resources
import pytest
from airbyte_api_client.model.workspace_id_request_body import WorkspaceIdRequestBody
from click.testing import CliRunner
from octavia_cli import entrypoint

Expand All @@ -17,21 +18,25 @@ def dumb(ctx):

def test_set_context_object(mocker):
mocker.patch.object(entrypoint, "TelemetryClient")
mocker.patch.object(entrypoint, "build_user_agent")
mocker.patch.object(entrypoint, "get_api_client")
mocker.patch.object(entrypoint, "get_workspace_id")
mocker.patch.object(entrypoint, "build_user_agent")
mocker.patch.object(entrypoint, "check_is_initialized")
mocker.patch.object(entrypoint, "get_anonymous_data_collection")
mock_ctx = mocker.Mock(obj={})
built_context = entrypoint.set_context_object(mock_ctx, "my_airbyte_url", "my_workspace_id", "enable_telemetry")
entrypoint.TelemetryClient.assert_called_with("enable_telemetry")
mock_ctx.ensure_object.assert_called_with(dict)
built_context.obj == {
assert built_context.obj == {
"OCTAVIA_VERSION": pkg_resources.require("octavia-cli")[0].version,
"TELEMETRY_CLIENT": entrypoint.TelemetryClient.return_value,
"WORKSPACE_ID": entrypoint.get_workspace_id.return_value,
"API_CLIENT": entrypoint.get_api_client.return_value,
"PROJECT_IS_INITIALIZED": entrypoint.check_is_initialized.return_value,
"ANONYMOUS_DATA_COLLECTION": entrypoint.get_anonymous_data_collection.return_value,
}
entrypoint.build_user_agent.assert_called_with(built_context.obj["OCTAVIA_VERSION"])


def test_set_context_object_error(mocker):
Expand Down Expand Up @@ -106,6 +111,17 @@ def test_get_workspace_id_api_defined(mocker):
mock_api_instance.list_workspaces.assert_called_with(_check_return_type=False)


def test_get_anonymous_data_collection(mocker, mock_api_client):
mocker.patch.object(entrypoint, "workspace_api")
mock_api_instance = entrypoint.workspace_api.WorkspaceApi.return_value
assert (
entrypoint.get_anonymous_data_collection(mock_api_client, "my_workspace_id")
== mock_api_instance.get_workspace.return_value.anonymous_data_collection
)
entrypoint.workspace_api.WorkspaceApi.assert_called_with(mock_api_client)
mock_api_instance.get_workspace.assert_called_with(WorkspaceIdRequestBody("my_workspace_id"), _check_return_type=False)


def test_commands_in_octavia_group():
octavia_commands = entrypoint.octavia.commands.values()
for command in entrypoint.AVAILABLE_COMMANDS:
Expand Down
37 changes: 25 additions & 12 deletions octavia-cli/unit_tests/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


def test_build_user_agent():
ua = telemetry.build_user_agent("my_octavia_version", "my_workspace_id")
assert ua == "octavia-cli/my_octavia_version/my_workspace_id"
ua = telemetry.build_user_agent("my_octavia_version")
assert ua == "octavia-cli/my_octavia_version"


class TestTelemetryClient:
Expand Down Expand Up @@ -54,21 +54,33 @@ def test__create_command_name_single_context(self, mocker, telemetry_client, ext
assert command_name == "child_command"

@pytest.mark.parametrize(
"workspace_id,airbyte_role,project_is_initialized,octavia_version,error, expected_success, expected_error_type",
"workspace_id, anonymous_data_collection, airbyte_role, project_is_initialized, octavia_version, error, expected_success, expected_error_type",
[
(None, None, None, None, None, True, None),
("my_workspace_id", "my_airbyte_role", True, "0.1.0", None, True, None),
("my_workspace_id", "my_airbyte_role", False, "0.1.0", None, True, None),
("my_workspace_id", "my_airbyte_role", False, "0.1.0", AttributeError(), False, "AttributeError"),
("my_workspace_id", "my_airbyte_role", True, "0.1.0", AttributeError(), False, "AttributeError"),
(None, None, True, "0.1.0", AttributeError(), False, "AttributeError"),
(None, None, None, None, None, None, True, None),
(None, None, None, None, None, Exception(), False, "Exception"),
(None, None, None, None, None, AttributeError(), False, "AttributeError"),
(None, True, None, None, None, None, True, None),
(None, True, None, None, None, Exception(), False, "Exception"),
(None, True, None, None, None, AttributeError(), False, "AttributeError"),
("my_workspace_id", False, None, None, None, None, True, None),
("my_workspace_id", False, None, None, None, Exception(), False, "Exception"),
("my_workspace_id", True, None, None, None, None, True, None),
("my_workspace_id", True, None, None, None, Exception(), False, "Exception"),
("my_workspace_id", True, "airbyter", None, None, None, True, None),
("my_workspace_id", True, "non_airbyter", None, None, Exception(), False, "Exception"),
("my_workspace_id", True, "airbyter", True, None, None, True, None),
("my_workspace_id", True, "non_airbyter", False, None, Exception(), False, "Exception"),
("my_workspace_id", True, "airbyter", True, None, None, True, None),
("my_workspace_id", True, "non_airbyter", False, "0.1.0", Exception(), False, "Exception"),
("my_workspace_id", True, "non_airbyter", False, "0.1.0", None, True, None),
],
)
def test_send_command_telemetry(
self,
mocker,
telemetry_client,
workspace_id,
anonymous_data_collection,
airbyte_role,
project_is_initialized,
octavia_version,
Expand All @@ -79,21 +91,22 @@ def test_send_command_telemetry(
extra_info_name = "foo"
mocker.patch.object(telemetry.os, "getenv", mocker.Mock(return_value=airbyte_role))
mocker.patch.object(telemetry.uuid, "uuid1", mocker.Mock(return_value="MY_UUID"))
expected_user_id = workspace_id if workspace_id is not None else None
expected_anonymous_id = "MY_UUID" if workspace_id is None else None
expected_user_id = workspace_id if workspace_id is not None and anonymous_data_collection is False else None
expected_anonymous_id = "MY_UUID" if expected_user_id is None else None
mock_ctx = mocker.Mock(
obj={
"OCTAVIA_VERSION": octavia_version,
"PROJECT_IS_INITIALIZED": project_is_initialized,
"WORKSPACE_ID": workspace_id,
"ANONYMOUS_DATA_COLLECTION": anonymous_data_collection,
}
)
expected_segment_context = {"app": {"name": "octavia-cli", "version": octavia_version}}
expected_properties = {
"success": expected_success,
"error_type": expected_error_type,
"project_is_initialized": project_is_initialized,
"airbyte_role": airbyte_role,
"airbyter": airbyte_role == "airbyter",
}
telemetry_client.segment_client = mocker.Mock()
telemetry_client._create_command_name = mocker.Mock(return_value="my_command")
Expand Down

0 comments on commit 1f2fdae

Please sign in to comment.