Skip to content

Commit

Permalink
Revert "Revert "SNOW-1544013 Fetch events for app"" (#1362)
Browse files Browse the repository at this point in the history
* Revert "Revert "SNOW-1544013 Fetch events for app" (#1361)"

This reverts commit 76da3e3.
  • Loading branch information
sfc-gh-fcampbell authored Jul 24, 2024
1 parent 76da3e3 commit b01693e
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 55 deletions.
21 changes: 20 additions & 1 deletion src/snowflake/cli/plugins/nativeapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
CommandResult,
MessageResult,
ObjectResult,
StreamResult,
)
from snowflake.cli.api.project.project_verification import assert_project_type
from snowflake.cli.api.secure_path import SecurePath
Expand Down Expand Up @@ -377,7 +378,6 @@ def app_validate(**options):
@nativeapp_definition_v2_to_v1
def app_events(**options):
"""Fetches events for this app from the event table configured in Snowflake."""
# WIP: only validates event table setup for now while the command is hidden
assert_project_type("native_app")

manager = NativeAppManager(
Expand All @@ -387,3 +387,22 @@ def app_events(**options):
events = manager.get_events()
if not events:
return MessageResult("No events found.")

def g():
for event in events:
yield EventResult(event)

return StreamResult(g())


class EventResult(ObjectResult, MessageResult):
"""ObjectResult that renders as a custom string when not printed as JSON."""

@property
def message(self):
e = self._element
return f"{e['TIMESTAMP']} {e['VALUE']}"

@property
def result(self):
return self._element
25 changes: 19 additions & 6 deletions src/snowflake/cli/plugins/nativeapp/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from functools import cached_property
from pathlib import Path
from textwrap import dedent
from typing import Any, List, Optional, TypedDict
from typing import Any, List, NoReturn, Optional, TypedDict

import jinja2
from click import ClickException
Expand Down Expand Up @@ -89,7 +89,7 @@

def generic_sql_error_handler(
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
):
) -> NoReturn:
# Potential refactor: If moving away from Python 3.8 and 3.9 to >= 3.10, use match ... case
if err.errno == DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED:
raise ProgrammingError(
Expand Down Expand Up @@ -315,10 +315,10 @@ def get_app_pkg_distribution_in_snowflake(self) -> str:
)

@cached_property
def account_event_table(self) -> str | None:
def account_event_table(self) -> str:
query = "show parameters like 'event_table' in account"
results = self._execute_query(query, cursor_class=DictCursor)
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), None)
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")

def verify_project_distribution(
self, expected_distribution: Optional[str] = None
Expand Down Expand Up @@ -715,9 +715,22 @@ def get_validation_result(self, use_scratch_stage: bool):
)

def get_events(self) -> list[dict]:
if self.account_event_table is None:
if not self.account_event_table:
raise NoEventTableForAccount()
return []

# resource_attributes:"snow.database.name" uses the unquoted/uppercase app name
app_name = unquote_identifier(self.app_name)
query = dedent(
f"""\
select timestamp, value::varchar value
from {self.account_event_table}
where resource_attributes:"snow.database.name" = '{app_name}'
order by timestamp asc;"""
)
try:
return self._execute_query(query, cursor_class=DictCursor).fetchall()
except ProgrammingError as err:
generic_sql_error_handler(err)


def _validation_item_to_str(item: dict[str, str | int]):
Expand Down
76 changes: 73 additions & 3 deletions tests/nativeapp/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
NATIVEAPP_MODULE,
mock_execute_helper,
mock_snowflake_yml_file,
quoted_override_yml_file,
touch,
)
from tests.testing_utils.files_and_dirs import create_named_file
Expand Down Expand Up @@ -1349,23 +1350,92 @@ def test_account_event_table_not_set_up(mock_execute, temp_dir, mock_cursor):
mock_execute.side_effect = side_effects

native_app_manager = _get_na_manager()
assert native_app_manager.account_event_table is None
assert native_app_manager.account_event_table == ""


@mock.patch(
NATIVEAPP_MANAGER_ACCOUNT_EVENT_TABLE,
return_value="db.schema.event_table",
new_callable=mock.PropertyMock,
)
def test_get_events(mock_account_event_table, temp_dir, mock_cursor):
@mock.patch(NATIVEAPP_MANAGER_EXECUTE)
def test_get_events(mock_execute, mock_account_event_table, temp_dir, mock_cursor):
create_named_file(
file_name="snowflake.yml",
dir_name=temp_dir,
contents=[mock_snowflake_yml_file],
)

side_effects, expected = mock_execute_helper(
[
(
mock_cursor([dict(TIMESTAMP="2020-01-01T00:00:00Z", VALUE="test")], []),
mock.call(
dedent(
f"""\
select timestamp, value::varchar value
from db.schema.event_table
where resource_attributes:"snow.database.name" = 'MYAPP'
order by timestamp asc;"""
),
cursor_class=DictCursor,
),
),
]
)
mock_execute.side_effect = side_effects

native_app_manager = _get_na_manager()
assert native_app_manager.get_events() == []
assert native_app_manager.get_events() == [
dict(TIMESTAMP="2020-01-01T00:00:00Z", VALUE="test")
]
assert mock_execute.mock_calls == expected


@mock.patch(
NATIVEAPP_MANAGER_ACCOUNT_EVENT_TABLE,
return_value="db.schema.event_table",
new_callable=mock.PropertyMock,
)
@mock.patch(NATIVEAPP_MANAGER_EXECUTE)
def test_get_events_quoted_app_name(
mock_execute, mock_account_event_table, temp_dir, mock_cursor
):
create_named_file(
file_name="snowflake.yml",
dir_name=temp_dir,
contents=[mock_snowflake_yml_file],
)
create_named_file(
file_name="snowflake.local.yml",
dir_name=temp_dir,
contents=[quoted_override_yml_file],
)

side_effects, expected = mock_execute_helper(
[
(
mock_cursor([dict(TIMESTAMP="2020-01-01T00:00:00Z", VALUE="test")], []),
mock.call(
dedent(
f"""\
select timestamp, value::varchar value
from db.schema.event_table
where resource_attributes:"snow.database.name" = 'My Application'
order by timestamp asc;"""
),
cursor_class=DictCursor,
),
),
]
)
mock_execute.side_effect = side_effects

native_app_manager = _get_na_manager()
assert native_app_manager.get_events() == [
dict(TIMESTAMP="2020-01-01T00:00:00Z", VALUE="test")
]
assert mock_execute.mock_calls == expected


@mock.patch(
Expand Down
45 changes: 0 additions & 45 deletions tests_integration/nativeapp/test_events.py

This file was deleted.

0 comments on commit b01693e

Please sign in to comment.