Add datetimeparse Jinja2 template helper filter function (#4312)

# 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>
This commit is contained in:
jorgeav 2024-05-14 03:16:38 +10:00 committed by GitHub
parent bd8c078347
commit dba6748efd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 56 additions and 2 deletions

View file

@ -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(".*") }}`

View file

@ -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)

View file

@ -7,6 +7,7 @@ from .filters import (
b64decode,
datetimeformat,
datetimeformat_as_timezone,
datetimeparse,
iso8601_to_time,
json_dumps,
parse_json,
@ -25,6 +26,7 @@ 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["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

View file

@ -1,5 +1,6 @@
import base64
import json
from datetime import datetime
from unittest.mock import patch
import pytest
@ -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():
@ -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():

View file

@ -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' },