Jinja2 template helper filter datetimeformat_as_timezone (#3426)

# What this PR does
Add an additional jinja2 template helper filter to convert a timezone
aware datetime to a different timezone.

## Which issue(s) this PR fixes
Alert payloads that originate from different time zones may include
timestamps having a local time offset. This filter enables
standardization of timestamp timezones.

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] 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: Joey Orlando <joey.orlando@grafana.com>
This commit is contained in:
jorgeav 2023-12-05 05:39:04 +11:00 committed by GitHub
parent e39baa6bbe
commit 4df8985283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 10 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Add `datetimeformat_as_timezone` Jinja2 template helper filter by @jorgeav ([#3426](https://github.com/grafana/oncall/pull/3426))
### Changed
- Disallow creating and deleting direct paging integrations by @vadimkerr ([#3475](https://github.com/grafana/oncall/pull/3475))

View file

@ -205,8 +205,12 @@ Built-in functions:
- `tojson_pretty` - same as tojson, but prettified
- `iso8601_to_time` - converts time from iso8601 (`2015-02-17T18:30:20.000Z`) to datetime
- `datetimeformat` - converts time from datetime to the given format (`%H:%M / %d-%m-%Y` by default)
- `datetimeformat_as_timezone` - same as `datetimeformat`, with the inclusion of timezone conversion (`UTC` by default)
- Usage example: `{{ payload.alerts.startsAt | iso8601_to_time | datetimeformat_as_timezone('%Y-%m-%dT%H:%M:%S%z', 'America/Chicago') }}`
- `regex_replace` - performs a regex find and replace
- `regex_match` - performs a regex match, returns `True` or `False`. Usage example: `{{ payload.ruleName | regex_match(".*") }}`
- `b64decode` - performs a base64 string decode. Usage example: `{{ payload.data | b64decode }}`
- `regex_match` - performs a regex match, returns `True` or `False`
- Usage example: `{{ payload.ruleName | regex_match(".*") }}`
- `b64decode` - performs a base64 string decode
- Usage example: `{{ payload.data | b64decode }}`
{{< section >}}

View file

@ -3,6 +3,7 @@ import json
import re
from django.utils.dateparse import parse_datetime
from pytz import timezone
def datetimeformat(value, format="%H:%M / %d-%m-%Y"):
@ -12,6 +13,13 @@ def datetimeformat(value, format="%H:%M / %d-%m-%Y"):
return None
def datetimeformat_as_timezone(value, format="%H:%M / %d-%m-%Y", tz="UTC"):
try:
return value.astimezone(timezone(tz)).strftime(format)
except (ValueError, AttributeError, TypeError):
return None
def iso8601_to_time(value):
try:
return parse_datetime(value)

View file

@ -6,6 +6,7 @@ from jinja2.sandbox import SandboxedEnvironment
from .filters import (
b64decode,
datetimeformat,
datetimeformat_as_timezone,
iso8601_to_time,
json_dumps,
regex_match,
@ -22,6 +23,7 @@ def raise_security_exception(name):
jinja_template_env = SandboxedEnvironment(loader=BaseLoader())
jinja_template_env.filters["datetimeformat"] = datetimeformat
jinja_template_env.filters["datetimeformat_as_timezone"] = datetimeformat_as_timezone
jinja_template_env.filters["iso8601_to_time"] = iso8601_to_time
jinja_template_env.filters["tojson_pretty"] = to_pretty_json
jinja_template_env.globals["time"] = timezone.now

View file

@ -1,7 +1,10 @@
import base64
import json
import pytest
from django.conf import settings
from django.utils.dateparse import parse_datetime
from pytz import timezone
from common.jinja_templater import apply_jinja_template
from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning
@ -14,6 +17,60 @@ def test_apply_jinja_template():
assert payload == result
def test_apply_jinja_template_iso8601_to_time():
payload = {"name": "2023-11-22T15:30:00.000000000Z"}
result = apply_jinja_template(
"{{ payload.name | iso8601_to_time }}",
payload,
)
expected = str(parse_datetime(payload["name"]))
assert result == expected
def test_apply_jinja_template_datetimeformat():
payload = {"aware": "2023-05-28 23:11:12+0000", "naive": "2023-05-28 23:11:12"}
assert apply_jinja_template(
"{{ payload.aware | iso8601_to_time | datetimeformat('%Y-%m-%dT%H:%M:%S%z') }}",
payload,
) == parse_datetime(payload["aware"]).strftime("%Y-%m-%dT%H:%M:%S%z")
assert apply_jinja_template(
"{{ payload.naive | iso8601_to_time | datetimeformat('%Y-%m-%dT%H:%M:%S%z') }}",
payload,
) == parse_datetime(payload["naive"]).strftime("%Y-%m-%dT%H:%M:%S%z")
def test_apply_jinja_template_datetimeformat_as_timezone():
payload = {"aware": "2023-05-28 23:11:12+0000", "naive": "2023-05-28 23:11:12"}
assert apply_jinja_template(
"{{ payload.aware | iso8601_to_time | datetimeformat_as_timezone('%Y-%m-%dT%H:%M:%S%z', 'America/Chicago') }}",
payload,
) == parse_datetime(payload["aware"]).astimezone(timezone("America/Chicago")).strftime("%Y-%m-%dT%H:%M:%S%z")
assert apply_jinja_template(
"{{ payload.naive | iso8601_to_time | datetimeformat_as_timezone('%Y-%m-%dT%H:%M:%S%z', 'America/Chicago') }}",
payload,
) == parse_datetime(payload["naive"]).astimezone(timezone("America/Chicago")).strftime("%Y-%m-%dT%H:%M:%S%z")
with pytest.raises(JinjaTemplateWarning):
apply_jinja_template(
"{{ payload.aware | iso8601_to_time | datetimeformat_as_timezone('%Y-%m-%dT%H:%M:%S%z', 'potato') }}",
payload,
)
def test_apply_jinja_template_b64decode():
payload = {"name": "SGVsbG8sIHdvcmxkIQ=="}
assert apply_jinja_template(
"{{ payload.name | b64decode }}",
payload,
) == base64.b64decode(
payload["name"]
).decode("utf-8")
def test_apply_jinja_template_json_dumps():
payload = {"name": "test"}

View file

@ -1,7 +0,0 @@
from common.jinja_templater.filters import b64decode
def test_base64_decode():
original = "dGVzdCBzdHJpbmch"
expected = "test string!"
assert b64decode(original) == expected

View file

@ -80,7 +80,7 @@ export const genericTemplateCheatSheet: CheatSheetInterface = {
{ listItemName: 'payload - payload of last alert in the group' },
{ listItemName: 'web_title, web_mesage, web_image_url - templates from Web' },
{ listItemName: 'payload, grafana_oncall_link, grafana_oncall_incident_id, integration_name, source_link' },
{ listItemName: 'time(), datetimeformat, iso8601_to_time' },
{ listItemName: 'time(), datetimeformat, datetimeformat_as_timezone, iso8601_to_time' },
{ listItemName: 'to_pretty_json' },
{ listItemName: 'regex_replace, regex_match' },
{ listItemName: 'b64decode' },