Commit graph

49 commits

Author SHA1 Message Date
Vadim Stepanov
f977f9faee
Minor formatting changes (#2641)
# What this PR does

- Updates `black` and `flake8` to latest
- Removes `F541` from flake8 ignore (`F541 f-string is missing
placeholders`)
- Enables ["float to top"
option](https://pycqa.github.io/isort/docs/configuration/options.html#float-to-top)
for `isort`
2023-07-26 14:45:44 +01:00
Vadim Stepanov
b2f4ffb98a
apps.get_model -> import (#2619)
# What this PR does

Remove
[`apps.get_model`](https://docs.djangoproject.com/en/3.2/ref/applications/#django.apps.apps.get_model)
invocations and use inline `import` statements in places where models
are imported within functions/methods to avoid circular imports.

I believe `import` statements are more appropriate for most use cases as
they allow for better static code analysis & formatting, and solve the
issue of circular imports without being unnecessarily dynamic as
`apps.get_model`. With `import` statements, it's possible to:

- Jump to model definitions in most IDEs
- Automatically sort inline imports with `isort`
- Find import errors faster/easier (most IDEs highlight broken imports)
- Have more consistency across regular & inline imports when importing
models

This PR also adds a flake8 rule to ban imports of `django.apps.apps`, so
it's harder to use `apps.get_model` by mistake (it's possible to ignore
this rule by using `# noqa: I251`). The rule is not enforced on
directories with migration files, because `apps.get_model` is often used
to get a historical state of a model, which is useful when writing
migrations ([see this SO answer for more
details](https://stackoverflow.com/a/37769213)). So `apps.get_model` is
considered OK in migrations (even necessary in some cases).

## 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)
2023-07-25 09:43:23 +00:00
Joey Orlando
385e1377d6
remove deprecated backend code (#2502)
# What this PR does

See more details comments alongside the code.

Regarding frontend changes, the main changes in this PR are to remove
unused fields on the `Team` interface + unused methods on the `Team`
model.

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required) (N/A)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required) (N/A)
2023-07-12 02:07:45 -04:00
Andrey Oleynik
aeb35009be
add zvonok integration (#2339)
Added integration with [zvonok.com](https://zvonok.com) service.

Features:
- Phone number validation
- Test calls
- Selection of pre-recorded audio
- Making calls
- Processing call status
- Acknowledgment alert group (optional)

To process the call status, it is required to add a postback with the
GET method on the side of the zvonok.com service with the following
format ([more info
here](https://zvonok.com/ru-ru/guide/guide_postback/)):

```${ONCALL_BASE_URL}/zvonok/call_status_events?campaign_id={ct_campaign_id}&call_id={ct_call_id}&status={ct_status}&user_choice={ct_user_choice}```

The names of the transmitted parameters can be redefined through environment variables.

---------

Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
2023-07-05 05:55:53 +00:00
Joey Orlando
75028d0427
continue addressing mypy violations (#2170)
# What this PR does

See #2173 

Also, closes #2187 . All of the new files under `type_stubs/icalendar`
were autogenerated by running:

```bash
stubgen -p icalendar -o type_stubs
```

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-27 10:23:08 +00:00
Ildar Iskhakov
1bdb54df35
Remove request reading middleware as we use post-buffering (#2094)
# What this PR does

RequestBodyReadingMiddleware is excess as [post-buffering is
enabled](https://github.com/grafana/oncall/blob/dev/engine/uwsgi.ini#L17):

If an HTTP request has a body (like a POST request generated by a form),
you have to read (consume) it in your application. If you do not do
this, the communication socket with your webserver may be clobbered. If
you are lazy you can use the post-buffering option that will
automatically read data for you. For Rack applications this is
automatically enabled.
(https://uwsgi-docs.readthedocs.io/en/latest/ThingsToKnow.html)

## Which issue(s) this PR fixes

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-05 11:49:39 +08:00
Matias Bordese
eee5065e74
Add initial setup for local dev prometheus exporter (#2039) 2023-06-01 12:31:33 +00:00
Joey Orlando
e69062cd73
update engine log format + log out device type when sending push notifications (#2021)
# What this PR does

- update engine log format to include `org_slug` + `user_agent`
```bash
2023-05-25 11:28:22 source=engine:app google_trace_id=none logger=root inbound latency=0.33164 status=200 method=GET path=/api/internal/v1/alertgroups/stats user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 content-length=0 slow=0 user_id=1 org_id=1 org_slug=self_hosted_org
2023-05-25 11:28:23 source=engine:app google_trace_id=none logger=root inbound latency=0.402866 status=200 method=GET path=/api/internal/v1/alertgroups user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 content-length=0 slow=0 user_id=1 org_id=1 org_slug=self_hosted_org
```
- log out device type when sending push notifications ([possible values
are `web`, `ios`, or
`android`](https://github.com/xtrinch/fcm-django/blob/master/fcm_django/models.py#L264-L266))

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated (N/A)
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required) (N/A)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required) (N/A)
2023-05-25 07:49:12 -04:00
Innokentii Konstantinov
1f786e8d2a
Phone provider refactoring (#1713)
# What this PR does
This PR moves phone notification logic into separate object PhoneBackend
and introduces PhoneProvider interface to hide actual implementation of
external phone services provider. It should allow add new phone
providers just by implementing one class (See SimplePhoneProvider for
example).
# Why 
[Asterisk PR](https://github.com/grafana/oncall/pull/1282) showed that
our phone notification system is not flexible. However this is one of
the most frequent community questions - how to add "X" phone provider.
Also, this refactoring move us one step closer to unifying all
notification backends, since with PhoneBackend all phone notification
logic is collected in one place and independent from concrete
realisation.
# Highligts
1. PhoneBackend object - contains all phone notifications business
logic.
2. PhoneProvider - interface to  external phone services provider.
3. TwilioPhoneProvider and SimplePhoneProvider - two examples of
PhoneProvider implementation.
4. PhoneCallRecord and SMSRecord models. I introduced these models to
keep phone notification limits logic decoupled from external providers.
Existing TwilioPhoneCall and TwilioSMS objects will be migrated to the
new table to not to reset limits counter. To be able to receive status
callbacks and gather from Twilio TwilioPhoneCall and TwilioSMS still
exists, but they are linked to PhoneCallRecord and SMSRecord via fk, to
not to leat twilio logic into core code.

---------

Co-authored-by: Yulia Shanyrova <yulia.shanyrova@grafana.com>
2023-05-24 06:27:48 +00:00
Vadim Stepanov
53d34164ef
Fix SQLite permission issue (#1984)
# What this PR does
Fixes https://github.com/grafana/oncall/issues/1960.

## 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)
2023-05-22 19:16:31 +00:00
Joey Orlando
9be8080e51
add the ability to set/display "currently undergoing maintenance message" in the UI (#1917)
# What this PR does

add a new endpoint, `GET /maintenance-mode/`, which returns either a
string message pulled from the
`CURRENTLY_UNDERGOING_MAINTENANCE_MESSAGE` env var, or `None` + update
the UI to conditionally show this message if it is set

<img width="1321" alt="Screenshot 2023-05-10 at 11 28 16"
src="https://github.com/grafana/oncall/assets/9406895/833a77fb-3a90-4f9f-88d6-dae0d98d99d4">


## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required) (N/A)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-05-12 15:44:09 +00:00
Ildar Iskhakov
b707a7337e Specify celery worker queue in pyroscope tags 2023-03-28 13:26:03 +08:00
Ildar Iskhakov
e0dd4bd128
Update wsgi.py 2023-03-28 13:11:22 +08:00
Ildar Iskhakov
c158c8f28b
Configure pyroscope (#1638)
# What this PR does

## Which issue(s) this PR fixes

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-03-28 11:34:37 +08:00
Ildar Iskhakov
8d5cbcecf2
Start pyroscope only after uwsgi fork (#1607)
# What this PR does
Currently main uwsgi process sends spans while being idle, which make
graphs unreadable
<img width="494" alt="Screenshot 2023-03-23 at 18 00 21"
src="https://user-images.githubusercontent.com/2262529/227168746-125f2329-bfaa-4989-a391-712a230e0087.png">

## Which issue(s) this PR fixes

## Checklist

- [ ] Tests updated
- [ ] Documentation added
- [ ] `CHANGELOG.md` updated
2023-03-27 12:00:57 +08:00
Joey Orlando
4d655dff60
modify check_escalation_finished_task task (#1266)
# What this PR does

This PR:
- modifies the `check_escalation_finished_task` celery task to:
  - do stricter escalation validation based on the alert group's
escalation snapshot (see the `audit_alert_group_escalation` method in
`engine/apps/alerts/tasks/check_escalation_finished.py` for the
validation logic)
- use a read-only database for querying alert-groups if one is
configured, otherwise use the "default" one
- ping a configurable heartbeat (new env var
`ALERT_GROUP_ESCALATION_AUDITOR_CELERY_TASK_HEARTBEAT_URL` added)
- increase the task frequency from every 10 to every 13 minutes (this
can be configured via an env variable)
  - adds public documentation on how to configure this auditor task
- modifies the local celery startup command to properly take into
consideration all celery related env vars (similar to the ones we use in
`engine/celery_with_exporter.sh`; this made it easier to enable `celery
beat` locally for testing)
- removes the following code:
- removes references to `AlertGroup.estimate_escalation_finish_time` and
marks the model field as deprecated using the [`django-deprecate-fields`
library](https://pypi.org/project/django-deprecate-fields/). This field
was only used for the previous version of this validation task
- `EscalationSnapshotMixin.calculate_eta_for_finish_escalation` was only
used to calculate the value for
`AlertGroup.estimate_escalation_finish_time`
  - `calculate_escalation_finish_time` celery task
  

## Which issue(s) this PR fixes

https://github.com/grafana/oncall-private/issues/1558

## Checklist

- [x] Tests updated
- [x] Documentation added
- [x] `CHANGELOG.md` updated
2023-03-17 10:14:08 +00:00
Vadim Stepanov
ea60c0d247
Inbound email integration (#837)
This PR add Inbound Email integration.

It designed to support some variety of ESPs, but in prod we will use
Mailgun, so locally I tested it only with mailgun ESP.

**Important:**
To make it work on different clusters I'm planning to provide different
email domains for different regions, like ....@us.oncall.grafana.net,
...@eu.oncall.grafana.net

---------

Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
2023-03-16 13:59:21 +08:00
Joey Orlando
7c8722e714
remove mobile app feature flag (#1484)
# What this PR does

## Which issue(s) this PR fixes

## Checklist

- [x] Tests updated
- [ ] Documentation added (N/A)
- [x] `CHANGELOG.md` updated
2023-03-08 11:22:44 +01:00
Innokentii Konstantinov
7bad073626
Remove OSS_INSTALATION env var (#881)
It's a duplicate of LICENSE env var

**What this PR does**:
Remove OSS_INSTALLATION env var in favour of LICENSE env var. Also, I
refactored features tests a little. From my point of view it makes
little sense to test if all features are disabled or enabled. Better to
test specific use-case (e.g. oss installation).
Also to test that all features are disabled it is needed to set LICENSE
equals cloud license, which makes test confusing.

**Checklist**
- [x] Tests updated
- [ ] Documentation added
- [ ] `CHANGELOG.md` updated
2023-03-07 11:07:42 +00:00
Matias Bordese
a121f84a89
Rework slack login to check backend before redirecting (#1306)
Also:
- Remove unused slack login views in `social_auth` app
- Disable unlink actions in the profile if user is not owner (otherwise
it will disconnect the logged in user, not the one being shown on
screen)
2023-02-08 09:08:18 -03:00
Vadim Stepanov
08dbab73d2
Remove mobile_app_settings DynamicSetting (#1268)
# What this PR does
Remove checks for `mobile_app_settings` DynamicSetting, so changing
`FEATURE_MOBILE_APP_INTEGRATION_ENABLED` is enough for toggling the
mobile app backend (aka remove per-org feature flag)

Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-02-02 13:21:04 +00:00
Vadim Stepanov
9b709e86c9
Fix local dev setup slowness (#1270)
# What this PR does
Fixes an issue when a local dev setup becomes extremely slow.

- Set `DEBUG` and `SILK_PROFILER_ENABLED` to `False` by default + add
utility make commands to toggle it
- Use `uwsgi` instead of Django's built-in `runserver` for local dev
setup
- Limit Celery concurrency to 3 for local dev setup (previously was 20,
used >1GB RAM on my machine)

---------

Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-02-02 09:08:48 +00:00
Vadim Stepanov
ae5949aa7e
Allow viewers fetch cloud connection status (#1181)
# What this PR does
Fixes the issue when users with the viewer role can't fetch the cloud
connection status, which makes the plugin fail to load for viewers. This
PR makes the cloud connection endpoint use `OTHER_SETTINGS_READ` for
fetching the cloud connection status instead of `OTHER_SETTINGS_WRITE`.

## Checklist

- [x] Tests updated
- [x] `CHANGELOG.md` updated
2023-01-23 11:17:57 +00:00
Ildar Iskhakov
c06709fdb6
Move silk profiler under env variable setting (#1175)
# What this PR does

This PR moves silk profiler under the settings flag which can be
configured with env vars. It will allow us to enable silk on the
clusters, e.g. dev

## Which issue(s) this PR fixes

## Checklist

- [ ] Tests updated
- [ ] Documentation added
- [ ] `CHANGELOG.md` updated
2023-01-20 18:19:31 +08:00
Joey Orlando
98241b9a10
fake-data generation script + fixes for django-silk and django-debug-toolbar (#1128)
# What this PR does

## Main stuff

- add Python script to populate local Grafana/OnCall setup w/ large
amounts of fake data. Right now the data types that can be generated
are:
- teams and Admin users via the Grafana API (must be synced manually by
going into the UI before going onto the next step)
- Calendar Schedules which have three 8h oncall-shifts, via the OnCall
public API
- fixes `django-debug-toolbar` when being run in `docker-compose`
locally

## Other stuff
- documents how to easily modify the Grafana `docker-compose` container
provisioning configuration
- document solutions for two backend setup related issues encountered
when running the engine/celery workers locally, outside of
`docker-compose`, on an Apple silicon Mac
- fixes small bug in `grafana_plugin.helpers.client.APIClient.call_api`
where it would call `response.json()` for all requests, regardless of
whether or not the response actually contained data or not
- in `engine/settings/dev.py`, properly setup `django-silk` and document
the steps to use it locally
- make it possible to log out debug SQL queries by specifying
`DEV_DEBUG_VIEW_SQL_QUERIES` env var, rather than having to uncomment
out a section of `settings/dev.py`

## Which issue(s) this PR fixes

- Some local setup issues when trying to use `django-silk` and
`django-debug-toolbar`
- Makes it much easier to populate your local setup with a lot of fake
data
- Makes it possible to easily modify your local grafana's provisioning
configuration

## Checklist

- [ ] Tests updated (N/A)
- [ ] Documentation added (N/A)
- [ ] `CHANGELOG.md` updated (N/A)
2023-01-20 09:19:41 +01:00
Ildar Iskhakov
15256bc4cf Remove local uwsgi instrumentation and local development tempo and agent 2023-01-04 22:25:17 +08:00
Ildar Iskhakov
846497ddc7 Move env vars to docker-compose-dev.yml and clean up 2023-01-04 10:49:42 +08:00
Ildar Iskhakov
fa3413d2a9 Add tracing support 2022-12-19 17:15:06 +08:00
Joey Orlando
ed4be171f6
add make command to configure mobile app feature (#988)
Adds a make command, `enable-mobile-app-feature-flags`, which sets the
backend feature flag in `./dev/.env.dev`, and updates a record in the
`base_dynamicsetting` database table, which are needed to enable the
mobile app backend features.
2022-12-14 09:36:35 +01:00
Joey Orlando
53a7b02393
patch engine logging middleware (#950)
need to check that the user associated
with the request actually has an organization_id
attribute (there are certain cases, such as
if the request.user is a "django user"
where this attribute would not be present)
2022-12-05 17:01:49 +01:00
Vadim Stepanov
96868e1038
Add a temporary URL for the mobile app API 2022-11-29 16:46:51 +00:00
Vadim Stepanov
0985094112
Add a temporary URL for the mobile app API 2022-11-29 11:46:19 +00:00
Ildar Iskhakov
132cf1da7f
Add celery profiling (#913) 2022-11-29 16:20:41 +08:00
Vadim Stepanov
54d14d1025
Move MobileAppAuthToken view to mobile_app (#902) 2022-11-28 12:50:58 +00:00
Vadim Stepanov
255964ceaf
Mobile app messaging backend (#874)
* move mobile notifications to a separate backend, remove critical notification

* remove outdated mobile app code

* MOBILE_APP_PUSH_NOTIFICATIONS_ENABLED -> FEATURE_MOBILE_APP_INTEGRATION_ENABLED

* create error log if no devices are set up

* move mobile auth related code to the mobile_app Django app

* move mobile auth related code to the mobile_app Django app

* move mobile auth related code to the mobile_app Django app

* fix typing

* add GCMDevice todos

* add user connection capabilities

* add user connect/disconnect to the messaging backend

* move APNS endpoint to mobile_app Django app

* restore critical notifications

* support hackathon app

* tweak migrations so mobile app auth tokens are preserved

* reuse notify_by IDs

* use mobile app template to render push notification

* add GCM/FCM (Android) support

* fix unlink user

* logger.error -> logger.info
2022-11-23 15:56:43 +00:00
Yulya Artyukhina
381520ee13
Get rid of installation token + add a bunch of tests (#624)
* Get rid of installation token (for OSS installations)

This is done by being required to supply the grafana API URL as an
environment variable on the backend. Additionally, optionally an OnCall
API URL environment variable can be passed in to the frontend (this basically
allows completely skipping the need to configure anything).
- deduplicated a lot of the sync logic on the frontend + made
error message more useful and consistent
- Split PluginConfigPage component into several subcomponents
(making it easier to test each individual component)
- Moved RootWithLoader (from plugin/GrafanaPluginRootPage) into its own
subcomponent (making it easier to test)
- Added tests for pre-existing components that were touched:
  - PluginConfigPage component (and its new subcomponents)
  - state/plugin and state/rootBaseStore functions
  - apps.grafana_plugin django app

Helm changes:
- add GRAFANA_API_URL to oncall.env
- some yaml autoformatting changes
- remove reference to python manage.py issue_invite_for_the_frontend --override

Co-authored-by: Joey Orlando <joseph.t.orlando@gmail.com>
2022-11-21 16:26:00 +01:00
Ildar Iskhakov
d26f76fea0
Add new ci pipeline: helm chart end to end testing (#855) 2022-11-21 16:10:25 +01:00
Joey Orlando
fd4877408a
remove grafana_plugin_management django app (#812)
* remove grafana_plugin_management django app

it seems to be no longer used or referenced. In addition apps.api.serializers.organization.PluginOrganizationSerializer was only
referenced from within grafana_plugin_management and is thereby safe
to remove.
2022-11-09 13:53:59 +01:00
Michael Derynck
538e28e739 Check user attribute 2022-11-02 10:47:26 -06:00
Michael Derynck
c2546460f3 Add user and org id to log to make troubleshooting easier 2022-11-02 10:36:12 -06:00
Innokentii Konstantinov
4e8982cc81
Add management command to manually verify phone numbers (#743)
* Add cmd to manully verify phone numbers

* Rework verify_phone command
2022-11-01 16:38:04 +08:00
Joey Orlando
7974f32239
Revert "Revert "Remove migration-tool Django app and UI page (#708)"" 2022-10-27 15:42:21 +02:00
Joey Orlando
56c6a25dfb Revert "Remove migration-tool Django app and UI page (#708)"
This reverts commit 94934bf3e4.
2022-10-27 12:12:57 +02:00
Joey Orlando
94934bf3e4
Remove migration-tool Django app and UI page (#708)
* remove migration_tool app (#687)
* remove migration-tool page (#699)
2022-10-26 15:47:08 +02:00
Vadim Stepanov
e67d3519fe
Restore email notifications (#621)
* remove email verification related code

* remove email verification related code

* remove sendgrid callback

* remove sendgrid related code

* remove sendgrid related code

* rename sendgrid app to email

* remove email from built-in channels

* remove email from built-in channels

* remove email from built-in channels

* add email backend: https://github.com/grafana/oncall/pull/50

* add email templater

* add email templater

* convert md to html

* add email settings to live settings

* use task to send email, handle some exceptions to create logs

* remove ERROR_NOTIFICATION_MAIL_DELIVERY_FAILED usage

* add email limit logic

* fix tests

* add docs

* remove old email templates

* remove old email templates

* add template_fields to messaging backend

* add messaging backends templates to public api

* add comment for deprecated fields

* fix test

* fix tests

* disable email by default

* don't retry on SMTPException and TimeoutError

* add tests

* bring email back to public api docs

* return ERROR_NOTIFICATION_MAIL_LIMIT_EXCEEDED

* make template_fields tuple

* build_subject_and_title -> build_subject_and_message

* add one more comment about template deprecation

* use 8 as backend id

* add comment about gaierror and BadHeaderError

* add comment on importing in notify_user_async

* edit oss docs
2022-10-19 12:32:56 +01:00
Innokentii Konstantinov
587fdb441d Fix default value for OSS_INSTALLATION 2022-06-08 20:23:26 +04:00
Innokentii Konstantinov
e277534f32 Sync only admins and editors 2022-06-08 17:31:55 +04:00
Innokentii Konstantinov
829ed8230b Make CloudConnection instance wide 2022-06-06 16:02:09 +04:00
Michael Derynck
6b40f95033 World, meet OnCall!
Co-authored-by: Eve832 <eve.meelan@grafana.com>
    Co-authored-by: Francisco Montes de Oca <nevermind89x@gmail.com>
    Co-authored-by: Ildar Iskhakov <ildar.iskhakov@grafana.com>
    Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
    Co-authored-by: Julia <ferril.darkdiver@gmail.com>
    Co-authored-by: maskin25 <kengurek@gmail.com>
    Co-authored-by: Matias Bordese <mbordese@gmail.com>
    Co-authored-by: Matvey Kukuy <motakuk@gmail.com>
    Co-authored-by: Michael Derynck <michael.derynck@grafana.com>
    Co-authored-by: Richard Hartmann <richih@richih.org>
    Co-authored-by: Robby Milo <robbymilo@fastmail.com>
    Co-authored-by: Timur Olzhabayev <timur.olzhabayev@grafana.com>
    Co-authored-by: Vadim Stepanov <vadimkerr@gmail.com>
    Co-authored-by: Yulia Shanyrova <yulia.shanyrova@grafana.com>
2022-06-03 08:09:47 -06:00