oncall-engine/engine/apps/labels/models.py

207 lines
8.3 KiB
Python
Raw Permalink Normal View History

import logging
import typing
from json import JSONDecodeError
from django.db import models
from django.utils import timezone
from apps.labels.client import LabelsAPIClient, LabelsRepoAPIException
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
from apps.labels.tasks import update_label_pairs_cache
from apps.labels.types import LabelPair
from apps.labels.utils import LABEL_OUTDATED_TIMEOUT_MINUTES
if typing.TYPE_CHECKING:
from apps.user_management.models import Organization
logger = logging.getLogger(__name__)
MAX_KEY_NAME_LENGTH = 200
MAX_VALUE_NAME_LENGTH = 200
class LabelKeyCache(models.Model):
id = models.CharField(primary_key=True, editable=False, max_length=36)
name = models.CharField(max_length=MAX_KEY_NAME_LENGTH)
organization = models.ForeignKey("user_management.Organization", on_delete=models.CASCADE)
last_synced = models.DateTimeField(auto_now=True)
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
prescribed = models.BooleanField(default=False, null=True)
@property
def is_outdated(self) -> bool:
return timezone.now() - self.last_synced > timezone.timedelta(minutes=LABEL_OUTDATED_TIMEOUT_MINUTES)
@classmethod
def get_or_create_by_name(cls, organization: "Organization", key_name: str) -> typing.Optional["LabelKeyCache"]:
"""
`get_or_create_by_name` tries to get label key with provided name from cache.
If there is no label key with this name in the cache - it tries to fetch it from the labels repo API.
"""
label_key = cls.objects.filter(organization=organization, name=key_name).first()
if label_key:
return label_key
# fetch label key from labels repo
try:
label, _ = LabelsAPIClient(organization.grafana_url, organization.api_token).get_label_by_key_name(
label_key
)
except (LabelsRepoAPIException, JSONDecodeError) as e:
logger.error(f"Failed to get or create label key {key_name} for organization {organization.id}: {e}")
return None
# save labels key in cache
label_key = LabelKeyCache(
id=label["key"]["id"],
name=label["key"]["name"],
organization=organization,
prescribed=label["key"]["prescribed"],
)
label_key.save()
return label_key
class LabelValueCache(models.Model):
id = models.CharField(primary_key=True, editable=False, max_length=36)
name = models.CharField(max_length=MAX_VALUE_NAME_LENGTH)
key = models.ForeignKey("labels.LabelKeyCache", on_delete=models.CASCADE, related_name="values")
last_synced = models.DateTimeField(auto_now=True)
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
prescribed = models.BooleanField(default=False, null=True)
@property
def is_outdated(self) -> bool:
return timezone.now() - self.last_synced > timezone.timedelta(minutes=LABEL_OUTDATED_TIMEOUT_MINUTES)
class AssociatedLabel(models.Model):
"""
Abstract model, is used to keep information about label association with other instances
(integrations, schedules, etc.). To add ability to associate labels with a type of instances ,
inhere this model and add a foreign key to the instance model.
Attention: add `AssociatedLabel` to the end of the name of inheritor (example: AlertReceiveChannelAssociatedLabel)
"""
key = models.ForeignKey(LabelKeyCache, on_delete=models.CASCADE)
value = models.ForeignKey(LabelValueCache, on_delete=models.CASCADE)
organization = models.ForeignKey("user_management.Organization", on_delete=models.CASCADE, related_name="labels")
class Meta:
abstract = True
@staticmethod
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
def update_association(
label_pairs: typing.List[LabelPair], instance: models.Model, organization: "Organization"
) -> None:
"""
Update label associations for selected instance: delete associations with labels that are not in `labels_data`,
create new associations and labels, if needed.
Then call celery task to update cache for labels from `labels_data`
instance: the model instance that the labels are associated with (e.g. AlertReceiveChannel instance)
"""
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
labels_data_keys = {label["key"]["id"]: label["key"]["name"] for label in label_pairs}
labels_data_values = {label["value"]["id"]: label["value"]["name"] for label in label_pairs}
# delete associations with labels that are not presented in labels_data
instance.labels.exclude(key_id__in=labels_data_keys.keys(), value_id__in=labels_data_values.keys()).delete()
labels_keys = []
labels_values = []
labels_associations = []
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
for label_pair in label_pairs:
key_id = label_pair["key"]["id"]
key_name = label_pair["key"]["name"]
key_prescribed = label_pair["key"]["prescribed"]
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
value_id = label_pair["value"]["id"]
value_name = label_pair["value"]["name"]
value_prescribed = label_pair["value"]["prescribed"]
label_key = LabelKeyCache(id=key_id, name=key_name, organization=organization, prescribed=key_prescribed)
labels_keys.append(label_key)
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
label_value = LabelValueCache(id=value_id, name=value_name, key_id=key_id, prescribed=value_prescribed)
labels_values.append(label_value)
associated_instance = {instance.labels.field.name: instance}
labels_associations.append(
instance.labels.model(
key_id=key_id, value_id=value_id, organization=organization, **associated_instance
)
)
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
# create labels cache and associations that don't exist.
# Ignoring conflicts because some labels might laready exist. They will be updates in task.
LabelKeyCache.objects.bulk_create(labels_keys, ignore_conflicts=True, batch_size=5000)
LabelValueCache.objects.bulk_create(labels_values, ignore_conflicts=True, batch_size=5000)
instance.labels.model.objects.bulk_create(labels_associations, ignore_conflicts=True, batch_size=5000)
Support prescribed labels (#3848) # What this PR does **Cleanup label typing:** 1. LabelParam -> two separate types LabekKey and LabelValue 2. LabelData -> renamed to LabelPair. 3. LabelKeyData -> renamed to LabelOption Data is not giving any info about what this type represents. 4. Remove LabelsData and LabelsKeysData types. They are just list of types listed above and with new naming it feels obsolete. 5. ValueData removed. LabelPair is used instead. 6. Rework AlertGroupCustomLabel to use LabelKey type for key to make type system more consistent. Name model type AlertGroupCustomLabel**DB** and api type AlertGroupCustomLabel**API** to clearly distinguish them. **Split update_labels_cache into two tasks** update_label_option_cache and update_label_pairs_cache. Original task was expecting array of LabelsData (now it's LabelPair) OR one LabelKeyData ( now it's LabelOption). I believe having one function with two sp different argument types makes it more complicated for understanding. **Make OnCall backend support prescribed labels**. OnCall will sync and store "prescribed" field for key and values, so Label dropdown able to disable editing for certain labels. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com> Co-authored-by: Yulya Artyukhina <Ferril.darkdiver@gmail.com>
2024-02-20 14:42:51 +08:00
update_label_pairs_cache.apply_async((label_pairs,))
@staticmethod
def get_associating_label_field_name() -> str:
"""Returns ForeignKey field name for the associated model"""
raise NotImplementedError
class AlertReceiveChannelAssociatedLabel(AssociatedLabel):
"""Keeps information about label association with alert receive channel instances"""
alert_receive_channel = models.ForeignKey(
"alerts.AlertReceiveChannel", on_delete=models.CASCADE, related_name="labels"
)
class Meta:
unique_together = ["key_id", "value_id", "alert_receive_channel_id"]
@staticmethod
def get_associating_label_field_name() -> str:
"""Returns ForeignKey field name for the associated model"""
return "alert_receive_channel"
class AlertGroupAssociatedLabel(models.Model):
"""
A model for alert group labels (similar to AlertReceiveChannelAssociatedLabel for integrations).
The key difference is that alert group labels do not use label IDs, but rather key and value names explicitly.
This is done to make alert group labels "static" (so they don't change when the labels are updated in label repo).
"""
alert_group = models.ForeignKey("alerts.AlertGroup", on_delete=models.CASCADE, related_name="labels")
organization = models.ForeignKey(
"user_management.Organization", on_delete=models.CASCADE, related_name="alert_group_labels"
)
key_name = models.CharField(max_length=MAX_KEY_NAME_LENGTH)
value_name = models.CharField(max_length=MAX_VALUE_NAME_LENGTH)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["organization", "key_name", "value_name", "alert_group"],
name="unique_alert_group_label",
)
]
Webhook labels (#3383) This PR add labels for webhooks. 1. Make webhook "labelable" with ability to filter by labels. 2. Add labels to the webhook payload. It contain new field webhook with it's name, id and labels. Field integration and alert_group has a corresponding label field as well. See example of a new payload below: ``` { "event": { "type": "escalation" }, "user": null, "alert_group": { "id": "IRFN6ZD31N31B", "integration_id": "CTWM7U4A2QG97", "route_id": "RUE7U7Z46SKGY", "alerts_count": 1, "state": "firing", "created_at": "2023-11-22T08:54:55.178243Z", "resolved_at": null, "acknowledged_at": null, "title": "Incident", "permalinks": { "slack": null, "telegram": null, "web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B" }, "labels": { "severity": "critical" } }, "alert_group_id": "IRFN6ZD31N31B", "alert_payload": { "message": "This alert was sent by user for demonstration purposes" }, "integration": { "id": "CTWM7U4A2QG97", "type": "webhook", "name": "hi - Webhook", "team": null, "labels": { "hello": "world", "severity": "critical" } }, "notified_users": [], "users_to_be_notified": [], "webhook": { "id": "WHAXK4BTC7TAEQ", "name": "test", "labels": { "hello": "kesha" } } } ``` I feel that there is an opportunity to make code cleaner - remove all label logic from serializers, views and utils to models or dedicated LabelerService and introduce Labelable interface with something like label_verbal, update_labels methods. However, I don't want to tie webhook labels with a refactoring. --------- Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
class WebhookAssociatedLabel(AssociatedLabel):
"""Keeps information about label association with outgoing webhooks instances"""
webhook = models.ForeignKey(
"webhooks.Webhook",
on_delete=models.CASCADE,
related_name="labels",
)
organization = models.ForeignKey(
"user_management.Organization", on_delete=models.CASCADE, related_name="webhook_labels"
)
class Meta:
unique_together = ["key_id", "value_id", "webhook_id"]
@staticmethod
def get_associating_label_field_name() -> str:
"""Returns ForeignKey field name for the associated model"""
return "webhook"