-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
18 changed files
with
480 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from django_redis import get_redis_connection | ||
from typing import Optional, Any | ||
|
||
|
||
class CacheClient: | ||
def __init__(self) -> None: | ||
pass | ||
|
||
def get(self, key: str) -> Optional[Any]: | ||
with get_redis_connection() as redis_connection: | ||
return redis_connection.get(key) | ||
|
||
def set(self, key: str, value: Any, ex: Optional[int] = None) -> bool: | ||
with get_redis_connection() as redis_connection: | ||
return redis_connection.set(key, value, ex=ex) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import requests | ||
from insights.internals.base import VtexAuthentication | ||
from concurrent.futures import ThreadPoolExecutor, as_completed | ||
import json | ||
from insights.sources.vtexcredentials.clients import AuthRestClient | ||
from insights.sources.cache import CacheClient | ||
from insights.utils import format_to_iso_utc | ||
from django.conf import settings | ||
|
||
from datetime import datetime | ||
|
||
|
||
class VtexOrdersRestClient(VtexAuthentication): | ||
def __init__(self, auth_params, cache_client: CacheClient) -> None: | ||
self.headers = { | ||
"X-VTEX-API-AppToken": auth_params["VTEX-API-AppToken"], | ||
"X-VTEX-API-AppKey": auth_params["VTEX-API-AppKey"], | ||
} | ||
self.base_url = auth_params["domain"] | ||
self.cache = cache_client | ||
|
||
def get_cache_key(self, query_filters): | ||
"""Gere uma chave única para o cache baseada nos filtros de consulta.""" | ||
return f"vtex_data:{json.dumps(query_filters, sort_keys=True)}" | ||
|
||
def get_vtex_endpoint(self, query_filters: dict, page_number: int = 1): | ||
start_date = query_filters.get("ended_at__gte") | ||
end_date = query_filters.get("ended_at__lte") | ||
utm_source = query_filters.get("utm_source") | ||
|
||
if start_date is not None: | ||
url = f"https://{self.base_url}.myvtex.com/api/oms/pvt/orders/?f_UtmSource={utm_source}&per_page=100&page={page_number}&f_authorizedDate=authorizedDate:[{start_date} TO {end_date}]&f_status=invoiced" | ||
else: | ||
url = f"https://{self.base_url}.myvtex.com/api/oms/pvt/orders/?f_UtmSource={utm_source}&per_page=100&page={page_number}&f_status=invoiced" | ||
return url | ||
|
||
def parse_datetime(self, date_str): | ||
try: | ||
# Tente fazer o parse da string para datetime | ||
return datetime.fromisoformat(date_str) # Para strings ISO formatadas | ||
except ValueError: | ||
return None # Retorne None se a conversão falhar | ||
|
||
def list(self, query_filters: dict): | ||
cache_key = self.get_cache_key(query_filters) | ||
|
||
cached_data = self.cache.get(cache_key) | ||
if cached_data: | ||
return json.loads(cached_data) | ||
|
||
if not query_filters.get("utm_source", None): | ||
return {"error": "utm_source field is mandatory"} | ||
|
||
if query_filters.get("ended_at__gte", None): | ||
start_date_str = query_filters["ended_at__gte"] | ||
start_date = self.parse_datetime(start_date_str) | ||
if start_date: | ||
query_filters["ended_at__gte"] = start_date.strftime( | ||
"%Y-%m-%dT%H:%M:%S.%fZ" | ||
) | ||
|
||
if query_filters.get("ended_at__lte", None): | ||
end_date_str = query_filters["ended_at__lte"] | ||
end_date = self.parse_datetime(end_date_str) | ||
if end_date: | ||
query_filters["ended_at__lte"] = end_date.strftime( | ||
"%Y-%m-%dT%H:%M:%S.%fZ" | ||
) | ||
|
||
if query_filters.get("utm_source", None): | ||
query_filters["utm_source"] = query_filters.pop("utm_source")[0] | ||
|
||
total_value = 0 | ||
total_sell = 0 | ||
max_value = float("-inf") | ||
min_value = float("inf") | ||
|
||
response = requests.get( | ||
self.get_vtex_endpoint(query_filters), headers=self.headers | ||
) | ||
data = response.json() | ||
|
||
if "list" not in data or not data["list"]: | ||
return response.status_code, data | ||
|
||
pages = data["paging"]["pages"] if "paging" in data else 1 | ||
|
||
# botar o max_workers em variavel de ambiente | ||
with ThreadPoolExecutor(max_workers=10) as executor: | ||
page_futures = { | ||
executor.submit( | ||
lambda page=page: requests.get( | ||
self.get_vtex_endpoint({**query_filters, "page": page}), | ||
headers=self.headers, | ||
) | ||
): page | ||
for page in range(1, pages + 1) | ||
} | ||
|
||
for page_future in as_completed(page_futures): | ||
try: | ||
response = page_future.result() | ||
if response.status_code == 200: | ||
results = response.json() | ||
for result in results["list"]: | ||
if result["status"] != "canceled": | ||
total_value += result["totalValue"] | ||
total_sell += 1 | ||
max_value = max(max_value, result["totalValue"]) | ||
min_value = min(min_value, result["totalValue"]) | ||
else: | ||
print( | ||
f"Request failed with status code: {response.status_code}" | ||
) | ||
except Exception as exc: | ||
print(f"Generated an exception: {exc}") | ||
|
||
total_value /= 100 | ||
max_value /= 100 | ||
min_value /= 100 | ||
medium_ticket = total_value / total_sell if total_sell > 0 else 0 | ||
|
||
result_data = { | ||
"countSell": total_sell, | ||
"accumulatedTotal": total_value, | ||
"ticketMax": max_value, | ||
"ticketMin": min_value, | ||
"medium_ticket": medium_ticket, | ||
} | ||
|
||
self.cache.set(cache_key, json.dumps(result_data), ex=3600) | ||
|
||
return response.status_code, result_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import pytest | ||
from clients import VtexOrdersRestClient | ||
from unittest.mock import patch, Mock | ||
import json | ||
|
||
|
||
@pytest.mark.django_db | ||
@patch("clients.VtexOrdersRestClient.list") | ||
def test_verify_fields(mock_list): | ||
mock_list.return_value = { | ||
"countSell": 1, | ||
"accumulatedTotal": 50.21, | ||
"ticketMax": 50.21, | ||
"ticketMin": 50.21, | ||
"medium_ticket": 50.21, | ||
} | ||
|
||
auth_params = { | ||
"app_token": "fake_token", | ||
"app_key": "fake_key", | ||
"domain": "fake_domain", | ||
} | ||
cache_client = Mock() | ||
|
||
client = VtexOrdersRestClient(auth_params, cache_client) | ||
|
||
query_filters = { | ||
"start_date": "2024-09-01T00:00:00.000Z", | ||
"end_date": "2024-09-04T00:00:00.000Z", | ||
"base_url": "gbarbosa", | ||
"utm_source": "gbarbosa-recuperacaochatbotvtex", | ||
} | ||
|
||
response = client.list(query_filters) | ||
expected_keys = { | ||
"countSell", | ||
"accumulatedTotal", | ||
"ticketMax", | ||
"ticketMin", | ||
"medium_ticket", | ||
} | ||
assert set(response.keys()) == expected_keys | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_missing_utm_source(): | ||
# Mockando os auth_params e o cache_client | ||
auth_params = { | ||
"app_token": "fake_token", | ||
"app_key": "fake_key", | ||
"domain": "fake_domain", | ||
} | ||
cache_client = Mock() # CacheClient mockado | ||
|
||
# Simular que o cache não tem dados, retornando None | ||
cache_client.get.return_value = None | ||
|
||
client = VtexOrdersRestClient(auth_params, cache_client) | ||
|
||
query_filters = { | ||
"start_date": "2024-09-01T00:00:00.000Z", | ||
"end_date": "2024-09-04T00:00:00.000Z", | ||
"base_url": "gbarbosa", | ||
} | ||
|
||
response = client.list(query_filters) | ||
|
||
assert response == {"error": "utm_source field is mandatory"} | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_cache_behavior(): | ||
auth_params = { | ||
"app_token": "fake_token", | ||
"app_key": "fake_key", | ||
"domain": "fake_domain", | ||
} | ||
cache_client = Mock() | ||
|
||
client = VtexOrdersRestClient(auth_params, cache_client) | ||
|
||
cached_response = { | ||
"countSell": 1, | ||
"accumulatedTotal": 50.21, | ||
"ticketMax": 50.21, | ||
"ticketMin": 50.21, | ||
"medium_ticket": 50.21, | ||
} | ||
|
||
client.cache.get.return_value = json.dumps(cached_response) | ||
|
||
query_filters = { | ||
"start_date": "2024-09-01T00:00:00.000Z", | ||
"end_date": "2024-09-04T00:00:00.000Z", | ||
"base_url": "gbarbosa", | ||
"utm_source": "gbarbosa-recuperacaochatbotvtex", | ||
} | ||
|
||
response = client.list(query_filters) | ||
|
||
assert response == (200, cached_response) | ||
|
||
client.cache.get.assert_called_once_with(client.get_cache_key(query_filters)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from insights.sources.orders.clients import VtexOrdersRestClient | ||
from insights.sources.cache import CacheClient | ||
|
||
|
||
class QueryExecutor: | ||
def execute( | ||
filters: dict, | ||
operation: str, | ||
parser: callable, | ||
query_kwargs: dict = {}, | ||
auth_params: dict = {}, | ||
*args, | ||
**kwargs | ||
): | ||
client = VtexOrdersRestClient( | ||
auth_params=auth_params, cache_client=CacheClient() | ||
) | ||
list_data = client.list(query_filters=filters) | ||
|
||
return list_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import requests | ||
from django.conf import settings | ||
|
||
from insights.internals.base import InternalAuthentication | ||
|
||
|
||
class AuthRestClient(InternalAuthentication): | ||
def __init__(self, project) -> None: | ||
self.url = f"{settings.INTEGRATIONS_URL}/api/v1/apptypes/vtex/integration-details/{project}" | ||
|
||
def get_vtex_auth(self): | ||
# response = requests.get(url=self.url, headers=self.headers) | ||
# print("url", self.url) | ||
# print("RESPONSE", response) | ||
# tokens = response.json() | ||
# print("TOKEN", tokens) | ||
# credentials = { | ||
# "app_key": tokens["app_key"], | ||
# "app_token": tokens["app_token"], | ||
# "domain": tokens["domain"], | ||
# } | ||
# print("credenciais") | ||
return {} |
Oops, something went wrong.