-
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.
Merge pull request #29 from NYPL/daily-location-visits-alarms
Add DailyLocationVisitsAlarms
- Loading branch information
Showing
13 changed files
with
244 additions
and
9 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import os | ||
|
||
from alarms.alarm import Alarm | ||
from datetime import timedelta | ||
from helpers.query_helper import build_redshift_daily_location_visits_query | ||
from nypl_py_utils.classes.s3_client import S3Client | ||
from nypl_py_utils.functions.log_helper import create_log | ||
|
||
|
||
class DailyLocationVisitsAlarms(Alarm): | ||
def __init__(self, redshift_client): | ||
super().__init__(redshift_client) | ||
self.logger = create_log("daily_location_visits_alarms") | ||
|
||
def run_checks(self): | ||
date_to_test = (self.yesterday_date - timedelta(days=29)).isoformat() | ||
self.logger.info(f"\nDAILY LOCATION VISITS: {date_to_test}\n") | ||
s3_client = S3Client( | ||
os.environ["SHOPPERTRAK_S3_BUCKET"], os.environ["SHOPPERTRAK_S3_RESOURCE"] | ||
) | ||
all_shoppertrak_sites = set(s3_client.fetch_cache()) | ||
s3_client.close() | ||
|
||
redshift_table = "daily_location_visits" + self.redshift_suffix | ||
redshift_query = build_redshift_daily_location_visits_query( | ||
redshift_table, date_to_test | ||
) | ||
|
||
self.redshift_client.connect() | ||
redshift_results = self.redshift_client.execute_query(redshift_query) | ||
self.redshift_client.close_connection() | ||
|
||
redshift_sites = [] | ||
redshift_healthy = [] | ||
for shoppertrak_site, is_all_healthy in redshift_results: | ||
redshift_sites.append(shoppertrak_site) | ||
redshift_healthy.append(int(is_all_healthy)) | ||
|
||
self.check_redshift_duplicate_sites_alarm(redshift_sites) | ||
self.check_redshift_missing_sites_alarm(redshift_sites, all_shoppertrak_sites) | ||
self.check_redshift_extra_sites_alarm(redshift_sites, all_shoppertrak_sites) | ||
self.check_redshift_healthy_sites_alarm(redshift_healthy) | ||
|
||
def check_redshift_duplicate_sites_alarm(self, redshift_sites): | ||
seen_sites = set() | ||
duplicate_sites = set() | ||
for site in redshift_sites: | ||
if site in seen_sites: | ||
duplicate_sites.add(site) | ||
seen_sites.add(site) | ||
|
||
if duplicate_sites: | ||
self.logger.error( | ||
"The following ShopperTrak sites are duplicated: {}".format( | ||
sorted(list(duplicate_sites)) | ||
) | ||
) | ||
|
||
def check_redshift_missing_sites_alarm(self, redshift_sites, all_sites): | ||
missing_sites = all_sites.difference(set(redshift_sites)) | ||
if missing_sites: | ||
self.logger.error( | ||
"The following ShopperTrak sites are missing: {}".format( | ||
sorted(list(missing_sites)) | ||
) | ||
) | ||
|
||
def check_redshift_extra_sites_alarm(self, redshift_sites, all_sites): | ||
extra_sites = set(redshift_sites).difference(all_sites) | ||
if extra_sites: | ||
self.logger.error( | ||
"The following unknown ShopperTrak site ids were found: {}".format( | ||
sorted(list(extra_sites)) | ||
) | ||
) | ||
|
||
def check_redshift_healthy_sites_alarm(self, redshift_healthy): | ||
percent_healthy = sum(redshift_healthy) / len(redshift_healthy) | ||
if percent_healthy < 0.5: | ||
self.logger.error( | ||
"Only {0:.2f}% of ShopperTrak sites were healthy".format( | ||
percent_healthy * 100 | ||
) | ||
) |
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 |
---|---|---|
@@ -1,2 +1,2 @@ | ||
nypl-py-utils[mysql-client,postgresql-client,redshift-client,config-helper]==1.4.0 | ||
nypl-py-utils[mysql-client,postgresql-client,redshift-client,s3-client,config-helper]==1.6.2 | ||
selenium>=4.10.0 |
125 changes: 125 additions & 0 deletions
125
tests/alarms/models/test_daily_location_visits_alarms.py
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,125 @@ | ||
import logging | ||
import pytest | ||
|
||
from alarms.models.daily_location_visits_alarms import DailyLocationVisitsAlarms | ||
from datetime import date | ||
|
||
|
||
class TestDailyLocationVisitsAlarms: | ||
@pytest.fixture | ||
def test_instance(self, mocker): | ||
mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.S3Client.fetch_cache", | ||
return_value=["aa", "bb", "cc"], | ||
) | ||
return DailyLocationVisitsAlarms(mocker.MagicMock()) | ||
|
||
def test_init(self, mocker): | ||
daily_location_visits_alarms = DailyLocationVisitsAlarms(mocker.MagicMock()) | ||
assert daily_location_visits_alarms.redshift_suffix == "_test_redshift_db" | ||
assert daily_location_visits_alarms.run_added_tests | ||
assert daily_location_visits_alarms.yesterday_date == date(2023, 5, 31) | ||
assert daily_location_visits_alarms.yesterday == "2023-05-31" | ||
|
||
def test_run_checks_no_alarm(self, mocker, caplog): | ||
daily_location_visits_alarms = DailyLocationVisitsAlarms(mocker.MagicMock()) | ||
mock_s3_client = mocker.MagicMock() | ||
mock_s3_constructor = mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.S3Client", | ||
return_value=mock_s3_client, | ||
) | ||
mock_s3_client.fetch_cache.return_value = ["aa", "bb", "cc"] | ||
|
||
mock_redshift_query = mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.build_redshift_daily_location_visits_query", | ||
return_value="redshift query", | ||
) | ||
daily_location_visits_alarms.redshift_client.execute_query.return_value = ( | ||
["aa", True], | ||
["bb", True], | ||
["cc", False], | ||
) | ||
|
||
with caplog.at_level(logging.ERROR): | ||
daily_location_visits_alarms.run_checks() | ||
assert caplog.text == "" | ||
|
||
mock_s3_constructor.assert_called_once_with( | ||
"test_shoppertrak_s3_bucket", "test_shoppertrak_s3_resource" | ||
) | ||
mock_s3_client.fetch_cache.assert_called_once() | ||
mock_s3_client.close.assert_called_once() | ||
daily_location_visits_alarms.redshift_client.connect.assert_called_once() | ||
mock_redshift_query.assert_called_once_with( | ||
"daily_location_visits_test_redshift_db", "2023-05-02" | ||
) | ||
daily_location_visits_alarms.redshift_client.execute_query.assert_called_once_with( | ||
"redshift query" | ||
) | ||
daily_location_visits_alarms.redshift_client.close_connection.assert_called_once() | ||
|
||
def test_run_checks_redshift_duplicate_sites_alarm( | ||
self, test_instance, mocker, caplog | ||
): | ||
mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.build_redshift_daily_location_visits_query" | ||
) | ||
test_instance.redshift_client.execute_query.return_value = ( | ||
["aa", True], | ||
["bb", True], | ||
["bb", True], | ||
["cc", False], | ||
) | ||
|
||
with caplog.at_level(logging.ERROR): | ||
test_instance.run_checks() | ||
assert ("The following ShopperTrak sites are duplicated: ['bb']") in caplog.text | ||
|
||
def test_run_checks_redshift_missing_sites_alarm( | ||
self, test_instance, mocker, caplog | ||
): | ||
mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.build_redshift_daily_location_visits_query" | ||
) | ||
test_instance.redshift_client.execute_query.return_value = ( | ||
["aa", True], | ||
["cc", True], | ||
) | ||
|
||
with caplog.at_level(logging.ERROR): | ||
test_instance.run_checks() | ||
assert "The following ShopperTrak sites are missing: ['bb']" in caplog.text | ||
|
||
def test_run_checks_redshift_extra_sites_alarm(self, test_instance, mocker, caplog): | ||
mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.build_redshift_daily_location_visits_query" | ||
) | ||
test_instance.redshift_client.execute_query.return_value = ( | ||
["aa", True], | ||
["bb", True], | ||
["cc", False], | ||
["ee", True], | ||
["dd", False], | ||
) | ||
|
||
with caplog.at_level(logging.ERROR): | ||
test_instance.run_checks() | ||
assert ( | ||
"The following unknown ShopperTrak site ids were found: ['dd', 'ee']" | ||
) in caplog.text | ||
|
||
def test_run_checks_redshift_healthy_sites_alarm( | ||
self, test_instance, mocker, caplog | ||
): | ||
mocker.patch( | ||
"alarms.models.daily_location_visits_alarms.build_redshift_daily_location_visits_query" | ||
) | ||
test_instance.redshift_client.execute_query.return_value = ( | ||
["aa", True], | ||
["bb", False], | ||
["cc", False], | ||
) | ||
|
||
with caplog.at_level(logging.ERROR): | ||
test_instance.run_checks() | ||
assert "Only 33.33% of ShopperTrak sites were healthy" in caplog.text |
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