decoupled alerts from Slack#1266
Conversation
…le-1852-decouple-alerts-from-slack
…le-1852-decouple-alerts-from-slack
| logger = get_logger(__name__) | ||
|
|
||
|
|
||
| ALERT_TABLES = dict( |
There was a problem hiding this comment.
Isn't is simpler to just create a dict called TABLE_NAME_TO_RESOURCE_TYPE ?
There was a problem hiding this comment.
It might. I actually just moved this part around 🙂
Checking for a simpler solution!
There was a problem hiding this comment.
It might. I actually just moved this part around 🙂
Checking for a simpler solution!
There was a problem hiding this comment.
I removed the need of that const and improved the logic and moved it into the alerts fetcher (this is actually the only place that should be aware of the tables)
| self.sent_alert_count = 0 | ||
| self.send_test_message_on_success = send_test_message_on_success | ||
| self.override_meta_slack_channel = override_config | ||
| self.alerts_integraion = self._get_integration_client() |
There was a problem hiding this comment.
should this be a class method in BaseIntegration? or somewhere over there?
There was a problem hiding this comment.
This is the function that determines which integration we should use.
It can't be part of the integration itself, and once we will add more integrations it should be more dynamic (currently it just returns SlackIntegration)
There was a problem hiding this comment.
I want to think about a way to move all its logic to the integrations area, I will think about it when I integrate PD
There was a problem hiding this comment.
We can create a method / class (outside of the BaseIntegration) that determines which integration we should use (base on params / config) and we can call it at the data monitoring.
We will still have a method that calls it inside the data monitoring but all the logic won't be there.
| ], | ||
| ) -> Dict[str, Any]: | ||
| return dict( | ||
| channel=( |
There was a problem hiding this comment.
isn't this also a little slack-specific? I wonder if we can somehow pass on all the param handling to the integration itself
There was a problem hiding this comment.
The issue is that currently we pass the params as part of our dbt package.
If I could I would have changed that, but currently users are relaying on it so its a bit problematic.
I will try to think of a way to move it to the integration itself but I a not sure this is that simple
There was a problem hiding this comment.
Moved the get integration params to be part of the integration itself
| filter=self.filter.get_filter(), | ||
| ) | ||
|
|
||
| def _format_alert( |
There was a problem hiding this comment.
It looks like the Pending alert and Alert class are pretty much overlapping... wouldn't it make more senes to have just one class, and if there's additional info or methods you can keep them in a helper class or something?
In any case, if you choose not to do that- I don't love this function, I think it can be nicer to create a to_formatted_alert method in every type of pending alert
There was a problem hiding this comment.
In my philosophy of code, those are 2 different class (although they are overlapping).
One (Pending alerts schema) is a schema which is used to validate typing and the structure of the data we query from the db using the alerts fetcher.
This will not be used in the SaaS version because we are going to query it from other tables and the fields probably going to be different.
The Alerts models are the "normalized" schema of alerts that is used by the integrations. This is the data structure we "sign" on that is going to contain all the data an integration need on an alert.
Merging the 2 is not a good option IMO because they have a different purpose and use, they first one is changeable depends on the system, and currently they are overlapping but they can easily be changed and don't 🙂
There was a problem hiding this comment.
ok, in that case I would move this method to the pending alerts classes instead of using ifs and isinstance, I think it's cleaner
There was a problem hiding this comment.
I will move it 🙂
This is scares me a bit because it can create a couple between the 2 in the future (I really like to separate the 2 - fetcher should not be aware of the API that is calling it, and adding the format to the schema kinda makes it coupled).
So if I will see in the future that we are coupling them I will ask to revert it to that change 🙂
| ) == json.dumps(["alert_id_2", "alert_id_3", "alert_id_4"]) | ||
| assert json.dumps( | ||
| [alert.id for alert in source_freshness_alerts], sort_keys=True | ||
| ) == json.dumps(["alert_id_2", "alert_id_3", "alert_id_4"]) |
There was a problem hiding this comment.
maybe we want to check more fields are formatted properly? could be nice to add a verification that nothing is omitted in the formatting
There was a problem hiding this comment.
So I don't check here that the fields got formatted properly, but that the right alerts got formatted to the relevant format.
Currently the Formatting is straight forward so I didn't add a test for that explicit
…c into the alerts fetcher (where it should be!)
… the data monitoring alerts flow
| filter=self.filter.get_filter(), | ||
| ) | ||
|
|
||
| def _format_alert( |
There was a problem hiding this comment.
ok, in that case I would move this method to the pending alerts classes instead of using ifs and isinstance, I think it's cleaner
| @@ -0,0 +1,184 @@ | |||
| from datetime import datetime | |||
There was a problem hiding this comment.
I think pending_alert.py is a more suitable name for this file, no?
|
|
||
|
|
||
| # Merge dicts attributes into a single list that contains all the values. | ||
| def merge_dicts_attribute(dicts: List[Dict], attribute_key: str) -> List: |
There was a problem hiding this comment.
| def merge_dicts_attribute(dicts: List[Dict], attribute_key: str) -> List: | |
| from itertools import chain | |
| def merge_dicts_attribute(dicts: List[Dict], attribute_key: str) -> List: | |
| attributes = [d.get(attribute_key) for d in dicts] | |
| return list(chain.from_iterable(attributes)) |
There was a problem hiding this comment.
sorry I love python magic so couldn't resist myself, you don't have to use this :)
There was a problem hiding this comment.
It will only work if the attributes are lists but we want to support attributes that are single item (like str, int, dict) and merge them into a list as well 🙂
So in order to make it work we will need to have 2 iterations to convert all to lists or have one big inline iteration with inline if statement inside it that uses the .get method on the same item 3 times:
attributes = [
d.get(attribute_key, []) if isinsatnce(d.get(attribute_key, []), list) else [d.get(attribute_key)]
for d
in dicts
]
return list(chain.from_iterable(attributes))There was a problem hiding this comment.
no I checked, it works for both cases actually :)
| # Flatten a nested dict by a given key, for example: | ||
| # nested_dict = {"top1": "one", "nested": {"top1": "One", "top2": "Two"}}, flatten_by_key = "nested" -> {"top1": "One", "top2": "Two"} | ||
| def flatten_dict_by_key(nested_dict: Dict, flatten_by_key: str) -> Dict: | ||
| flatten_dict = {**nested_dict, **nested_dict.get(flatten_by_key, {})} |
There was a problem hiding this comment.
pop also returns the value so you can do:
inner_dict = nested_dict.pop(flatten_by_key, {})
return {**nested_dict, **inner_dict}
There was a problem hiding this comment.
True, but pop on nested_dict which is a variable of the method will pop out the key from the origin dict as well (outside of the method) which can cause some big voodoo 😱
(this is actually breaking the test, because we run the same dict with different scenarios and this is editing the dict outside of the method calls)
We can do:
def flatten_dict_by_key(nested_dict: Dict, flatten_by_key: str) -> Dict:
nested_dict_copy = {**nested_dict}
inner_dict = nested_dict_copy.pop(flatten_by_key, {})
return {**nested_dict_copy, **inner_dict}Which IMO is quite the same with a different order 🙂
I can change to that if you prefer it though
|
|
||
|
|
||
| def test_get_alert_meta_attrs(): | ||
| base_alert = BasePendingAlertSchema(**{**BASE_ALERT, "model_meta": dict(attr="a")}) |
There was a problem hiding this comment.
you can use Parametrize here :)
| self.sent_alert_count = 0 | ||
| self.send_test_message_on_success = send_test_message_on_success | ||
| self.override_meta_slack_channel = override_config | ||
| self.alerts_integraion = self._get_integration_client() |
There was a problem hiding this comment.
I want to think about a way to move all its logic to the integrations area, I will think about it when I integrate PD
…toringAlers to pending alerts
No description provided.