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

feat: POC automation test DRAFT DO NOT MERGE #928

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12.4
3.11.4
9 changes: 9 additions & 0 deletions gooddata-sdk/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# (C) 2024 GoodData Corporation
# env.py
import os

# Define environment variables
HOST = os.getenv("GOODDATA_HOST", "https://checklist.staging.stg11.panther.intgdc.com")
TOKEN = os.getenv("GOODDATA_TOKEN", "<your token>")
DATASOURCE_ID = os.getenv("DATASOURCE_ID", "your datasource")
WORKSPACE_ID = "your workspace"
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"id": "total_returns_per_month",
"title": "Total Returns per Month",
"visualizationType": "COLUMN",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [],
"suggestions": [
{
"query": "Switch to a line chart to better visualize the trend of total returns over the months.",
"label": "Line Chart for Trends"
},
{
"query": "Filter the data to show total returns for this year only.",
"label": "This Year's Returns"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_returns_per_month_current_year",
"title": "Total Returns Per Month - Current Year",
"visualizationType": "COLUMN",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [
{
"using": "return_date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Switch to a line chart to better represent the trend of total returns over months.",
"label": "Show Line Chart Trend"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_customers_per_month_current_year",
"title": "Total Customers per Month - Current Year",
"visualizationType": "LINE",
"metrics": [
{
"id": "total_customers",
"type": "metric",
"title": "Total Customers"
}
],
"dimensionality": [
{
"id": "date.month",
"type": "attribute",
"title": "Date - Month/Year"
}
],
"filters": [
{
"using": "date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Consider switching to a COLUMN chart for clearer comparisons of Total Customers per month.",
"label": "Switch to COLUMN chart"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_returns_per_month_current_year",
"title": "Total Returns Per Month - Current Year",
"visualizationType": "LINE",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [
{
"using": "return_date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Switch to a line chart to better represent the trend of total returns over months.",
"label": "Show Line Chart Trend"
}
]
}
18 changes: 18 additions & 0 deletions gooddata-sdk/integration_tests/fixtures/ai_questions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"question": "What is total returns per month? show as COLUMN chart",
"expected_objects_file": "column_total_returns_by_month.json"
},
{
"question": "Add filter by current year, show as COLUMN chart",
"expected_objects_file": "column_total_returns_by_month_filter_current_year.json"
},
{
"question": "Switch to LINE chart",
"expected_objects_file": "line_total_returns_by_month_filter_current_year.json"
},
{
"question": "Replace metric Total Customers instead of total returns",
"expected_objects_file": "line_total_customers_by_month_filter_current_year.json"
}
]
83 changes: 83 additions & 0 deletions gooddata-sdk/integration_tests/scripts/aiChat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# (C) 2024 GoodData Corporation
import os
import sys

import gooddata_api_client
import pytest
from gooddata_api_client.api import smart_functions_api
from gooddata_api_client.model.chat_history_request import ChatHistoryRequest
from gooddata_api_client.model.chat_history_result import ChatHistoryResult
from gooddata_api_client.model.chat_request import ChatRequest
from utils import load_json, normalize_metrics

sys.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from env import WORKSPACE_ID

EXPECTED_OBJECTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data_response")
QUESTIONS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fixtures")

questions_list = load_json(os.path.join(QUESTIONS_DIR, "ai_questions.json"))


class gooddataAiChatApp:
def __init__(self, api_client, workspace_id):
self.api_instance = smart_functions_api.SmartFunctionsApi(api_client)
self.workspace_id = workspace_id

async def ask_question(self, question: str):
return self.api_instance.ai_chat(self.workspace_id, ChatRequest(question=question))

async def chat_history(self, interaction_id: int, feedback: str):
return self.api_instance.ai_chat_history(
self.workspace_id, ChatHistoryRequest(chat_history_interaction_id=interaction_id, user_feedback=feedback)
)


def validate_response(actual_response, expected_response):
actual_metrics = normalize_metrics(
actual_response["created_visualizations"]["objects"][0]["metrics"], exclude_keys=["title"]
)
expected_metrics = normalize_metrics(expected_response["metrics"], exclude_keys=["title"])
assert actual_metrics == expected_metrics, "Metrics do not match"
assert (
actual_response["created_visualizations"]["objects"][0]["visualization_type"]
== expected_response["visualizationType"]
), "Visualization type mismatch"
assert (
actual_response["created_visualizations"]["objects"][0]["dimensionality"] == expected_response["dimensionality"]
), "Dimensionality mismatch"
assert (
actual_response["created_visualizations"]["objects"][0]["filters"] == expected_response["filters"]
), "Filters mismatch"


@pytest.fixture(scope="module")
def app(set_authorization_header): # Using the global fixture for Authorization header
app_instance = gooddataAiChatApp(set_authorization_header, WORKSPACE_ID)
return app_instance


@pytest.mark.parametrize(
"question, expected_file",
[(item["question"], item["expected_objects_file"]) for item in questions_list],
ids=[item["question"] for item in questions_list],
)
@pytest.mark.asyncio
async def test_ai_chat(app, question, expected_file):
expected_objects = load_json(os.path.join(EXPECTED_OBJECTS_DIR, expected_file))
try:
api_response = await app.ask_question(question)
validate_response(api_response.to_dict(), expected_objects)

interaction_id = api_response.chat_history_interaction_id
user_feedback = await app.chat_history(interaction_id, "POSITIVE")
assert isinstance(user_feedback, ChatHistoryResult), "Invalid response from chat history"
except gooddata_api_client.ApiException as e:
pytest.fail(f"API exception: {e}")
except Exception as e:
pytest.fail(f"Unexpected error: {e}")


if __name__ == "__main__":
pytest.main()
36 changes: 36 additions & 0 deletions gooddata-sdk/integration_tests/scripts/ai_chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# (C) 2024 GoodData Corporation
import os
import sys

import pytest

# Add the root directory to sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from env import HOST, TOKEN, WORKSPACE_ID
from gooddata_sdk import GoodDataSdk


@pytest.fixture
def test_config():
return {"host": HOST, "token": TOKEN, "workspace_id": WORKSPACE_ID}


questions = [
"What is the number of Accounts?",
"What is the total of Amount?",
]


@pytest.mark.parametrize("question", questions)
def test_ask_ai(test_config, question):
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
workspace_id = test_config["workspace_id"]
chat_ai_res = sdk.compute.ai_chat(workspace_id, question=question)

print(f"Chat AI response: {chat_ai_res}")
assert chat_ai_res is not None, "Response should not be None"


if __name__ == "__main__":
pytest.main()
26 changes: 26 additions & 0 deletions gooddata-sdk/integration_tests/scripts/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# (C) 2024 GoodData Corporation

import os
import sys

import pytest
from env import HOST, TOKEN
from gooddata_api_client import ApiClient, Configuration

# Add the root directory to the Python path
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ROOT_DIR)


@pytest.fixture(scope="session", autouse=True)
def set_authorization_header():
"""
Fixture to set the Authorization header globally for all tests.
"""
configuration = Configuration(host=HOST)
configuration.access_token = TOKEN
api_client = ApiClient(configuration)
api_client.default_headers["Authorization"] = f"Bearer {TOKEN}"
yield api_client
# Cleanup after the tests, if necessary (e.g., closing client)
api_client.close()
16 changes: 16 additions & 0 deletions gooddata-sdk/integration_tests/scripts/create_ref_workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# (C) 2024 GoodData Corporation
from env import DATASOURCE_ID, HOST, TOKEN, WORKSPACE_ID
from workspace_manager import createWorkspace, getDataSource, update_env_file

if __name__ == "__main__":
test_config = {"host": HOST, "token": TOKEN}

if WORKSPACE_ID:
print(f"Workspace ID '{WORKSPACE_ID}' already exists. Skipping workspace creation.")
else:
workspace_id = createWorkspace(test_config)
dataSource = getDataSource(DATASOURCE_ID, test_config)
if workspace_id:
update_env_file(workspace_id)
else:
print("Failed to create workspace.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# (C) 2024 GoodData Corporation
from env import HOST, TOKEN
from workspace_manager import deleteWorkspace

if __name__ == "__main__":
test_config = {"host": HOST, "token": TOKEN}

deleteWorkspace(test_config)
30 changes: 30 additions & 0 deletions gooddata-sdk/integration_tests/scripts/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# (C) 2024 GoodData Corporation
import json


def load_json(file_path):
"""Load a JSON file and return its contents."""
with open(file_path) as file: # Removed the "r" as it's the default mode
return json.load(file)


def normalize_metrics(metrics, exclude_keys=None):
"""
Normalize keys in the metrics list to camelCase, excluding specified keys.
:param metrics: List of dictionaries with metric data.
:param exclude_keys: List of keys to exclude from normalization.
:return: List of normalized metric dictionaries.
"""
if exclude_keys is None:
exclude_keys = []

def snake_to_camel(snake_str):
components = snake_str.split("_")
return components[0] + "".join(x.title() for x in components[1:])

return [
{snake_to_camel(key): value for key, value in metric.items() if key not in exclude_keys}
for metric in metrics
if isinstance(metric, dict)
]
Loading
Loading