Skip to content

Commit

Permalink
Add datetimeparse Jinja2 template helper filter function (#4312)
Browse files Browse the repository at this point in the history
# What this PR does
Jinja2 filter to parse strings into datetime objects. Previously, only a
limited set of strings could be parsed into datetime objects
([datetime_re](https://docs.djangoproject.com/en/2.2/_modules/django/utils/dateparse/)).

The addition of this filter allows for strings of any format to be
converted into datetime.

## Which issue(s) this PR closes


<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## 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] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.

Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
  • Loading branch information
jorgeav and joeyorlando committed May 13, 2024
1 parent bd8c078 commit dba6748
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 2 deletions.
3 changes: 2 additions & 1 deletion docs/sources/configure/jinja2-templating/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,10 @@ Built-in functions:
- `tojson` - dumps a structure to JSON
- `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` - converts datetime to string according to strftime format codes (`%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') }}`
- `datetimeparse` - converts string to datetime according to strftime format codes (`%H:%M / %d-%m-%Y` by default)
- `regex_replace` - performs a regex find and replace
- `regex_match` - performs a regex match, returns `True` or `False`
- Usage example: `{{ payload.ruleName | regex_match(".*") }}`
Expand Down
8 changes: 8 additions & 0 deletions engine/common/jinja_templater/filters.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import base64
import json
import re
from datetime import datetime

from django.utils.dateparse import parse_datetime
from pytz import timezone


def datetimeparse(value, format="%H:%M / %d-%m-%Y"):
try:
return datetime.strptime(value, format)
except (ValueError, AttributeError, TypeError):
return None


def datetimeformat(value, format="%H:%M / %d-%m-%Y"):
try:
return value.strftime(format)
Expand Down
2 changes: 2 additions & 0 deletions engine/common/jinja_templater/jinja_template_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
b64decode,
datetimeformat,
datetimeformat_as_timezone,
datetimeparse,
iso8601_to_time,
json_dumps,
parse_json,
Expand All @@ -25,6 +26,7 @@ def raise_security_exception(name):

jinja_template_env.filters["datetimeformat"] = datetimeformat
jinja_template_env.filters["datetimeformat_as_timezone"] = datetimeformat_as_timezone
jinja_template_env.filters["datetimeparse"] = datetimeparse
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
Expand Down
43 changes: 43 additions & 0 deletions engine/common/tests/test_apply_jinja_template.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import base64
import json
from datetime import datetime
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -44,6 +45,14 @@ def test_apply_jinja_template_datetimeformat():
"{{ 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")
assert apply_jinja_template(
"{{ payload.aware | datetimeparse('%Y-%m-%d %H:%M:%S%z') | datetimeformat('%Y-%m-%dT%H:%M:%S%z') }}",
payload,
) == datetime.strptime(payload["aware"], "%Y-%m-%d %H:%M:%S%z").strftime("%Y-%m-%dT%H:%M:%S%z")
assert apply_jinja_template(
"{{ payload.naive | datetimeparse('%Y-%m-%d %H:%M:%S') | datetimeformat('%Y-%m-%dT%H:%M:%S%z') }}",
payload,
) == datetime.strptime(payload["naive"], "%Y-%m-%d %H:%M:%S").strftime("%Y-%m-%dT%H:%M:%S%z")


def test_apply_jinja_template_datetimeformat_as_timezone():
Expand All @@ -57,12 +66,46 @@ def test_apply_jinja_template_datetimeformat_as_timezone():
"{{ 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")
assert (
apply_jinja_template(
"""{{ payload.aware | datetimeparse('%Y-%m-%d %H:%M:%S%z') | 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 | datetimeparse('%Y-%m-%d %H:%M:%S') | 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,
)
apply_jinja_template(
"""{{ payload.aware | datetimeparse('%Y-%m-%d %H:%M:%S%z') |
datetimeformat_as_timezone('%Y-%m-%dT%H:%M:%S%z', 'potato') }}""",
payload,
)


def test_apply_jinja_template_datetimeparse():
payload = {"aware": "15 05 2024 07:52:11 -0600", "naive": "2024-05-15T07:52:11"}

assert apply_jinja_template(
"{{ payload.aware | datetimeparse('%d %m %Y %H:%M:%S %z') }}",
payload,
) == str(datetime.strptime(payload["aware"], "%d %m %Y %H:%M:%S %z"))
assert apply_jinja_template(
"{{ payload.naive | datetimeparse('%Y-%m-%dT%H:%M:%S') }}",
payload,
) == str(datetime.strptime(payload["naive"], "%Y-%m-%dT%H:%M:%S"))


def test_apply_jinja_template_b64decode():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const genericTemplateCheatSheet: CheatSheetInterface = {
{ listItemName: 'labels - labels assigned to the 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, datetimeformat_as_timezone, iso8601_to_time' },
{ listItemName: 'time(), datetimeformat, datetimeformat_as_timezone, datetimeparse, iso8601_to_time' },
{ listItemName: 'to_pretty_json' },
{ listItemName: 'regex_replace, regex_match' },
{ listItemName: 'b64decode' },
Expand Down

0 comments on commit dba6748

Please sign in to comment.