Merge branch 'OSS-launch-doc-updates' into oncall-get-started
This commit is contained in:
commit
4bd984cadd
211 changed files with 4782 additions and 4608 deletions
|
|
@ -5,4 +5,5 @@ frontend/node_modules
|
|||
frontend/build
|
||||
package-lock.json
|
||||
./engine/extensions
|
||||
.env
|
||||
.env
|
||||
.env-hobby
|
||||
|
|
|
|||
19
.drone.yml
19
.drone.yml
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: Build and Release
|
||||
|
|
@ -30,8 +31,8 @@ steps:
|
|||
- yarn ci-build:finish
|
||||
- yarn ci-package
|
||||
- cd ci/dist
|
||||
- zip -r grafana-oncall-app-${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}.zip ./grafana-oncall-app
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app-${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
|
||||
- zip -r grafana-oncall-app.zip ./grafana-oncall-app
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
|
||||
|
||||
- name: Publish Plugin to GCS (release)
|
||||
image: plugins/gcs
|
||||
|
|
@ -156,8 +157,13 @@ services:
|
|||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
include:
|
||||
- tag
|
||||
- push
|
||||
ref:
|
||||
include:
|
||||
- refs/heads/**
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
---
|
||||
# Secret for pulling docker images.
|
||||
|
|
@ -227,4 +233,9 @@ get:
|
|||
name: machine-user-token
|
||||
path: infra/data/ci/drone
|
||||
kind: secret
|
||||
name: drone_token
|
||||
name: drone_token
|
||||
---
|
||||
kind: signature
|
||||
hmac: 5cdafa5ca416acb1763d1d9ac93bbd932982c874718f40af533914a6711c1a1f
|
||||
|
||||
...
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ DJANGO_SETTINGS_MODULE=settings.dev
|
|||
SECRET_KEY=jkashdkjashdkjh
|
||||
BASE_URL=http://localhost:8000
|
||||
|
||||
FEATURE_TELEGRAM_INTEGRATION_ENABLED=
|
||||
FEATURE_TELEGRAM_INTEGRATION_ENABLED=True
|
||||
FEATURE_SLACK_INTEGRATION_ENABLED=True
|
||||
FEATURE_EXTRA_MESSAGING_BACKENDS_ENABLED=
|
||||
|
||||
|
|
|
|||
42
.github/workflows/publish_docs.yml
vendored
42
.github/workflows/publish_docs.yml
vendored
|
|
@ -17,24 +17,24 @@ jobs:
|
|||
- name: Build Website
|
||||
run: |
|
||||
docker run -v ${PWD}/sources:/hugo/content/docs/amixr --rm grafana/docs-base:latest /bin/bash -c 'make hugo'
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync
|
||||
- name: publish-to-git
|
||||
uses: ./.github/actions/website-sync
|
||||
id: publish
|
||||
with:
|
||||
repository: grafana/website
|
||||
branch: master
|
||||
host: github.com
|
||||
github_pat: '${{ secrets.GH_BOT_ACCESS_TOKEN }}'
|
||||
source_folder: docs/sources
|
||||
target_folder: content/docs/amixr/v0.0.39
|
||||
- shell: bash
|
||||
run: |
|
||||
test -n "${{ steps.publish.outputs.commit_hash }}"
|
||||
test -n "${{ steps.publish.outputs.working_directory }}"
|
||||
# sync:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: test
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync
|
||||
# - name: publish-to-git
|
||||
# uses: ./.github/actions/website-sync
|
||||
# id: publish
|
||||
# with:
|
||||
# repository: grafana/website
|
||||
# branch: master
|
||||
# host: github.com
|
||||
# github_pat: '${{ secrets.GH_BOT_ACCESS_TOKEN }}'
|
||||
# source_folder: docs/sources
|
||||
# target_folder: content/docs/amixr/v0.0.39
|
||||
# - shell: bash
|
||||
# run: |
|
||||
# test -n "${{ steps.publish.outputs.commit_hash }}"
|
||||
# test -n "${{ steps.publish.outputs.working_directory }}"
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@
|
|||
*.pyc
|
||||
venv
|
||||
.env
|
||||
.env_hobby
|
||||
.vscode
|
||||
dump.rdb
|
||||
.idea
|
||||
|
|
|
|||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@grafana.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
184
DEVELOPER.md
184
DEVELOPER.md
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
1. Start stateful services (RabbitMQ, Redis, Grafana with mounted plugin folder)
|
||||
```bash
|
||||
docker-compose -f developer-docker-compose.yml up -d
|
||||
docker-compose -f docker-compose-developer.yml up -d
|
||||
```
|
||||
|
||||
2. Prepare a python environment:
|
||||
|
|
@ -53,13 +53,10 @@ export $(grep -v '^#' .env | xargs -0)
|
|||
# Hint: there is a known issue with uwsgi. It's not used in the local dev environment. Feel free to comment it in `engine/requirements.txt`.
|
||||
cd engine && pip install -r requirements.txt
|
||||
|
||||
# Create folder for database
|
||||
mkdir sqlite_data
|
||||
|
||||
# Migrate the DB:
|
||||
python manage.py migrate
|
||||
|
||||
# Create user for django admin panel:
|
||||
# Create user for django admin panel (if you need it):
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
|
|
@ -69,7 +66,7 @@ python manage.py createsuperuser
|
|||
# Http server:
|
||||
python manage.py runserver
|
||||
|
||||
# Worker for background tasks(run it in the parallel terminal, don't forget to export .env there)
|
||||
# Worker for background tasks (run it in the parallel terminal, don't forget to export .env there)
|
||||
python manage.py start_celery
|
||||
|
||||
# Additionally you could launch the worker with periodic tasks launcher (99% you don't need this)
|
||||
|
|
@ -107,7 +104,7 @@ python manage.py issue_invite_for_the_frontend --override
|
|||
OnCall API URL:
|
||||
http://host.docker.internal:8000
|
||||
|
||||
OnCall Invitation Token (Single use token to connect Grafana instance):
|
||||
Invitation Token (Single use token to connect Grafana instance):
|
||||
Response from the invite generator command (check above)
|
||||
|
||||
Grafana URL (URL OnCall will use to talk to Grafana instance):
|
||||
|
|
@ -119,7 +116,7 @@ host IP from inside the container by running:
|
|||
```bash
|
||||
/sbin/ip route|awk '/default/ { print $3 }'
|
||||
|
||||
# Alternatively add host.docker.internal as an extra_host for grafana in developer-docker-compose.yml
|
||||
# Alternatively add host.docker.internal as an extra_host for grafana in docker-compose-developer.yml
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
|
|
@ -127,161 +124,7 @@ extra_hosts:
|
|||
|
||||
### Slack application setup
|
||||
|
||||
This instruction is also applicable if you set up self-hosted OnCall.
|
||||
|
||||
1. Start a [localtunnel](https://github.com/localtunnel/localtunnel) reverse proxy to make oncall engine api accessible to slack (if you don't have OnCall backend accessible from https),
|
||||
```bash
|
||||
# Choose the unique prefix instead of pretty-turkey-83
|
||||
# Localtunnel will generate an url, e.g. https://pretty-turkey-83.loca.lt
|
||||
# it is referred as <ONCALL_ENGINE_PUBLIC_URL> below
|
||||
lt --port 8000 -s pretty-turkey-83 --print-requests
|
||||
```
|
||||
|
||||
2. [Create a Slack Workspace](https://slack.com/create) for development.
|
||||
|
||||
3. Go to https://api.slack.com/apps and click Create New App button
|
||||
|
||||
4. Select `From an app manifest` option and choose the right workspace
|
||||
|
||||
5. Copy and paste the following block with the correct <YOUR_BOT_NAME> and <ONCALL_ENGINE_PUBLIC_URL> fields
|
||||
|
||||
<details>
|
||||
<summary>Click to expand!</summary>
|
||||
|
||||
```yaml
|
||||
_metadata:
|
||||
major_version: 1
|
||||
minor_version: 1
|
||||
display_information:
|
||||
name: <YOUR_BOT_NAME>
|
||||
features:
|
||||
app_home:
|
||||
home_tab_enabled: true
|
||||
messages_tab_enabled: true
|
||||
messages_tab_read_only_enabled: false
|
||||
bot_user:
|
||||
display_name: <YOUR_BOT_NAME>
|
||||
always_online: true
|
||||
shortcuts:
|
||||
- name: Create a new incident
|
||||
type: message
|
||||
callback_id: incident_create
|
||||
description: Creates a new OnCall incident
|
||||
- name: Add to postmortem
|
||||
type: message
|
||||
callback_id: add_postmortem
|
||||
description: Add this message to postmortem
|
||||
slash_commands:
|
||||
- command: /oncall
|
||||
url: <ONCALL_ENGINE_PUBLIC_URL>/slack/interactive_api_endpoint/
|
||||
description: oncall
|
||||
should_escape: false
|
||||
oauth_config:
|
||||
redirect_urls:
|
||||
- <ONCALL_ENGINE_PUBLIC_URL>/api/internal/v1/complete/slack-install-free/
|
||||
- <ONCALL_ENGINE_PUBLIC_URL>/api/internal/v1/complete/slack-login/
|
||||
scopes:
|
||||
user:
|
||||
- channels:read
|
||||
- chat:write
|
||||
- identify
|
||||
- users.profile:read
|
||||
bot:
|
||||
- app_mentions:read
|
||||
- channels:history
|
||||
- channels:read
|
||||
- chat:write
|
||||
- chat:write.customize
|
||||
- chat:write.public
|
||||
- commands
|
||||
- files:write
|
||||
- groups:history
|
||||
- groups:read
|
||||
- im:history
|
||||
- im:read
|
||||
- im:write
|
||||
- mpim:history
|
||||
- mpim:read
|
||||
- mpim:write
|
||||
- reactions:write
|
||||
- team:read
|
||||
- usergroups:read
|
||||
- usergroups:write
|
||||
- users.profile:read
|
||||
- users:read
|
||||
- users:read.email
|
||||
- users:write
|
||||
settings:
|
||||
event_subscriptions:
|
||||
request_url: <ONCALL_ENGINE_PUBLIC_URL>/slack/event_api_endpoint/
|
||||
bot_events:
|
||||
- app_home_opened
|
||||
- app_mention
|
||||
- channel_archive
|
||||
- channel_created
|
||||
- channel_deleted
|
||||
- channel_rename
|
||||
- channel_unarchive
|
||||
- member_joined_channel
|
||||
- message.channels
|
||||
- message.im
|
||||
- subteam_created
|
||||
- subteam_members_changed
|
||||
- subteam_updated
|
||||
- user_change
|
||||
interactivity:
|
||||
is_enabled: true
|
||||
request_url: <ONCALL_ENGINE_PUBLIC_URL>/slack/interactive_api_endpoint/
|
||||
org_deploy_enabled: false
|
||||
socket_mode_enabled: false
|
||||
```
|
||||
</details>
|
||||
|
||||
6. Click `Install to workspace` button to generate the credentials
|
||||
|
||||
6. Populate the environment with variables related to Slack
|
||||
|
||||
In your `.env` file, fill out the following variables:
|
||||
|
||||
```
|
||||
SLACK_CLIENT_OAUTH_ID = Basic Information -> App Credentials -> Client ID
|
||||
SLACK_CLIENT_OAUTH_SECRET = Basic Information -> App Credentials -> Client Secret
|
||||
SLACK_API_TOKEN = OAuth & Permissions -> Bot User OAuth Token
|
||||
SLACK_INSTALL_RETURN_REDIRECT_HOST = https://pretty-turkey-83.loca.lt
|
||||
```
|
||||
|
||||
Don't forget to export variables from the `.env` file and restart the server!
|
||||
|
||||
7. Edit `grafana-plugin/grafana-plugin.yml` to set `onCallApiUrl` fields with localtunnel url
|
||||
```
|
||||
onCallApiUrl: https://pretty-turkey-83.loca.lt
|
||||
```
|
||||
|
||||
or set BASE_URL Env variable through web interface.
|
||||
|
||||
8. Edit grafana-plugin/src/plugin.json to add `Bypass-Tunnel-Reminder` header section for all existing routes
|
||||
> this headers required for the local development only, otherwise localtunnel blocks requests from grafana plugin
|
||||
|
||||
```
|
||||
{
|
||||
"path": ...,
|
||||
...
|
||||
"headers": [
|
||||
...
|
||||
{
|
||||
"name": "Bypass-Tunnel-Reminder",
|
||||
"content": "True"
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
9. Rebuild the plugin
|
||||
```
|
||||
yarn watch
|
||||
```
|
||||
10. Restart grafana instance
|
||||
|
||||
11. All set! Go to Slack and check if your application is functional.
|
||||
For Slack app configuration check our docs: https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
@ -383,3 +226,18 @@ pytest --ds=settings.dev
|
|||
- Set Settings to settings/dev.py
|
||||
5. Create a new Django Server run configuration to Run/Debug the engine
|
||||
- Use a plugin such as EnvFile to load the .env file
|
||||
|
||||
## Update drone build
|
||||
The .drone.yml build file must be signed when changes are made to it. Follow these steps:
|
||||
|
||||
If you have not installed drone CLI follow [these instructions](https://docs.drone.io/cli/install/)
|
||||
|
||||
To sign the .drone.yml file:
|
||||
```bash
|
||||
export DRONE_SERVER=https://drone.grafana.net
|
||||
|
||||
# Get your drone token from https://drone.grafana.net/account
|
||||
export DRONE_TOKEN=<Your DRONE_TOKEN>
|
||||
|
||||
drone sign --save grafana/oncall .drone.yml
|
||||
```
|
||||
|
|
|
|||
159
GOVERNANCE.md
Normal file
159
GOVERNANCE.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
title: Governance
|
||||
---
|
||||
|
||||
# Governance
|
||||
|
||||
This document describes the rules and governance of the project. It is meant to be followed by all the developers of the project and the OnCall community. Common terminology used in this governance document are listed below:
|
||||
|
||||
- **Team members**: Any members of the private [team mailing list][team].
|
||||
|
||||
- **Maintainers**: Maintainers lead an individual project or parts thereof ([`MAINTAINERS.md`][maintainers]).
|
||||
|
||||
- **Projects**: A single repository in the Grafana GitHub organization and listed below is referred to as a project:
|
||||
|
||||
- oncall
|
||||
|
||||
- **The OnCall project**: The sum of all activities performed under this governance, concerning one or more repositories or the community.
|
||||
|
||||
## Values
|
||||
|
||||
The OnCall developers and community are expected to follow the values defined in the [Code of Conduct][coc]. Furthermore, the OnCall community strives for kindness, giving feedback effectively, and building a welcoming environment. The OnCall developers generally decide by consensus and only resort to conflict resolution by a majority vote if consensus cannot be reached.
|
||||
|
||||
## Projects
|
||||
|
||||
Each project must have a [`MAINTAINERS.md`][maintainers] file with at least one maintainer. Where a project has a release process, access and documentation should be such that more than one person can perform a release. Releases should be announced on the [announcements][https://github.com/grafana/oncall/discussions/categories/announcements] category at the GitHub Discussions. Any new projects should be first proposed on the [team mailing list][team] following the voting procedures listed below.
|
||||
|
||||
## Decision making
|
||||
|
||||
### Team members
|
||||
|
||||
Team member status may be given to those who have made ongoing contributions to the OnCall project for at least 3 months. This is usually in the form of code improvements and/or notable work on documentation, but organizing events or user support could also be taken into account.
|
||||
|
||||
New members may be proposed by any existing member by email to the [team mailing list][team]. It is highly desirable to reach consensus about acceptance of a new member. However, the proposal is ultimately voted on by a formal [supermajority vote](#supermajority-vote).
|
||||
|
||||
If the new member proposal is accepted, the proposed team member should be contacted privately via email to confirm or deny their acceptance of team membership. This email will also be CC'd to the [team mailing list][team] for record-keeping purposes.
|
||||
|
||||
If they choose to accept, the [onboarding](#onboarding) procedure is followed.
|
||||
|
||||
Team members may retire at any time by emailing [the team][team].
|
||||
|
||||
Team members can be removed by [supermajority vote](#supermajority-vote) on [the team mailing list][team].
|
||||
For this vote, the member in question is not eligible to vote and does not count towards the quorum.
|
||||
Any removal vote can cover only one single person.
|
||||
|
||||
Upon death of a member, they leave the team automatically.
|
||||
|
||||
In case a member leaves, the [offboarding](#offboarding) procedure is applied.
|
||||
|
||||
The current team members are:
|
||||
|
||||
- Ildar Iskhakov — [@iskhakov](https://github.com/iskhakov) ([Grafana Labs](https://grafana.com/))
|
||||
- Innokentii Konstantinov — [@Konstantinov-Innokentii](https://github.com/Konstantinov-Innokentii) ([Grafana Labs](https://grafana.com/))
|
||||
- Matías Bordese — [@matiasb](https://github.com/matiasb) ([Grafana Labs](https://grafana.com/))
|
||||
- Matvey Kukuy — [@Matvey-Kuk](https://github.com/Matvey-Kuk) ([Grafana Labs](https://grafana.com/))
|
||||
- Michael Derynck — [@mderynck](https://github.com/mderynck) ([Grafana Labs](https://grafana.com/))
|
||||
- Vadim Stepanov — [@vadimkerr](https://github.com/vadimkerr) ([Grafana Labs](https://grafana.com/))
|
||||
- Yulia Shanyrova — [@Ukochka](https://github.com/Ukochka) ([Grafana Labs](https://grafana.com/))
|
||||
- Maxim Mordasov — [@maskin25](https://github.com/maskin25) ([Grafana Labs](https://grafana.com/))
|
||||
- Julia Artyukhina — [@Ferril](https://github.com/Ferril) ([Grafana Labs](https://grafana.com/))
|
||||
- Julia Artyukhina — [@Ferril](https://github.com/Ferril) ([Grafana Labs](https://grafana.com/))
|
||||
|
||||
Previous team members:
|
||||
|
||||
- n/a
|
||||
|
||||
### Maintainers
|
||||
|
||||
Maintainers lead one or more project(s) or parts thereof and serve as a point of conflict resolution amongst the contributors to this project. Ideally, maintainers are also team members, but exceptions are possible for suitable maintainers that, for whatever reason, are not yet team members.
|
||||
|
||||
Changes in maintainership have to be announced on the [announcemount][https://github.com/grafana/oncall/discussions/categories/announcements] category at the GitHub Discussions. They are decided by [rough consensus](#consensus) and formalized by changing the [`MAINTAINERS.md`][maintainers] file of the respective repository.
|
||||
|
||||
Maintainers are granted commit rights to all projects covered by this governance.
|
||||
|
||||
A maintainer or committer may resign by notifying the [team mailing list][team]. A maintainer with no project activity for a year is considered to have resigned. Maintainers that wish to resign are encouraged to propose another team member to take over the project.
|
||||
|
||||
A project may have multiple maintainers, as long as the responsibilities are clearly agreed upon between them. This includes coordinating who handles which issues and pull requests.
|
||||
|
||||
### Technical decisions
|
||||
|
||||
Technical decisions that only affect a single project are made informally by the maintainer of this project, and [rough consensus](#consensus) is assumed. Technical decisions that span multiple parts of the project should be discussed and made on the the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
Decisions are usually made by [rough consensus](#consensus). If no consensus can be reached, the matter may be resolved by [majority vote](#majority-vote).
|
||||
|
||||
### Governance changes
|
||||
|
||||
Changes to this document are made by Grafana Labs.
|
||||
|
||||
### Other matters
|
||||
|
||||
Any matter that needs a decision may be called to a vote by any member if they deem it necessary. For private or personnel matters, discussion and voting takes place on the [team mailing list][team], otherwise on the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
## Voting
|
||||
|
||||
The OnCall project usually runs by informal consensus, however sometimes a formal decision must be made.
|
||||
|
||||
Depending on the subject matter, as laid out [above](#decision-making), different methods of voting are used.
|
||||
|
||||
For all votes, voting must be open for at least one week. The end date should be clearly stated in the call to vote. A vote may be called and closed early if enough votes have come in one way so that further votes cannot change the final decision.
|
||||
|
||||
In all cases, all and only [team members](#team-members) are eligible to vote, with the sole exception of the forced removal of a team member, in which said member is not eligible to vote.
|
||||
|
||||
Discussion and votes on personnel matters (including but not limited to team membership and maintainership) are held in private on the [team mailing list][team]. All other discussion and votes are held in public on the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
For public discussions, anyone interested is encouraged to participate. Formal power to object or vote is limited to [team members](#team-members).
|
||||
|
||||
### Consensus
|
||||
|
||||
The default decision making mechanism for the OnCall project is [rough][rough] consensus. This means that any decision on technical issues is considered supported by the [team][team] as long as nobody objects or the objection has been considered but not necessarily accommodated.
|
||||
|
||||
Silence on any consensus decision is implicit agreement and equivalent to explicit agreement. Explicit agreement may be stated at will. Decisions may, but do not need to be called out and put up for decision on the [GitHub Discussions][https://github.com/grafana/oncall/discussions] at any time and by anyone.
|
||||
|
||||
Consensus decisions can never override or go against the spirit of an earlier explicit vote.
|
||||
|
||||
If any [team member](#team-members) raises objections, the team members work together towards a solution that all involved can accept. This solution is again subject to rough consensus.
|
||||
|
||||
In case no consensus can be found, but a decision one way or the other must be made, any [team member](#team-members) may call a formal [majority vote](#majority-vote).
|
||||
|
||||
### Majority vote
|
||||
|
||||
Majority votes must be called explicitly in a separate thread on the appropriate mailing list. The subject must be prefixed with `[VOTE]`. In the body, the call to vote must state the proposal being voted on. It should reference any discussion leading up to this point.
|
||||
|
||||
Votes may take the form of a single proposal, with the option to vote yes or no, or the form of multiple alternatives.
|
||||
|
||||
A vote on a single proposal is considered successful if more vote in favor than against.
|
||||
|
||||
If there are multiple alternatives, members may vote for one or more alternatives, or vote “no” to object to all alternatives. It is not possible to cast an “abstain” vote. A vote on multiple alternatives is considered decided in favor of one alternative if it has received the most votes in favor, and a vote from more than half of those voting. Should no alternative reach this quorum, another vote on a reduced number of options may be called separately.
|
||||
|
||||
### Supermajority vote
|
||||
|
||||
Supermajority votes must be called explicitly in a separate thread on the appropriate mailing list. The subject must be prefixed with `[VOTE]`. In the body, the call to vote must state the proposal being voted on. It should reference any discussion leading up to this point.
|
||||
|
||||
Votes may take the form of a single proposal, with the option to vote yes or no, or the form of multiple alternatives.
|
||||
|
||||
A vote on a single proposal is considered successful if at least two thirds of those eligible to vote vote in favor.
|
||||
|
||||
If there are multiple alternatives, members may vote for one or more alternatives, or vote “no” to object to all alternatives. A vote on multiple alternatives is considered decided in favor of one alternative if it has received the most votes in favor, and a vote from at least two thirds of those eligible to vote. Should no alternative reach this quorum, another vote on a reduced number of options may be called separately.
|
||||
|
||||
## On- / Offboarding
|
||||
|
||||
### Onboarding
|
||||
|
||||
The new member is
|
||||
|
||||
- added to the list of [team members](#team-members). Ideally by sending a PR of their own, at least approving said PR.
|
||||
- announced on the [GitHub Discussions][https://github.com/grafana/oncall/discussions] by an existing team member. Ideally, the new member replies in this thread, acknowledging team membership.
|
||||
- added to the projects with commit rights.
|
||||
- added to the [team mailing list][team].
|
||||
|
||||
### Offboarding
|
||||
|
||||
The ex-member is
|
||||
|
||||
- removed from the list of [team members](#team-members). Ideally by sending a PR of their own, at least approving said PR. In case of forced removal, no approval is needed.
|
||||
- removed from the projects. Optionally, they can retain maintainership of one or more repositories if the [team](#team-members) agrees.
|
||||
- removed from the team mailing list and demoted to a normal member of the other mailing lists.
|
||||
- not allowed to call themselves an active team member any more, nor allowed to imply this to be the case.
|
||||
- added to a list of previous members if they so choose.
|
||||
|
||||
If needed, we reserve the right to publicly announce removal.
|
||||
|
|
@ -9,9 +9,11 @@ The default license for this project is [AGPL-3.0-only](LICENSE).
|
|||
The following directories and their subdirectories are licensed under Apache-2.0:
|
||||
|
||||
```
|
||||
n/a
|
||||
```
|
||||
|
||||
The following directories and their subdirectories are licensed under their original upstream licenses:
|
||||
|
||||
```
|
||||
n/a
|
||||
```
|
||||
|
|
|
|||
14
MAINTAINERS.md
Normal file
14
MAINTAINERS.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
The following are the main/default maintainers:
|
||||
|
||||
- Ildar Iskhakov — [@iskhakov](https://github.com/iskhakov) ([Grafana Labs](https://grafana.com/))
|
||||
- Matvey Kukuy — [@Matvey-Kuk](https://github.com/Matvey-Kuk) ([Grafana Labs](https://grafana.com/))
|
||||
|
||||
Some parts of the codebase have other maintainers, the package paths also include all sub-packages:
|
||||
|
||||
n/a
|
||||
|
||||
For the sake of brevity, not all subtrees are explicitly listed. Due to the
|
||||
size of this repository, the natural changes in focus of maintainers over time,
|
||||
and nuances of where particular features live, this list will always be
|
||||
incomplete and out of date. However the listed maintainer(s) should be able to
|
||||
direct a PR/question to the right person.
|
||||
91
README.md
91
README.md
|
|
@ -1,76 +1,61 @@
|
|||
# Grafana OnCall Incident Response
|
||||
Grafana OnCall, cloud version of Grafana OnCall: https://grafana.com/products/cloud/
|
||||
<img width="400px" src="docs/img/logo.png">
|
||||
|
||||
Developer-friendly, incident response management with brilliant Slack integration.
|
||||
- Connect monitoring systems
|
||||
- Collect and analyze data
|
||||
- On-call rotation
|
||||
- Automatic escalation
|
||||
- Never miss alerts with calls and SMS
|
||||
Developer-friendly, incident response with brilliant Slack integration.
|
||||
|
||||

|
||||
<img width="60%" src="screenshot.png">
|
||||
|
||||
- Collect and analyze alerts from multiple monitoring systems
|
||||
- On-call rotations based on schedules
|
||||
- Automatic escalations
|
||||
- Phone calls, SMS, Slack, Telegram notifications
|
||||
|
||||
## Getting Started
|
||||
OnCall consists of two parts:
|
||||
1. OnCall backend
|
||||
2. "Grafana OnCall" plugin you need to install in your Grafana
|
||||
|
||||
### How to run OnCall backend
|
||||
1. An all-in-one image of OnCall is available on docker hub to run it:
|
||||
We prepared multiple environments: [production](https://grafana.com/docs/grafana-cloud/oncall/open-source/#production-environment), [developer](DEVELOPER.md) and hobby:
|
||||
|
||||
1. Download docker-compose.yaml:
|
||||
```bash
|
||||
docker run -it --name oncall-backend -p 8000:8000 grafana/oncall-all-in-one
|
||||
curl https://github.com/grafana/oncall/blob/dev/docker-compose.yml -o docker-compose.yaml
|
||||
```
|
||||
|
||||
2. When the image starts up you will see a message like this:
|
||||
2. Set variables:
|
||||
```bash
|
||||
👋 This script will issue an invite token to securely connect the frontend.
|
||||
Maintainers will be happy to help in the slack channel #grafana-oncall: https://slack.grafana.com/
|
||||
Your invite token: <TOKEN>, use it in the Grafana OnCall plugin.
|
||||
echo "DOMAIN=http://localhost
|
||||
SECRET_KEY=my_random_secret_must_be_more_than_32_characters_long
|
||||
RABBITMQ_PASSWORD=rabbitmq_secret_pw
|
||||
MYSQL_PASSWORD=mysql_secret_pw
|
||||
COMPOSE_PROFILES=with_grafana # Remove this line if you want to use existing grafana
|
||||
GRAFANA_USER=admin
|
||||
GRAFANA_PASSWORD=admin" > .env_hobby
|
||||
```
|
||||
|
||||
3. If you started your container detached with -d check the log:
|
||||
3. Launch services:
|
||||
```bash
|
||||
docker logs oncall-backend
|
||||
docker-compose --env-file .env_hobby -f docker-compose.yml up --build -d
|
||||
```
|
||||
|
||||
### How to install "Grafana OnCall" Plugin and connect with a backend
|
||||
1. Open Grafana in your browser and login as an Admin
|
||||
2. Navigate to Configuration → Plugins
|
||||
3. Type Grafana OnCall into the "Search Grafana plugins" field
|
||||
4. Select the Grafana OnCall plugin and press the "Install" button
|
||||
5. On the Grafana OnCall Plugin page Enable the plugin and go to the Configuration tab you should see a status field with the message
|
||||
```
|
||||
OnCall has not been setup, configure & initialize below.
|
||||
```
|
||||
6. Fill in configuration fields using the token you got from the backend earlier, then press "Install Configuration"
|
||||
```
|
||||
OnCall API URL: (The URL & port used to access OnCall)
|
||||
http://host.docker.internal:8000
|
||||
|
||||
OnCall Invitation Token (Single use token to connect Grafana instance):
|
||||
Invitation token from docker startup
|
||||
|
||||
Grafana URL (URL OnCall will use to talk to this Grafana instance):
|
||||
http://localhost:3000 (or http://host.docker.internal:3000 if your grafana is running in Docker locally)
|
||||
4. Issue one-time invite token:
|
||||
```bash
|
||||
docker-compose --env-file .env_hobby -f docker-compose.yml run engine python manage.py issue_invite_for_the_frontend --override
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
- `#grafana-oncall` channel at https://slack.grafana.com/
|
||||
- Grafana Labs community forum for OnCall: https://community.grafana.com
|
||||
- File an [issue](https://github.com/grafana/oncall/issues) for bugs, issues and feature suggestions.
|
||||
5. Go to [OnCall Plugin Configuration](http://localhost:3000/plugins/grafana-oncall-app) (or find OnCall plugin in configuration->plugins) and connect OnCall _plugin_ with OnCall _backend_:
|
||||
```
|
||||
Invite token: ^^^ from the previous step.
|
||||
OnCall backend URL: http://engine:8080
|
||||
Grafana Url: http://grafana:3000
|
||||
```
|
||||
|
||||
## Production Setup
|
||||
6. Enjoy!
|
||||
|
||||
Looking for the production instructions? We're going to release them soon. Please join our Slack channel to be the first to know about them.
|
||||
|
||||
## Join community
|
||||
|
||||
<a href="https://github.com/grafana/oncall/discussions/categories/community-calls"><img width="200px" src="docs/img/community_call.png"></a>
|
||||
<a href="https://github.com/grafana/oncall/discussions"><img width="200px" src="docs/img/GH_discussions.png"></a>
|
||||
<a href="https://slack.grafana.com/"><img width="200px" src="docs/img/slack.png"></a>
|
||||
|
||||
## Further Reading
|
||||
- *Documentation* - [Grafana OnCall](https://grafana.com/docs/grafana-cloud/oncall/)
|
||||
- *Blog Post* - [Announcing Grafana OnCall, the easiest way to do on-call management](https://grafana.com/blog/2021/11/09/announcing-grafana-oncall/)
|
||||
- *Presentation* - [Deep dive into the Grafana, Prometheus, and Alertmanager stack for alerting and on-call management](https://grafana.com/go/observabilitycon/2021/alerting/?pg=blog)
|
||||
|
||||
## FAQ
|
||||
|
||||
- How do I generate a new invitation token to connect plugin with a backend?
|
||||
```bash
|
||||
docker exec oncall-backend python manage.py issue_invite_for_the_frontend --override
|
||||
```
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ services:
|
|||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: local_dev_pwd
|
||||
MYSQL_ROOT_PASSWORD: empty
|
||||
MYSQL_DATABASE: oncall_local_dev
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||
|
|
@ -42,13 +42,13 @@ services:
|
|||
mysql-to-create-grafana-db:
|
||||
image: mariadb:10.2
|
||||
platform: linux/x86_64
|
||||
command: bash -c "mysql -h mysql -uroot -plocal_dev_pwd -e 'CREATE DATABASE IF NOT EXISTS grafana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"
|
||||
command: bash -c "mysql -h mysql -uroot -pempty -e 'CREATE DATABASE IF NOT EXISTS grafana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
||||
grafana:
|
||||
image: "grafana/grafana:8.3.2"
|
||||
image: "grafana/grafana:9.0.0-beta3"
|
||||
restart: always
|
||||
mem_limit: 500m
|
||||
cpus: 0.5
|
||||
|
|
@ -56,7 +56,7 @@ services:
|
|||
GF_DATABASE_TYPE: mysql
|
||||
GF_DATABASE_HOST: mysql
|
||||
GF_DATABASE_USER: root
|
||||
GF_DATABASE_PASSWORD: local_dev_pwd
|
||||
GF_DATABASE_PASSWORD: empty
|
||||
GF_SECURITY_ADMIN_USER: oncall
|
||||
GF_SECURITY_ADMIN_PASSWORD: oncall
|
||||
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
|
||||
174
docker-compose.yml
Normal file
174
docker-compose.yml
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
services:
|
||||
engine:
|
||||
# TODO: change to the public image once it's public
|
||||
# image: ...
|
||||
build: engine
|
||||
ports:
|
||||
- 8080:8080
|
||||
command: >
|
||||
sh -c "uwsgi --ini uwsgi.ini"
|
||||
environment:
|
||||
BASE_URL: $DOMAIN
|
||||
SECRET_KEY: $SECRET_KEY
|
||||
RABBITMQ_USERNAME: "rabbitmq"
|
||||
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD
|
||||
RABBITMQ_HOST: "rabbitmq"
|
||||
RABBITMQ_PORT: "5672"
|
||||
RABBITMQ_DEFAULT_VHOST: "/"
|
||||
MYSQL_PASSWORD: $MYSQL_PASSWORD
|
||||
MYSQL_DB_NAME: oncall_hobby
|
||||
MYSQL_USER: ${MYSQL_USER:-root}
|
||||
MYSQL_HOST: ${MYSQL_HOST:-mysql}
|
||||
MYSQL_PORT: 3306
|
||||
REDIS_URI: redis://redis:6379/0
|
||||
DJANGO_SETTINGS_MODULE: settings.hobby
|
||||
OSS: "True"
|
||||
CELERY_WORKER_QUEUE: "default,critical,long,slack,telegram,webhook,retry,celery"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
oncall_db_migration:
|
||||
condition: service_completed_successfully
|
||||
rabbitmq:
|
||||
condition: service_started
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
celery:
|
||||
# TODO: change to the public image once it's public
|
||||
build: engine
|
||||
command: sh -c "./celery_with_exporter.sh"
|
||||
environment:
|
||||
BASE_URL: $DOMAIN
|
||||
SECRET_KEY: $SECRET_KEY
|
||||
RABBITMQ_USERNAME: "rabbitmq"
|
||||
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD
|
||||
RABBITMQ_HOST: "rabbitmq"
|
||||
RABBITMQ_PORT: "5672"
|
||||
RABBITMQ_DEFAULT_VHOST: "/"
|
||||
MYSQL_PASSWORD: $MYSQL_PASSWORD
|
||||
MYSQL_DB_NAME: oncall_hobby
|
||||
MYSQL_USER: ${MYSQL_USER:-root}
|
||||
MYSQL_HOST: ${MYSQL_HOST:-mysql}
|
||||
MYSQL_PORT: 3306
|
||||
REDIS_URI: redis://redis:6379/0
|
||||
DJANGO_SETTINGS_MODULE: settings.hobby
|
||||
OSS: "True"
|
||||
CELERY_WORKER_QUEUE: "default,critical,long,slack,telegram,webhook,retry,celery"
|
||||
CELERY_WORKER_CONCURRENCY: "1"
|
||||
CELERY_WORKER_MAX_TASKS_PER_CHILD: "100"
|
||||
CELERY_WORKER_SHUTDOWN_INTERVAL: "65m"
|
||||
CELERY_WORKER_BEAT_ENABLED: "True"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
oncall_db_migration:
|
||||
condition: service_completed_successfully
|
||||
rabbitmq:
|
||||
condition: service_started
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
oncall_db_migration:
|
||||
build: engine
|
||||
command: python manage.py migrate --noinput
|
||||
environment:
|
||||
BASE_URL: $DOMAIN
|
||||
SECRET_KEY: $SECRET_KEY
|
||||
RABBITMQ_USERNAME: "rabbitmq"
|
||||
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD
|
||||
RABBITMQ_HOST: "rabbitmq"
|
||||
RABBITMQ_PORT: "5672"
|
||||
RABBITMQ_DEFAULT_VHOST: "/"
|
||||
MYSQL_PASSWORD: $MYSQL_PASSWORD
|
||||
MYSQL_DB_NAME: oncall_hobby
|
||||
MYSQL_USER: ${MYSQL_USER:-root}
|
||||
MYSQL_HOST: ${MYSQL_HOST:-mysql}
|
||||
MYSQL_PORT: 3306
|
||||
REDIS_URI: redis://redis:6379/0
|
||||
DJANGO_SETTINGS_MODULE: settings.hobby
|
||||
OSS: "True"
|
||||
CELERY_WORKER_QUEUE: "default,critical,long,slack,telegram,webhook,retry,celery"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
platform: linux/x86_64
|
||||
mem_limit: 500m
|
||||
cpus: 0.5
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
- dbdata:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: $MYSQL_PASSWORD
|
||||
MYSQL_DATABASE: oncall_hobby
|
||||
healthcheck:
|
||||
test: "mysql -uroot -p$MYSQL_PASSWORD oncall_hobby -e 'select 1'"
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
mem_limit: 100m
|
||||
cpus: 0.1
|
||||
restart: always
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
rabbitmq:
|
||||
image: "rabbitmq:3.7.15-management"
|
||||
hostname: rabbitmq
|
||||
mem_limit: 1000m
|
||||
cpus: 0.5
|
||||
volumes:
|
||||
- rabbitmqdata:/var/lib/rabbitmq
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: "rabbitmq"
|
||||
RABBITMQ_DEFAULT_PASS: $RABBITMQ_PASSWORD
|
||||
RABBITMQ_DEFAULT_VHOST: "/"
|
||||
|
||||
mysql_to_create_grafana_db:
|
||||
image: mysql:5.7
|
||||
platform: linux/x86_64
|
||||
command: bash -c "mysql -h ${MYSQL_HOST:-mysql} -uroot -p${MYSQL_PASSWORD:?err} -e 'CREATE DATABASE IF NOT EXISTS grafana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
profiles:
|
||||
- with_grafana
|
||||
|
||||
grafana:
|
||||
image: "grafana/grafana:9.0.0-beta3"
|
||||
mem_limit: 500m
|
||||
ports:
|
||||
- 3000:3000
|
||||
cpus: 0.5
|
||||
environment:
|
||||
GF_DATABASE_TYPE: mysql
|
||||
GF_DATABASE_HOST: ${MYSQL_HOST:-mysql}
|
||||
GF_DATABASE_USER: ${MYSQL_USER:-root}
|
||||
GF_DATABASE_PASSWORD: ${MYSQL_PASSWORD:?err}
|
||||
GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:?err}
|
||||
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
|
||||
GF_INSTALL_PLUGINS: grafana-oncall-app
|
||||
volumes:
|
||||
- ./grafana-plugin:/var/lib/grafana/plugins/grafana-plugin
|
||||
depends_on:
|
||||
mysql_to_create_grafana_db:
|
||||
condition: service_completed_successfully
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
profiles:
|
||||
- with_grafana
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
rabbitmqdata:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
IMAGE = grafana/docs-base:latest
|
||||
CONTENT_PATH = /hugo/content/docs/amixr/latest
|
||||
CONTENT_PATH = /hugo/content/docs/oncall/latest
|
||||
PORT = 3002:3002
|
||||
|
||||
.PHONY: pull
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ Source for documentation at https://grafana.com/docs/amixr/
|
|||
|
||||
## Preview the website
|
||||
|
||||
Run `make docs`. This launches a preview of the website with the current grafana docs at `http://localhost:3002/docs/amixr/` which will refresh automatically when changes are made to content in the `sources` directory.
|
||||
Run `make docs`. This launches a preview of the website with the current grafana docs at `http://localhost:3002/docs/oncall/latest/` which will refresh automatically when changes are made to content in the `sources` directory.
|
||||
Make sure Docker is running.
|
||||
|
|
|
|||
BIN
docs/img/GH_discussions.png
Normal file
BIN
docs/img/GH_discussions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
docs/img/community_call.png
Normal file
BIN
docs/img/community_call.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/img/logo.png
Normal file
BIN
docs/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
docs/img/slack.png
Normal file
BIN
docs/img/slack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
|
|
@ -1,9 +1,18 @@
|
|||
+++
|
||||
title = "Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "OnCall", "irm"]
|
||||
weight = 1000
|
||||
aliases = ["/docs/grafana-cloud/oncall/"]
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/
|
||||
- /docs/oncall/latest/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- OnCall
|
||||
- irm
|
||||
title: Grafana OnCall
|
||||
weight: 1000
|
||||
---
|
||||
|
||||
# Grafana OnCall
|
||||
|
||||
|
|
@ -13,4 +22,4 @@ When you integrate an alert monitoring system with Grafana OnCall, the alerts wi
|
|||
|
||||
Follow these links to learn more:
|
||||
|
||||
{{< section >}}
|
||||
{{< section >}}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
+++
|
||||
title = "Configure and manage on-call schedules"
|
||||
description = ""
|
||||
keywords = ["Grafana", "oncall", "on-call", "calendar"]
|
||||
aliases = []
|
||||
weight = 900
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/calendar-schedules/
|
||||
description: ""
|
||||
keywords:
|
||||
- Grafana
|
||||
- oncall
|
||||
- on-call
|
||||
- calendar
|
||||
title: Configure and manage on-call schedules
|
||||
weight: 900
|
||||
---
|
||||
|
||||
# Configure and manage on-call schedules
|
||||
|
||||
|
|
@ -20,26 +25,26 @@ When you create a schedule, you will be able to select a Slack channel, associat
|
|||
|
||||
## Create an on-call schedule calendar
|
||||
|
||||
Create a primary calendar and an optional override calendar to schedule on-call shifts for team members.
|
||||
Create a primary calendar and an optional override calendar to schedule on-call shifts for team members.
|
||||
|
||||
1. In the **Scheduling** section of Grafana OnCall, click **+ Create schedule**.
|
||||
|
||||
1. Give the schedule a name.
|
||||
1. Give the schedule a name.
|
||||
|
||||
1. Create a new calendar in your calendar service and locate the secret iCal URL. For example, in a Google calendar, this URL can be found in **Settings > Settings for my calendars > Integrate calendar**.
|
||||
|
||||
1. Copy the secret iCal URL. In OnCall, paste it into the **Primary schedule for iCal URL** field.
|
||||
The permissions you set when you create the calendar determine who can modify the calendar.
|
||||
1. Copy the secret iCal URL. In OnCall, paste it into the **Primary schedule for iCal URL** field.
|
||||
The permissions you set when you create the calendar determine who can modify the calendar.
|
||||
|
||||
1. Click **Create Schedule**.
|
||||
|
||||
1. Schedule on-call times for team members.
|
||||
|
||||
Use the Grafana username of team members as the event name to schedule their on-call times. You can take advantage of all of the features of your calendar service.
|
||||
Use the Grafana username of team members as the event name to schedule their on-call times. You can take advantage of all of the features of your calendar service.
|
||||
|
||||
1. Create overlapping schedules (optional).
|
||||
1. Create overlapping schedules (optional).
|
||||
|
||||
When you create schedules that overlap, you can prioritize a schedule by adding a level marker. For example, if users AliceGrafana and BobGrafana have overlapping schedules, but BobGrafana is the primary contact, you would name his event `[L1] BobGrafana`, AliceGrafana maintains the default `[L0]` status, and would not receive notifications during the overlapping time. You can prioritize up to and including a level 9 prioritization, or `[L9]`.
|
||||
When you create schedules that overlap, you can prioritize a schedule by adding a level marker. For example, if users AliceGrafana and BobGrafana have overlapping schedules, but BobGrafana is the primary contact, you would name his event `[L1] BobGrafana`, AliceGrafana maintains the default `[L0]` status, and would not receive notifications during the overlapping time. You can prioritize up to and including a level 9 prioritization, or `[L9]`.
|
||||
|
||||
# Create an override calendar (optional)
|
||||
|
||||
|
|
@ -47,10 +52,10 @@ You can use an override calendar to allow team members to schedule on-call dutie
|
|||
|
||||
1. Create a new calendar using the same calendar service you used to create the primary calendar.
|
||||
|
||||
Be sure to set permissions that allow team members to edit the calendar.
|
||||
Be sure to set permissions that allow team members to edit the calendar.
|
||||
|
||||
1. In the scheduling section of Grafana OnCall, select the primary calendar you want to override.
|
||||
1. In the scheduling section of Grafana OnCall, select the primary calendar you want to override.
|
||||
|
||||
1. Click **Edit**.
|
||||
1. Click **Edit**.
|
||||
|
||||
1. Enter the secret iCal URL in the **Overrides schedule iCal URL** field and click **Update**.
|
||||
1. Enter the secret iCal URL in the **Overrides schedule iCal URL** field and click **Update**.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
+++
|
||||
title = "Connect ChatOps to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "slack"]
|
||||
weight = 700
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/chat-options/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- slack
|
||||
title: Connect ChatOps to Grafana OnCall
|
||||
weight: 700
|
||||
---
|
||||
|
||||
# Connect ChatOps to Grafana OnCall
|
||||
# Connect ChatOps to Grafana OnCall
|
||||
|
||||
Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../integrations/webhooks/configure-outgoing-webhooks.md" >}}).
|
||||
|
||||
To configure supported messaging apps, see the following topics:
|
||||
|
||||
{{< section >}}
|
||||
{{< section >}}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,30 @@
|
|||
+++
|
||||
title = "Connect Slack to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "slack"]
|
||||
weight = 100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/chat-options/configure-slack/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- slack
|
||||
title: Connect Slack to Grafana OnCall
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Connect Slack to Grafana OnCall
|
||||
Grafana OnCall integrates closely with your Slack workspace to deliver alert group notifications to individuals, groups, and team members.
|
||||
|
||||
Grafana OnCall integrates closely with your Slack workspace to deliver alert group notifications to individuals, groups, and team members.
|
||||
|
||||
## Connect to Slack
|
||||
|
||||
Connect your organization's Slack workspace to your Grafana OnCall instance.
|
||||
|
||||
>**NOTE:** Only Grafana users with the administrator role can configure OnCall settings.
|
||||
> **NOTE:** Only Grafana users with the administrator role can configure OnCall settings.
|
||||
|
||||
1. In OnCall, click on the **ChatOps** tab and select Slack in the side menu.
|
||||
1. Click **Install Slack integration**.
|
||||
1. Click **Install Slack integration**.
|
||||
1. Read the notice and click the button to proceed to the Slack website.
|
||||
1. Sign in to your organization's workspace.
|
||||
1. Click **Allow** to allow OnCall to access Slack.
|
||||
|
|
@ -22,12 +32,13 @@ Connect your organization's Slack workspace to your Grafana OnCall instance.
|
|||
|
||||
## Configure Slack in OnCall
|
||||
|
||||
In the Slack settings for Grafana OnCall, administrators can set a default Slack channel for notifications and opt to set reminders for acknowledged alerts that can timeout and revert an alert group to the unacknowledged state.
|
||||
In the Slack settings for Grafana OnCall, administrators can set a default Slack channel for notifications and opt to set reminders for acknowledged alerts that can timeout and revert an alert group to the unacknowledged state.
|
||||
|
||||
1. In OnCall, click on the **ChatOps** tab and select Slack in the side menu.
|
||||
1. In the first dropdown menu, select a default Slack channel.
|
||||
When you set up escalation policies to notify Slack channels of incoming alerts, the default will be the one you set here. You will still have the option to select from all the channels available in your organization.
|
||||
When you set up escalation policies to notify Slack channels of incoming alerts, the default will be the one you set here. You will still have the option to select from all the channels available in your organization.
|
||||
1. In **Additional settings** you can choose how to remind users of acknowledged but unresolved alert groups. You can also select whether and or when to automatically revoke the "acknowledged" status from an alert group to an unacknowledged state.
|
||||
|
||||
## Slack settings for on-call calendar scheduling notifications
|
||||
Admins can configure settings in Slack to notify people and groups about on-call schedules. When an on-call shift notification is sent to a person or channel, click the gear button to access **Notification preferences**. Use the options to configure the behavior of future shift notifications.
|
||||
|
||||
Admins can configure settings in Slack to notify people and groups about on-call schedules. When an on-call shift notification is sent to a person or channel, click the gear button to access **Notification preferences**. Use the options to configure the behavior of future shift notifications.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
+++
|
||||
title = "Connect Telegram to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "telegram"]
|
||||
weight = 300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/chat-options/configure-telegram/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- telegram
|
||||
title: Connect Telegram to Grafana OnCall
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Connect Telegram to Grafana OnCall
|
||||
|
||||
You can use Telegram to deliver alert group notifications to a dedicated channel, and allow users to perform notification actions.
|
||||
You can use Telegram to deliver alert group notifications to a dedicated channel, and allow users to perform notification actions.
|
||||
|
||||
Each alert group notification is assigned a dedicated discussion. Users can perform notification actions (acknowledge, resolve, silence), create reports, and discuss alerts in the comments section of the discussions.
|
||||
|
||||
|
|
@ -14,19 +23,19 @@ Each alert group notification is assigned a dedicated discussion. Users can perf
|
|||
|
||||
Connect your organization's Telegram account to your Grafana OnCall instance by following the instructions provided in OnCall. You can use the following steps as a reference.
|
||||
|
||||
>**NOTE:** Only Grafana users with the administrator role can configure OnCall settings.
|
||||
> **NOTE:** Only Grafana users with the administrator role can configure OnCall settings.
|
||||
|
||||
1. In OnCall, click on the **ChatOps** tab and select Telegram in the side menu.
|
||||
1. Click **Connect Telegram channel** and follow the instructions, mirrored here for reference. A unique verification code will be generated that you must use to activate the channel.
|
||||
1. In your team Telegram account, create a new channel, and set it to **Private**.
|
||||
1. In your team Telegram account, create a new channel, and set it to **Private**.
|
||||
1. In **Manage Channel**, make sure **Sign messages** is enabled.
|
||||
1. Create a new discussion group.
|
||||
This group handles alert actions and comments.
|
||||
1. Add the discussion group to the channel.
|
||||
In **Manage Channel**, click **Discussion** to find and add the new group.
|
||||
1. In OnCall, click the link to the OnCall bot to add it to your contacts.
|
||||
1. In Telegram, add the bot to your channel as an Admin. Allow it to **Post Messages**.
|
||||
1. Add the bot to the discussion group.
|
||||
This group handles alert actions and comments.
|
||||
1. Add the discussion group to the channel.
|
||||
In **Manage Channel**, click **Discussion** to find and add the new group.
|
||||
1. In OnCall, click the link to the OnCall bot to add it to your contacts.
|
||||
1. In Telegram, add the bot to your channel as an Admin. Allow it to **Post Messages**.
|
||||
1. Add the bot to the discussion group.
|
||||
1. In OnCall, send the provided verification code to the channel.
|
||||
1. Make sure users connect to Telegram in their OnCall user profile.
|
||||
|
||||
|
|
@ -36,4 +45,4 @@ Connect your organization's Telegram account to your Grafana OnCall instance by
|
|||
1. Click **Connect automatically** for the bot to message you and to bring up your telegram account.
|
||||
1. Click **Start** when the OnCall bot messages you.
|
||||
|
||||
If you want to connect manually, you can click the URL provided and then **SEND MESSAGE**. In your Telegram account, click **Start**.
|
||||
If you want to connect manually, you can click the URL provided and then **SEND MESSAGE**. In your Telegram account, click **Start**.
|
||||
|
|
|
|||
|
|
@ -1,38 +1,47 @@
|
|||
+++
|
||||
title = "Manage users and teams for Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "integrations"]
|
||||
weight = 1100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/configure-user-settings/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- integrations
|
||||
title: Manage users and teams for Grafana OnCall
|
||||
weight: 1100
|
||||
---
|
||||
|
||||
# Manage users and teams for Grafana OnCall
|
||||
|
||||
Grafana OnCall is configured based on the teams you've created on the organization level of your Grafana instance, in **Configuration > Teams**. Administrators can create a different configuration for each team, and can navigate between team configurations in the **Select Team** dropdown menu in the **Incidents** section of Grafana OnCall.
|
||||
Grafana OnCall is configured based on the teams you've created on the organization level of your Grafana instance, in **Configuration > Teams**. Administrators can create a different configuration for each team, and can navigate between team configurations in the **Select Team** dropdown menu in the **Incidents** section of Grafana OnCall.
|
||||
|
||||
Users can edit their contact information, but user permissions are assigned at the Cloud portal level.
|
||||
|
||||
## Configure user notification policies
|
||||
|
||||
Administrators can configure how each user will receive notifications when they are are scheduled to receive them in escalation chains. Users can verify phone numbers and email addresses.
|
||||
Administrators can configure how each user will receive notifications when they are are scheduled to receive them in escalation chains. Users can verify phone numbers and email addresses.
|
||||
|
||||
>**NOTE**: You cannot add users or manage permissions in Grafana OnCall. Most user settings are found on the organizational level of your Grafana instance in **Configuration > Users**.
|
||||
> **NOTE**: You cannot add users or manage permissions in Grafana OnCall. Most user settings are found on the organizational level of your Grafana instance in **Configuration > Users**.
|
||||
|
||||
1. Find users.
|
||||
|
||||
Select the **Users** tab and use the browser to search for a user in the team associated with the OnCall configuration.
|
||||
|
||||
Select the **Users** tab and use the browser to search for a user in the team associated with the OnCall configuration.
|
||||
|
||||
1. Configure user settings.
|
||||
|
||||
Add and verify a phone number, a Slack username, and a Telegram account if you want to receive notifications using these mediums.
|
||||
Add and verify a phone number, a Slack username, and a Telegram account if you want to receive notifications using these mediums.
|
||||
|
||||
>**NOTE:** To edit a user's profile username, email, or role, you must do so in the **Users** tab in the **Configuration** menu of your Grafana instance.
|
||||
> **NOTE:** To edit a user's profile username, email, or role, you must do so in the **Users** tab in the **Configuration** menu of your Grafana instance.
|
||||
|
||||
1. Configure notification settings.
|
||||
|
||||
Specify the notification medium and frequency for each user. Notification steps will be followed in the order they are listed.
|
||||
|
||||
The settings you specify in **Default Notifications** dictate how a user is notified for most escalation thresholds.
|
||||
|
||||
**Important Notifications** are labeled in escalation chains. If an escalation event is marked as an important notification, it will bypass **Default Notification** settings and notify the user by the method specified.
|
||||
|
||||
Specify the notification medium and frequency for each user. Notification steps will be followed in the order they are listed.
|
||||
|
||||
The settings you specify in **Default Notifications** dictate how a user is notified for most escalation thresholds.
|
||||
|
||||
**Important Notifications** are labeled in escalation chains. If an escalation event is marked as an important notification, it will bypass **Default Notification** settings and notify the user by the method specified.
|
||||
|
||||
## Configure Telegram user settings in OnCall
|
||||
|
||||
|
|
@ -45,4 +54,4 @@ If you want to connect manually, you can click the URL provided and then **SEND
|
|||
## Configure Slack user settings in OnCall
|
||||
|
||||
1. In your profile, find the Slack setting and click **Connect**.
|
||||
1. Follow the instructions to verify your account.
|
||||
1. Follow the instructions to verify your account.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "onc
|
|||
weight = 500
|
||||
+++
|
||||
|
||||
|
||||
# Configure Escalation Chains and Routes for Grafana OnCall
|
||||
|
||||
Escalation Chains and Routes for Grafana OnCall
|
||||
|
|
@ -12,4 +13,4 @@ Administrators can create escalation policies to automatically send alert group
|
|||
|
||||
See the following topics for more information:
|
||||
|
||||
{{< section >}}
|
||||
{{< section >}}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,34 @@
|
|||
+++
|
||||
title = "Configure and manage Escalation Chains"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "integrations"]
|
||||
weight = 100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/escalation-policies/configure-escalation-chains/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- integrations
|
||||
title: Configure and manage Escalation Chains
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Configure and manage Escalation Chains
|
||||
|
||||
Escalation policies dictate how users and groups are notified when an alert notification is created. They can be very simple, or very complex. You can define as many escalation configurations for an integration as you need, and you can send notifications for certain alerts to a designated place when certain conditions are met, or not met.
|
||||
Escalation policies dictate how users and groups are notified when an alert notification is created. They can be very simple, or very complex. You can define as many escalation configurations for an integration as you need, and you can send notifications for certain alerts to a designated place when certain conditions are met, or not met.
|
||||
|
||||
Escalation policies have three main parts:
|
||||
* User settings, where a user sets up their preferred or required notification method.
|
||||
* An **escalation chain**, which can have one or more steps that are followed in order when a notification is triggered.
|
||||
* A **route**, that allows administrators to manage notifications by flagging expressions in an alert payload.
|
||||
|
||||
- User settings, where a user sets up their preferred or required notification method.
|
||||
- An **escalation chain**, which can have one or more steps that are followed in order when a notification is triggered.
|
||||
- A **route**, that allows administrators to manage notifications by flagging expressions in an alert payload.
|
||||
|
||||
## Escalation chains
|
||||
|
||||
An escalation chain can have many steps, or only one step. For example, steps can be configured to notify multiple users in some order, notify users that are scheduled for on-call shifts, ping groups in Slack, use outgoing webhooks to integrate with other services, such as JIRA, and do a number of other automated notification tasks.
|
||||
|
||||
## Routes
|
||||
|
||||
An escalation workflow can employ **routes** that administrators can configure to filter alerts by regular expressions in their payloads. Notifications for these alerts can be sent to individuals, or they can make use of a new or existing escalation chain.
|
||||
|
||||
To learn how to configure escalation chains and routes, see [Configure escalation policies]({{< relref "configure-escalation-policies">}}).
|
||||
To learn how to configure escalation chains and routes, see [Configure escalation policies]({{< relref "configure-escalation-policies">}}).
|
||||
|
|
|
|||
|
|
@ -1,42 +1,53 @@
|
|||
+++
|
||||
title = "Configure and manage Routes"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "integrations"]
|
||||
weight = 300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/escalation-policies/configure-routes/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- integrations
|
||||
title: Configure and manage Routes
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Configure and manage Routes
|
||||
Set up escalation chains and routes to configure escalation behavior for alert group notifications.
|
||||
|
||||
Set up escalation chains and routes to configure escalation behavior for alert group notifications.
|
||||
|
||||
## Configure escalation chains
|
||||
You can create and edit escalation chains in two places: within **Integrations**, by clicking on an integration tile, and in **Escalation Chains**. The following steps are for the **Integrations** workflow, but are generally applicable in both situations.
|
||||
|
||||
You can use **escalation chains** and **routes** to determine ordered escalation procedures. Escalation chains allow you to set up a series of alert group notification actions that trigger if certain conditions that you specify are met or not met.
|
||||
You can create and edit escalation chains in two places: within **Integrations**, by clicking on an integration tile, and in **Escalation Chains**. The following steps are for the **Integrations** workflow, but are generally applicable in both situations.
|
||||
|
||||
You can use **escalation chains** and **routes** to determine ordered escalation procedures. Escalation chains allow you to set up a series of alert group notification actions that trigger if certain conditions that you specify are met or not met.
|
||||
|
||||
1. Click on the integration tile for which you want to define escalation policies.
|
||||
|
||||
The **Escalations** section for the notification is in the pane to the right of the list of notifications.
|
||||
You can click **Change alert template and grouping** to customize the look of the alert. You can also do this by clicking the **Settings** (gear) icon in the integration tile.
|
||||
|
||||
The **Escalations** section for the notification is in the pane to the right of the list of notifications.
|
||||
You can click **Change alert template and grouping** to customize the look of the alert. You can also do this by clicking the **Settings** (gear) icon in the integration tile.
|
||||
|
||||
1. Create an escalation chain.
|
||||
|
||||
In the escalation pane, click **Escalate to** to choose from previously added escalation chains, or create a new one by clicking **Make a copy** or **Create a new chain**. This will be the name of the escalation policy you define.
|
||||
|
||||
In the escalation pane, click **Escalate to** to choose from previously added escalation chains, or create a new one by clicking **Make a copy** or **Create a new chain**. This will be the name of the escalation policy you define.
|
||||
|
||||
1. Add escalation steps.
|
||||
|
||||
Click **Add escalation step** to choose from a set of actions and specify their triggering conditions. By default, the first step is to notify a slack channel or user. Specify users or channels or toggle the switch to turn this step off.
|
||||
Click **Add escalation step** to choose from a set of actions and specify their triggering conditions. By default, the first step is to notify a slack channel or user. Specify users or channels or toggle the switch to turn this step off.
|
||||
|
||||
To mark an escalation as **Important**, select the option from the step **Start** dropdown menu. User notification policies can be separately defined for **Important** and **Default** escalations.
|
||||
To mark an escalation as **Important**, select the option from the step **Start** dropdown menu. User notification policies can be separately defined for **Important** and **Default** escalations.
|
||||
|
||||
## Create a route
|
||||
|
||||
To add a route, click **Add Route**.
|
||||
|
||||
You can set up a single route and specify notification escalation steps, or you can add multiple routes, each with its own configuration.
|
||||
To add a route, click **Add Route**.
|
||||
|
||||
Each route added to an escalation policy follows an `IF`, `ELSE IF`, or `ELSE` path and depends on the type of alert you specify using a regular expression that matches content in the payload body of the alert. You can also specify where to send the notification for each route.
|
||||
|
||||
For example, you can send notifications for alerts with `\"severity\": \"critical\"` in the payload to an escalation chain called `Bob_OnCall`. You can create a different route for alerts with the payload `\"namespace\" *: *\"synthetic-monitoring-dev-.*\"` and select a escalation chain called `NotifySecurity`.
|
||||
You can set up a single route and specify notification escalation steps, or you can add multiple routes, each with its own configuration.
|
||||
|
||||
Each route added to an escalation policy follows an `IF`, `ELSE IF`, or `ELSE` path and depends on the type of alert you specify using a regular expression that matches content in the payload body of the alert. You can also specify where to send the notification for each route.
|
||||
|
||||
For example, you can send notifications for alerts with `\"severity\": \"critical\"` in the payload to an escalation chain called `Bob_OnCall`. You can create a different route for alerts with the payload `\"namespace\" *: *\"synthetic-monitoring-dev-.*\"` and select a escalation chain called `NotifySecurity`.
|
||||
|
||||
You can set up escalation steps for each route in a chain.
|
||||
|
||||
>**NOTE:** When you modify an escalation chain or a route, it will modify that escalation chain across all integrations that use it.
|
||||
> **NOTE:** When you modify an escalation chain or a route, it will modify that escalation chain across all integrations that use it.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ aliases = ["/docs/grafana-cloud/oncall/getting-started"]
|
|||
weight = 100
|
||||
+++
|
||||
|
||||
|
||||
# Get started with Grafana OnCall
|
||||
|
||||
Grafana OnCall is an incident response tool built to help DevOps and SRE teams improve their collaboration and resolve incidents faster.
|
||||
|
|
@ -18,18 +19,16 @@ These procedures introduce you to initial Grafana OnCall configuration steps, in
|
|||
|
||||
Grafana OnCall is available for Grafana Cloud as well as Grafana open source users. You must have a Grafana Cloud account or [Open Source Grafana OnCall]({{< relref " open-source.md" >}})
|
||||
|
||||
For more information, see [Grafana Pricing](https://grafana.com/pricing/) for details.
|
||||
For more information, see [Grafana Pricing](https://grafana.com/pricing/) for details.
|
||||
|
||||
|
||||
## Install Open Source Grafana OnCall (OSS Only)
|
||||
|
||||
## Install Open Source Grafana OnCall
|
||||
|
||||
For Open Source Grafana OnCall installation guidance, refer to [Open Source Grafana OnCall]({{< relref " open-source.md" >}})
|
||||
|
||||
>**Note:** If you are using Grafana OnCall with your Grafana Cloud instance there are no install steps. Access Grafana OnCall from your Grafana Cloud account and skip ahead to “Get alerts into Grafana OnCall”
|
||||
|
||||
|
||||
|
||||
## Get alerts into Grafana OnCall
|
||||
|
||||
Once you’ve installed Grafana OnCall or accessed it from your Grafana Cloud instance, you can begin integrating with monitoring systems, configuring escalation chains, and get alerts into Grafana OnCall.
|
||||
|
|
@ -44,6 +43,7 @@ Regardless of where your alerts originate, you can send them to Grafana OnCall v
|
|||
3. Follow the configuration steps on the integration settings page.
|
||||
4. Complete any necessary configurations in your monitoring system to send alerts to Grafana OnCall.
|
||||
|
||||
|
||||
#### Send a demo alert
|
||||
|
||||
1. In the integration tab, click **Send demo alert** then navigate to the **Alert Groups** tab to see your test alert firing.
|
||||
|
|
@ -112,3 +112,4 @@ To integrate your on-call calendar with Grafana OnCall:
|
|||
|
||||
For more information on on-call schedules, refer to [Configure and manage on-call schedules]({{< relref " ../calendar-schedules.md/" >}})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
+++
|
||||
title = "Connect to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "oncall", "integrations"]
|
||||
weight = 300
|
||||
aliases = ["/docs/grafana-cloud/oncall/integrations/"]
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/integrations/
|
||||
- /docs/oncall/latest/integrations/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- oncall
|
||||
- integrations
|
||||
title: Connect to Grafana OnCall
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Connect to Grafana OnCall
|
||||
|
||||
Integrations allow you to connect monitoring systems of your choice to send alerts to Grafana OnCall. Regardless of where your alerts originate, you can configure alerts to be sent to Grafana OnCall for alert escalation and notification. Grafana OnCall receives alerts in JSON format via a POST request, OnCall then parses alert data using preconfigured alert templates to determine alert grouping, apply routes, and determine correct escalation.
|
||||
Integrations allow you to connect monitoring systems of your choice to send alerts to Grafana OnCall. Regardless of where your alerts originate, you can configure alerts to be sent to Grafana OnCall for alert escalation and notification. Grafana OnCall receives alerts in JSON format via a POST request, OnCall then parses alert data using preconfigured alert templates to determine alert grouping, apply routes, and determine correct escalation.
|
||||
|
||||
There are many integrations that are directly supported by Grafana OnCall. Those that aren’t currently listed in the Integrations menu can be connected using the webhook integration and configured alert templates.
|
||||
There are many integrations that are directly supported by Grafana OnCall. Those that aren’t currently listed in the Integrations menu can be connected using the webhook integration and configured alert templates.
|
||||
|
||||
## Configure and manage integrations
|
||||
|
||||
You can configure and manage your integrations from the **Integrations** tab in Grafana OnCall. The following sections describe how to configure and customize your integrations to ensure alerts are treated appropriately.
|
||||
|
||||
|
||||
### Connect an integration to Grafana OnCall
|
||||
|
||||
To configure an integration for Grafana OnCall:
|
||||
|
|
@ -25,10 +33,9 @@ To configure an integration for Grafana OnCall:
|
|||
3. Follow the configuration steps on the integration settings page.
|
||||
4. Complete any necessary configurations in your tool to send alerts to Grafana OnCall.
|
||||
|
||||
|
||||
### Manage Grafana OnCall integrations
|
||||
|
||||
To manage existing integrations, navigate to the **Integrations** tab in Grafana OnCall and select the integration you want to manage.
|
||||
To manage existing integrations, navigate to the **Integrations** tab in Grafana OnCall and select the integration you want to manage.
|
||||
|
||||
#### Customize alert templates and grouping
|
||||
|
||||
|
|
@ -37,17 +44,16 @@ To customize the alert template for an integration:
|
|||
1. Select an integration from your list of enabled integrations in the **Integrations** tab.
|
||||
2. Click **Change alert template and grouping**.
|
||||
3. Select a template to edit from the **Edit template for** dropdown menu.
|
||||
4. Edit alert templates as needed to customize the fields and content rendered for an alert.
|
||||
4. Edit alert templates as needed to customize the fields and content rendered for an alert.
|
||||
|
||||
To customize alert grouping for an integration:
|
||||
|
||||
1. Click **Change alert template and grouping**.
|
||||
2. Select **Alert Behavior** from the dropdown menu next to **Edit template for**.
|
||||
3. Edit the **grouping id**, **acknowledge condition**, and **resolve condition** templates as needed to customize your alert behavior.
|
||||
3. Edit the **grouping id**, **acknowledge condition**, and **resolve condition** templates as needed to customize your alert behavior.
|
||||
|
||||
For more information on alert templates, see [Configure alerts in Grafana OnCall]({{< relref " ../create-custom-templates/" >}})
|
||||
|
||||
|
||||
#### Add Routes
|
||||
|
||||
To add a route to an integration using regular expression:
|
||||
|
|
@ -56,25 +62,24 @@ To add a route to an integration using regular expression:
|
|||
2. Click **+ Add Route**.
|
||||
3. Use python style regex to match on your alert content.
|
||||
4. Click **Create Route**.
|
||||
5. Select an escalation chain for “**IF** alert payload matches regex” and “**ELSE**” to specify where to route each type of alert.
|
||||
5. Select an escalation chain for “**IF** alert payload matches regex” and “**ELSE**” to specify where to route each type of alert.
|
||||
|
||||
To learn more about routes, refer to [Configure and manage Routes]({{< relref " ../configure-routes/" >}})
|
||||
|
||||
|
||||
#### Edit integration name
|
||||
|
||||
To edit the name of an integration:
|
||||
|
||||
1. Navigate to the **Integrations** tab, select an integration from the list of enabled integrations.
|
||||
1. Navigate to the **Integrations** tab, select an integration from the list of enabled integrations.
|
||||
2. Click the **pencil icon** next to the integration name.
|
||||
3. Provide a new name and click **Update**.
|
||||
|
||||
#### Delete integration
|
||||
#### Delete integration
|
||||
|
||||
To delete an integration:
|
||||
|
||||
1. Select an integration from your list of enabled integrations in the **Integrations** tab.
|
||||
2. Click the **trash can** icon next to the selected integration.
|
||||
3. Confirm by clicking **Delete**.
|
||||
|
||||
|
||||
|
||||
{{< section >}}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,45 @@
|
|||
+++
|
||||
title = "Webhook integration for Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Alertmanager", "Prometheus"]
|
||||
weight = 700
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/integrations/add-webhook-integration/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Alertmanager
|
||||
- Prometheus
|
||||
title: Webhook integration for Grafana OnCall
|
||||
weight: 700
|
||||
---
|
||||
|
||||
# Configure Webhook integrations for Grafana OnCall
|
||||
|
||||
Grafana OnCall directly supports many integrations, those that aren’t currently listed in the Integrations menu can be connected using the webhook integration and configured alert templates.
|
||||
Grafana OnCall directly supports many integrations, those that aren’t currently listed in the Integrations menu can be connected using the webhook integration and configured alert templates.
|
||||
|
||||
With the webhook integration, you can connect to any alert source that isn't listed in the **Create Integration** page.
|
||||
With the webhook integration, you can connect to any alert source that isn't listed in the **Create Integration** page.
|
||||
|
||||
There are two available formats, **Webhook** and **Formatted Webhook**.
|
||||
|
||||
* **Webhook** will pull all of the raw JSON payload and display it in the manner that it's received.
|
||||
* **Formatted Webhook** can be used if the alert payload sent by your monitoring service is formatted in a way that OnCall recognizes.
|
||||
|
||||
The following fields are recognized, but none are required:
|
||||
* `alert_uid`: a unique alert ID for grouping.
|
||||
* `title`: a title.
|
||||
* `image_url`: a URL for an image attached to alert.
|
||||
* `state`: either `ok` or `alerting`. Helpful for auto-resolving.
|
||||
* `link_to_upstream_details`: link back to your monitoring system.
|
||||
* `message`: alert details.
|
||||
- **Webhook** will pull all of the raw JSON payload and display it in the manner that it's received.
|
||||
- **Formatted Webhook** can be used if the alert payload sent by your monitoring service is formatted in a way that OnCall recognizes.
|
||||
|
||||
The following fields are recognized, but none are required:
|
||||
|
||||
- `alert_uid`: a unique alert ID for grouping.
|
||||
- `title`: a title.
|
||||
- `image_url`: a URL for an image attached to alert.
|
||||
- `state`: either `ok` or `alerting`. Helpful for auto-resolving.
|
||||
- `link_to_upstream_details`: link back to your monitoring system.
|
||||
- `message`: alert details.
|
||||
|
||||
To configure a webhook integration:
|
||||
|
||||
1. In the **Integrations** tab, click **+ New integration for receiving alerts**.
|
||||
2. Select either **Webhook** or **Formatted Webhook** integration.
|
||||
2. Select either **Webhook** or **Formatted Webhook** integration.
|
||||
3. Follow the configuration steps in the **How to connect** section of the integration settings.
|
||||
4. Use the unique webhook URL to complete any configuration in your monitoring service to send POST requests. Use any http client, e.g. curl to send POST requests with any payload.
|
||||
|
||||
For example:
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
curl -X POST \
|
||||
|
|
@ -46,5 +55,4 @@ To configure a webhook integration:
|
|||
}'
|
||||
```
|
||||
|
||||
|
||||
To learn how to use custom alert templates for formatted webhooks, see [Configure alerts in Grafana OnCall]({{< relref "../create-custom-templates/" >}}).
|
||||
To learn how to use custom alert templates for formatted webhooks, see [Configure alerts in Grafana OnCall]({{< relref "../create-custom-templates/" >}}).
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
+++
|
||||
title = "Currently available integrations for Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Alertmanager", "Prometheus"]
|
||||
aliases = ["/docs/grafana-cloud/oncall/integrations/add-integration/"]
|
||||
weight = 100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/integrations/add-integration/
|
||||
- /docs/oncall/latest/integrations/available-integrations /
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Alertmanager
|
||||
- Prometheus
|
||||
title: Currently available integrations for Grafana OnCall
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Currently available integrations
|
||||
|
||||
Grafana OnCall can connect directly to the monitoring services where your alerts originate. All currently available integrations are listed in the Grafana OnCall **Create Integration** section.
|
||||
|
||||
If the integration you're looking for isn't currently listed, see [Configure Webhook integrations for Grafana OnCall]({{< relref " ../add-webhook-integration/" >}}) to integration your monitoring system with Grafana OnCall.
|
||||
|
||||
>**Note:** Some integrations are available for Grafana Cloud instances only. See individual integration guides for more information.
|
||||
If the integration you're looking for isn't currently listed, see [Configure Webhook integrations for Grafana OnCall]({{< relref " ../add-webhook-integration/" >}}) to integration your monitoring system with Grafana OnCall.
|
||||
|
||||
> **Note:** Some integrations are available for Grafana Cloud instances only. See individual integration guides for more information.
|
||||
|
||||
The following integrations are currently available for Grafana OnCall and have documentation:
|
||||
|
||||
|
||||
|
||||
{{< section >}}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
+++
|
||||
title = "Connect Alert Manager to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Alertmanager", "Prometheus"]
|
||||
aliases = ["/docs/grafana-cloud/oncall/available-integrations/add-alertmanager/"]
|
||||
weight = 300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/
|
||||
- /docs/oncall/latest/integrations/available-integrations /add-alertmanager/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Alertmanager
|
||||
- Prometheus
|
||||
title: Connect Alert Manager to Grafana OnCall
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Connect AlertManager to Grafana OnCall
|
||||
|
||||
The AlertManager integration for Grafana OnCall handles alerts sent by client applications such as the Prometheus server.
|
||||
The AlertManager integration for Grafana OnCall handles alerts sent by client applications such as the Prometheus server.
|
||||
|
||||
Grafana OnCall provides<!--[grouping](#alertmanager-grouping-amp-oncall-grouping)--> grouping abilities when processing alerts from Alert Manager, including initial deduplicating, grouping, and routing the alerts to Grafana OnCall.
|
||||
|
||||
|
|
@ -26,6 +34,7 @@ You must have an Admin role to create integrations in Grafana OnCall.
|
|||
## Configure AlertManager
|
||||
|
||||
Update the `receivers` section of your Alertmanager configuration to use a unique integration URL:
|
||||
|
||||
```
|
||||
route:
|
||||
receiver: 'oncall'
|
||||
|
|
@ -47,12 +56,12 @@ AlertManager offers three alert grouping options:
|
|||
- `group_by` provides two options, `instance` or `job`.
|
||||
- `group_wait` sets the length of time to initially wait before sending a notification for a particular group of alerts. For example, `group_wait` can be set to 45s.
|
||||
|
||||
Setting a high value for `group_wait` reduces alert noise and minimizes interruption, but it may introduce delays in receiving alert notifications. To set an appropriate wait time, consider whether the group of alerts will be the same as those previously sent.
|
||||
Setting a high value for `group_wait` reduces alert noise and minimizes interruption, but it may introduce delays in receiving alert notifications. To set an appropriate wait time, consider whether the group of alerts will be the same as those previously sent.
|
||||
|
||||
- `group_interval` sets the length of time to wait before sending notifications about new alerts that have been added to a group of alerts that have been previously alerted on. This setting is usually set to five minutes or more.
|
||||
|
||||
During high alert volume periods, AlertManager will send alerts at each `group_interval`, which can mean a lot of distraction. Grafana OnCall grouping will help manage this in the following ways:
|
||||
During high alert volume periods, AlertManager will send alerts at each `group_interval`, which can mean a lot of distraction. Grafana OnCall grouping will help manage this in the following ways:
|
||||
|
||||
- Grafana OnCall groups alerts based on the first label of each alert.
|
||||
- Grafana OnCall groups alerts based on the first label of each alert.
|
||||
|
||||
- Grafana OnCall marks an incident as resolved only when the amount of grouped alerts with state `resolved` equals the amount of alerts with state `firing`.
|
||||
- Grafana OnCall marks an incident as resolved only when the amount of grouped alerts with state `resolved` equals the amount of alerts with state `firing`.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
+++
|
||||
title = "Connect Grafana Alerting to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Prometheus"]
|
||||
aliases = ["/docs/grafana-cloud/oncall/integrations/add-grafana-alerting/"]
|
||||
weight = 100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/
|
||||
- /docs/oncall/latest/integrations/available-integrations /add-grafana-alerting/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Prometheus
|
||||
title: Connect Grafana Alerting to Grafana OnCall
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Connect Grafana Alerting to Grafana OnCall
|
||||
|
||||
|
|
@ -13,7 +20,7 @@ Grafana Alerting for Grafana OnCall can be set up using two methods:
|
|||
|
||||
- Grafana (Other Grafana): Grafana OnCall is connected to one or more Grafana instances separate from the one being used to manage Grafana OnCall.
|
||||
|
||||
## Configure Grafana Alerting for Grafana OnCall
|
||||
## Configure Grafana Alerting for Grafana OnCall
|
||||
|
||||
You must have an Admin role to create integrations in Grafana OnCall.
|
||||
|
||||
|
|
@ -23,21 +30,21 @@ You must have an Admin role to create integrations in Grafana OnCall.
|
|||
|
||||
3. Follow the configuration steps that display in the **How to connect** window to retrieve your unique integration URL and complete any necessary configurations.
|
||||
|
||||
|
||||
### Configure Grafana Cloud Alerting
|
||||
|
||||
Use the following method if you are connecting Grafana OnCall with alerts coming from the same Grafana instance from which Grafana OnCall is being managed.
|
||||
|
||||
1. In Grafana OnCall, navigate to the **Integrations** tab and select **New Integration for receiving alerts**.
|
||||
|
||||
1. Click **Quick connect** in the **Grafana Alerting** tile. This will automatically create the integration in Grafana OnCall as well as the required contact point in Alerting.
|
||||
1. Click **Quick connect** in the **Grafana Alerting** tile. This will automatically create the integration in Grafana OnCall as well as the required contact point in Alerting.
|
||||
|
||||
>**Note:** You must connect the contact point with a notification policy. For more information, see [Contact points in Grafana Alerting](https://grafana.com/docs/grafana/latest/alerting/unified-alerting/contact-points/)
|
||||
> **Note:** You must connect the contact point with a notification policy. For more information, see [Contact points in Grafana Alerting](https://grafana.com/docs/grafana/latest/alerting/unified-alerting/contact-points/)
|
||||
|
||||
1. Determine the escalation chain for the new integration by either selecting an existing one or by creating a new escalation chain.
|
||||
1. Determine the escalation chain for the new integration by either selecting an existing one or by creating a new escalation chain.
|
||||
|
||||
2. In Grafana Cloud Alerting, navigate to **Alerting > Contact Points** and find a contact point with a name matching the integration you created in Grafana OnCall.
|
||||
1. In Grafana Cloud Alerting, navigate to **Alerting > Contact Points** and find a contact point with a name matching the integration you created in Grafana OnCall.
|
||||
|
||||
3. Click the **Edit** (pencil) icon, then click **Test**. This will send a test alert to Grafana OnCall.
|
||||
1. Click the **Edit** (pencil) icon, then click **Test**. This will send a test alert to Grafana OnCall.
|
||||
|
||||
### Configure Grafana (Other Grafana)
|
||||
|
||||
|
|
@ -57,6 +64,6 @@ Connect Grafana OnCall with alerts coming from a Grafana instance that is differ
|
|||
|
||||
7. Choose the contact point type `webhook`, then paste the URL generated in step 3 into the URL field.
|
||||
|
||||
>**Note:** You must connect the contact point with a notification policy. For more information, see [Contact points in Grafana Alerting](https://grafana.com/docs/grafana/latest/alerting/unified-alerting/contact-points/).
|
||||
> **Note:** You must connect the contact point with a notification policy. For more information, see [Contact points in Grafana Alerting](https://grafana.com/docs/grafana/latest/alerting/unified-alerting/contact-points/).
|
||||
|
||||
8. Click the **Edit** (pencil) icon, then click **Test**. This will send a test alert to Grafana OnCall.
|
||||
8. Click the **Edit** (pencil) icon, then click **Test**. This will send a test alert to Grafana OnCall.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
+++
|
||||
title = "Connect Zabbix to Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Zabbix"]
|
||||
weight = 500
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/integrations/available-integrations /add-zabbix/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Zabbix
|
||||
title: Connect Zabbix to Grafana OnCall
|
||||
weight: 500
|
||||
---
|
||||
|
||||
# Connect Zabbix to Grafana OnCall
|
||||
|
||||
Zabbix is an open-source monitoring software tool for diverse IT components, including networks, servers, virtual machines, and cloud services. Zabbix provides monitoring for metrics such as network utilization, CPU load, and disk space consumption.
|
||||
|
||||
|
||||
## Configure Zabbix integration for Grafana OnCall
|
||||
|
||||
This integration is available for Grafana Cloud OnCall. You must have an Admin role to create integrations in Grafana OnCall.
|
||||
|
|
@ -17,86 +23,92 @@ This integration is available for Grafana Cloud OnCall. You must have an Admin r
|
|||
2. Select **Zabbix** from the list of available integrations
|
||||
3. Follow the instructions in the **How to connect** window to get your unique integration URL and review next steps.
|
||||
|
||||
|
||||
<!---->
|
||||
|
||||
## Configure the Zabbix server
|
||||
|
||||
1. Deploy a Zabbix playground if you don't have one set up:
|
||||
```bash
|
||||
docker run --name zabbix-appliance -t \
|
||||
-p 10051:10051 \
|
||||
-p 80:80 \
|
||||
-d zabbix/zabbix-appliance:latest
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run --name zabbix-appliance -t \
|
||||
-p 10051:10051 \
|
||||
-p 80:80 \
|
||||
-d zabbix/zabbix-appliance:latest
|
||||
```
|
||||
|
||||
1. Establish an ssh connection to a Zabbix server.
|
||||
|
||||
```bash
|
||||
docker exec -it zabbix-appliance bash
|
||||
```
|
||||
```bash
|
||||
docker exec -it zabbix-appliance bash
|
||||
```
|
||||
|
||||
1. Place the [grafana_oncall.sh](#grafana_oncallsh-script) script in the `AlertScriptsPath` directory specified within the Zabbix server configuration file (zabbix_server.conf).
|
||||
|
||||
```bash
|
||||
grep AlertScriptsPath /etc/zabbix/zabbix_server.conf
|
||||
```
|
||||
>**Note:** The script must be executable by the user running the zabbix_server binary (usually "zabbix") on the Zabbix server. For example, `chmod +x grafana_oncall.sh`
|
||||
```bash
|
||||
grep AlertScriptsPath /etc/zabbix/zabbix_server.conf
|
||||
```
|
||||
|
||||
``` bash
|
||||
ls -lh /usr/lib/zabbix/alertscripts/grafana_oncall.sh
|
||||
-rw-r--r-- 1 root root 1.5K Jun 6 07:52 /usr/lib/zabbix/alertscripts/grafana_oncall.sh
|
||||
```
|
||||
> **Note:** The script must be executable by the user running the zabbix_server binary (usually "zabbix") on the Zabbix server. For example, `chmod +x grafana_oncall.sh`
|
||||
|
||||
```bash
|
||||
ls -lh /usr/lib/zabbix/alertscripts/grafana_oncall.sh
|
||||
-rw-r--r-- 1 root root 1.5K Jun 6 07:52 /usr/lib/zabbix/alertscripts/grafana_oncall.sh
|
||||
```
|
||||
|
||||
## Configure Zabbix alerts
|
||||
|
||||
Within Zabbix web interface, do the following:
|
||||
|
||||
1. In a browser, open localhost:80.
|
||||
|
||||
1. Navigate to **Adminitstration > Media Types > Create Media Type**.
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
1. Create a Media Type with the following fields.
|
||||
* Name: Grafana OnCall
|
||||
* Type: script
|
||||
* Script parameters:
|
||||
* {ALERT.SENDTO}
|
||||
* {ALERT.SUBJECT}
|
||||
* {ALERT.MESSAGE}
|
||||
|
||||
<!---->
|
||||
- Name: Grafana OnCall
|
||||
- Type: script
|
||||
- Script parameters:
|
||||
- {ALERT.SENDTO}
|
||||
- {ALERT.SUBJECT}
|
||||
- {ALERT.MESSAGE}
|
||||
|
||||
<!---->
|
||||
|
||||
### Set the {ALERT.SEND_TO} value
|
||||
|
||||
To send alerts to Grafana OnCall, the {ALERT.SEND_TO} value must be set in the [user media configuration](https://www.zabbix.com/documentation/3.4/manual/config/notifications/media/script#user_media).
|
||||
|
||||
1. In the web UI, navigate to **Administration > Users** and open the **user properties** form.
|
||||
1. In the web UI, navigate to **Administration > Users** and open the **user properties** form.
|
||||
|
||||
1. In the **Media** tab, click **Add** and copy the link from Grafana OnCall in the `Send to` field.
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
1. Click **Test** in the last column to send a test alert to Grafana OnCall.
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
1. Specify **Send to** OnCall using the unique integration URL from the above step in the testing window that opens.
|
||||
Create a test message with a body and optional subject and click **Test**.
|
||||
<!--
|
||||
WHERE DID SLACK COME FROM?! 1. View the Grafana OnCall incident that appears in the Slack channel.
|
||||
-->
|
||||
Create a test message with a body and optional subject and click **Test**.
|
||||
<!--
|
||||
WHERE DID SLACK COME FROM?! 1. View the Grafana OnCall incident that appears in the Slack channel.
|
||||
-->
|
||||
|
||||
## Grouping and auto-resolve of Zabbix notifications
|
||||
|
||||
Grafana OnCall provides grouping and auto-resolve of Zabbix notifications.
|
||||
Use the following procedure to configure grouping and auto-resolve.
|
||||
|
||||
1. Provide a parameter as an identifier for group differentiation to Grafana OnCall.
|
||||
1. Provide a parameter as an identifier for group differentiation to Grafana OnCall.
|
||||
|
||||
1. Append that variable to the subject of the action as `ONCALL_GROUP: ID`, where `ID` is any of the Zabbix [macros](https://www.zabbix.com/documentation/4.2/manual/appendix/macros/supported_by_location).
|
||||
For example, `{EVENT.ID}`. The Grafana OnCall script [grafana_oncall.sh](#grafana_oncallsh-script) extracts this event and passes the `alert_uid` to Grafana OnCall.
|
||||
1. Append that variable to the subject of the action as `ONCALL_GROUP: ID`, where `ID` is any of the Zabbix [macros](https://www.zabbix.com/documentation/4.2/manual/appendix/macros/supported_by_location).
|
||||
For example, `{EVENT.ID}`. The Grafana OnCall script [grafana_oncall.sh](#grafana_oncallsh-script) extracts this event and passes the `alert_uid` to Grafana OnCall.
|
||||
|
||||
1. To enable auto-resolve within Grafana Oncall, the "Resolved" keyword is required in the **Default subject** field in **Recovered operations**.
|
||||
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
## grafana_oncall.sh script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# This is the modification of original ericos's shell script.
|
||||
|
|
@ -137,4 +149,5 @@ return=$(curl $url -d "${payload}" -H "Content-Type: application/json" -X POST)
|
|||
```
|
||||
|
||||
## More Information
|
||||
For more information on Zabbix scripts, see [scripts for notifications](https://www.zabbix.com/documentation/4.2/manual/config/notifications/media/script).
|
||||
|
||||
For more information on Zabbix scripts, see [scripts for notifications](https://www.zabbix.com/documentation/4.2/manual/config/notifications/media/script).
|
||||
|
|
|
|||
|
|
@ -1,37 +1,45 @@
|
|||
+++
|
||||
title = "Configure outgoing webhooks for Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "amixr", "webhooks"]
|
||||
weight = 500
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/integrations/configure-outgoing-webhooks/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- amixr
|
||||
- webhooks
|
||||
title: Configure outgoing webhooks for Grafana OnCall
|
||||
weight: 500
|
||||
---
|
||||
|
||||
# Configure outgoing webhooks for Grafana OnCall
|
||||
|
||||
Outgoing webhooks allow you to send alert details to a specified URL from Grafana OnCall. Once an outgoing webhook is configured, you can use it as a notification method in escalation chains.
|
||||
Outgoing webhooks allow you to send alert details to a specified URL from Grafana OnCall. Once an outgoing webhook is configured, you can use it as a notification method in escalation chains.
|
||||
|
||||
To automatically send alert data to a destination URL via outgoing webhook:
|
||||
|
||||
To automatically send alert data to a destination URL via outgoing webhook:
|
||||
1. In Grafana OnCall, navigate to **Outgoing Webhooks** and click **+ Create**.
|
||||
This is also the place to edit and delete existing outgoing webhooks.
|
||||
This is also the place to edit and delete existing outgoing webhooks.
|
||||
|
||||
2. Provide a name for your outgoing webhook and enter the destination URL.
|
||||
|
||||
3. If the destination requires authentication, enter your credentials.
|
||||
You can enter a username and password (HTTP) or an authorization header formatted in JSON.
|
||||
You can enter a username and password (HTTP) or an authorization header formatted in JSON.
|
||||
|
||||
4. Configure the webhook payload in the **Data** field.
|
||||
|
||||
4. Configure the webhook payload in the **Data** field.
|
||||
5. Click **Create Webhook**.
|
||||
|
||||
The format you use to call the variables must match the structure of how the fields are nested in the alert payload. The **Data** field can use the following four variables to auto-populate the webhook payload with information about the first alert in the alert group:
|
||||
|
||||
- `{{ alert_title }}`
|
||||
- `{{ alert_message }}`
|
||||
- `{{ alert_url }}`
|
||||
- `{{ alert_url }}`
|
||||
- `{{ alert_payload }}`
|
||||
<br>
|
||||
<br>
|
||||
|
||||
`alert_payload` is always the first level of any variable you want to call.
|
||||
|
||||
The following is an example of an entry in the **Data** field that might return an alert name and description.
|
||||
The following is an example of an entry in the **Data** field that might return an alert name and description.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -40,5 +48,4 @@ The following is an example of an entry in the **Data** field that might return
|
|||
}
|
||||
```
|
||||
|
||||
>**NOTE:** If you receive an error message and cannot create an outgoing webhook, verify that your JSON is formatted correctly.
|
||||
|
||||
> **NOTE:** If you receive an error message and cannot create an outgoing webhook, verify that your JSON is formatted correctly.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
+++
|
||||
title = "Configure alerts in Grafana OnCall"
|
||||
keywords = ["Grafana Cloud", "Alerts", "Notifications", "on-call", "Jinja"]
|
||||
weight = 300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/integrations/create-custom-templates/
|
||||
keywords:
|
||||
- Grafana Cloud
|
||||
- Alerts
|
||||
- Notifications
|
||||
- on-call
|
||||
- Jinja
|
||||
title: Configure alerts in Grafana OnCall
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Configure alerts in Grafana OnCall
|
||||
|
||||
Grafana OnCall can integrate with any monitoring systems that can send alerts using webhooks with JSON payloads. By default, webhooks deliver raw JSON payloads. When Grafana OnCall receives an alert and parses its payload, a default pre configured alert template is applied to modify the alert payload to be more human readable. These alert templates are customizable for any integration.
|
||||
Grafana OnCall can integrate with any monitoring systems that can send alerts using webhooks with JSON payloads. By default, webhooks deliver raw JSON payloads. When Grafana OnCall receives an alert and parses its payload, a default pre configured alert template is applied to modify the alert payload to be more human readable. These alert templates are customizable for any integration.
|
||||
|
||||
See Format alerts with alert templates in this document to learn more about how to customize alert templates.
|
||||
|
||||
|
|
@ -24,83 +31,88 @@ Alerts received by Grafana OnCall contain metadata as keys and values in a JSON
|
|||
|
||||
```json
|
||||
{
|
||||
"dashboardId":1,
|
||||
"title":"[Alerting] Panel Title alert",
|
||||
"message":"Notification Message",
|
||||
"evalMatches":[
|
||||
"dashboardId": 1,
|
||||
"title": "[Alerting] Panel Title alert",
|
||||
"message": "Notification Message",
|
||||
"evalMatches": [
|
||||
{
|
||||
"value":1,
|
||||
"metric":"Count",
|
||||
"tags":{}
|
||||
"value": 1,
|
||||
"metric": "Count",
|
||||
"tags": {}
|
||||
}
|
||||
],
|
||||
"imageUrl":"https://grafana.com/static/assets/img/blog/mixed_styles.png",
|
||||
"orgId":1,
|
||||
"panelId":2,
|
||||
"ruleId":1,
|
||||
"ruleName":"Panel Title alert",
|
||||
"ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1",
|
||||
"state":"alerting",
|
||||
"tags":{
|
||||
"tag name":"tag value"
|
||||
"imageUrl": "https://grafana.com/static/assets/img/blog/mixed_styles.png",
|
||||
"orgId": 1,
|
||||
"panelId": 2,
|
||||
"ruleId": 1,
|
||||
"ruleName": "Panel Title alert",
|
||||
"ruleUrl": "http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1",
|
||||
"state": "alerting",
|
||||
"tags": {
|
||||
"tag name": "tag value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Grafana OnCall every alert and alert group has the following fields:
|
||||
|
||||
- `Title`, `message` and `image url`
|
||||
- `Grouping Id`
|
||||
- `Resolve Signal`
|
||||
|
||||
The JSON payload is converted. For example:
|
||||
* `{{ payload.title }}` -> Title
|
||||
* `{{ payload.message }}` -> Message
|
||||
* `{{ payload.imageUrl }}` -> Image Url
|
||||
The JSON payload is converted. For example:
|
||||
|
||||
- `{{ payload.title }}` -> Title
|
||||
- `{{ payload.message }}` -> Message
|
||||
- `{{ payload.imageUrl }}` -> Image Url
|
||||
|
||||
The result is that each field of the alert in OnCall is now mapped to the JSON payload keys. This also true for the alert behavior:
|
||||
* `{{ payload.ruleId }}` -> Grouping Id
|
||||
* `{{ 1 if payload.state == 'OK' else 0 }}` -> Resolve Signal
|
||||
|
||||
- `{{ payload.ruleId }}` -> Grouping Id
|
||||
- `{{ 1 if payload.state == 'OK' else 0 }}` -> Resolve Signal
|
||||
|
||||
Grafana OnCall provides a pre configured default Jinja template for supported integrations. If your monitoring system is not in the Grafana OnCall integrations list, you can create a generic `webhook` integration, send an alert, and configure your templates.
|
||||
|
||||
## Customize alerts with alert templates
|
||||
|
||||
## Customize alerts with alert templates
|
||||
|
||||
Alert templates allow you to format any alert fields recognized by Grafana OnCall. You can customize default alert templates for all the different ways you receive your alerts such as web, slack, SMS, and email. For more advanced customization, use Jinja templates.
|
||||
Alert templates allow you to format any alert fields recognized by Grafana OnCall. You can customize default alert templates for all the different ways you receive your alerts such as web, slack, SMS, and email. For more advanced customization, use Jinja templates.
|
||||
|
||||
As a best practice, add _Playbooks_, _Useful links_, or _Checklists_ to the alert message.
|
||||
|
||||
To customize alert templates in Grafana OnCall:
|
||||
|
||||
1. Navigate to the **Integrations** tab, select the integration, then click **Change alert template and grouping**.
|
||||
1. Navigate to the **Integrations** tab, select the integration, then click **Change alert template and grouping**.
|
||||
|
||||
2. In Alert Templates, select a template from the **Edit template for** dropdown.
|
||||
|
||||
3. Edit the Appearances template as needed:
|
||||
* `Title`, `Message`, `Image url` for Web
|
||||
* `Title`, `Message`, `Image url` for Slack
|
||||
* `Title` used for SMS
|
||||
* `Title` used for Phone
|
||||
* `Title`, `Message` used for Email
|
||||
|
||||
- `Title`, `Message`, `Image url` for Web
|
||||
- `Title`, `Message`, `Image url` for Slack
|
||||
- `Title` used for SMS
|
||||
- `Title` used for Phone
|
||||
- `Title`, `Message` used for Email
|
||||
|
||||
4. Edit the alert behavior as needed:
|
||||
* `Grouping Id` - This output groups other alerts into a single alert group.
|
||||
* `Acknowledge Condition` - The output should be `ok`, `true`, or `1` to auto-acknowledge the alert group. For example, `{{ 1 if payload.state == 'OK' else 0 }}`.
|
||||
* `Resolve Condition` - The output should be `ok`, `true` or `1` to auto-resolve the alert group. For example, `{{ 1 if payload.state == 'OK' else 0 }}`.
|
||||
* `Source Link` - Used to customize the URL link to provide as the "source" of the alert.
|
||||
- `Grouping Id` - This output groups other alerts into a single alert group.
|
||||
- `Acknowledge Condition` - The output should be `ok`, `true`, or `1` to auto-acknowledge the alert group. For example, `{{ 1 if payload.state == 'OK' else 0 }}`.
|
||||
- `Resolve Condition` - The output should be `ok`, `true` or `1` to auto-resolve the alert group. For example, `{{ 1 if payload.state == 'OK' else 0 }}`.
|
||||
- `Source Link` - Used to customize the URL link to provide as the "source" of the alert.
|
||||
|
||||
## Advanced Jinja templates
|
||||
Grafana OnCall uses [Jinja templating language](http://jinja.pocoo.org/docs/2.10/) to format alert groups for the Web, Slack, phone calls, SMS messages, and more because the JSON format is not easily readable by humans. As a result, you can decide what you want to see when an alert group is triggered as well as how it should be presented.
|
||||
|
||||
|
||||
Grafana OnCall uses [Jinja templating language](http://jinja.pocoo.org/docs/2.10/) to format alert groups for the Web, Slack, phone calls, SMS messages, and more because the JSON format is not easily readable by humans. As a result, you can decide what you want to see when an alert group is triggered as well as how it should be presented.
|
||||
|
||||
Jinja2 offers simple but multi-faceted functionality by using loops, conditions, functions, and more.
|
||||
|
||||
> **NOTE:** Every alert from a monitoring system comes in the key/value format.
|
||||
> **NOTE:** Every alert from a monitoring system comes in the key/value format.
|
||||
|
||||
Grafana OnCall has rules about which of the keys match to: `__title`, `message`, `image`, `grouping`, and `auto-resolve__`.
|
||||
|
||||
### Loops
|
||||
|
||||
Monitoring systems can send an array of values. In this example, you can use Jinja to iterate and format the alert using a Grafana example:
|
||||
|
||||
```.jinja2
|
||||
*Values:*
|
||||
{% for evalMatch in payload.evalMatches -%}
|
||||
|
|
@ -109,9 +121,10 @@ Monitoring systems can send an array of values. In this example, you can use Jin
|
|||
```
|
||||
|
||||
### Conditions
|
||||
|
||||
You can add instructions if an alert comes from a specified Grafana alert rule:
|
||||
|
||||
```jinja2
|
||||
````jinja2
|
||||
{% if payload.ruleId == '1' -%}
|
||||
*Alert TODOs*
|
||||
1. Get acess to the container
|
||||
|
|
@ -122,22 +135,26 @@ You can add instructions if an alert comes from a specified Grafana alert rule:
|
|||
3. Open the container and reload caches.
|
||||
4. Click Custom Button `Send to Jira`
|
||||
{%- endif -%}
|
||||
```
|
||||
````
|
||||
|
||||
### Built-in Jinja functions
|
||||
|
||||
Jinja2 includes built-in functions that can also be used in Grafana OnCall. For example:
|
||||
|
||||
```.jinja2
|
||||
{{ payload | tojson_pretty }}
|
||||
```
|
||||
|
||||
Built-in functions:
|
||||
* `abs`
|
||||
* `capitalize`
|
||||
* `trim`
|
||||
* You can see the full list of Jinja built-in functions on github [here](https://github.com/pallets/jinja/blob/3915eb5c2a7e2e4d49ebdf0ecb167ea9c21c60b2/src/jinja2/filters.py#L1307)
|
||||
|
||||
- `abs`
|
||||
- `capitalize`
|
||||
- `trim`
|
||||
- You can see the full list of Jinja built-in functions on github [here](https://github.com/pallets/jinja/blob/3915eb5c2a7e2e4d49ebdf0ecb167ea9c21c60b2/src/jinja2/filters.py#L1307)
|
||||
|
||||
### Functions added by Grafana OnCall
|
||||
* `time` - current time
|
||||
* `tojson_pretty` - JSON 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)
|
||||
|
||||
- `time` - current time
|
||||
- `tojson_pretty` - JSON 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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
+++
|
||||
title = "Grafana OnCall HTTP API reference"
|
||||
weight = 1300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/oncall/latest/oncall-api-reference/
|
||||
title: Grafana OnCall HTTP API reference
|
||||
weight: 1300
|
||||
---
|
||||
|
||||
# HTTP API Reference
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ curl "api_endpoint_here" --header "Authorization: meowmeowmeow"
|
|||
```
|
||||
|
||||
Note that `meowmeowmeow` is a valid key for test purposes.
|
||||
Replace `meowmeowmeow` with your API key in production.
|
||||
Replace `meowmeowmeow` with your API key in production.
|
||||
|
||||
Grafana OnCall uses API keys to allow access to the API. You can request a new OnCall API key in the API section.
|
||||
|
||||
|
|
@ -31,33 +33,34 @@ An API key is specific to a user and a Grafana stack. If you want to switch to a
|
|||
|
||||
## Pagination
|
||||
|
||||
List endpoints such as List Integrations or List Alert Groups return multiple objects.
|
||||
List endpoints such as List Integrations or List Alert Groups return multiple objects.
|
||||
|
||||
The OnCall API returns them in pages. Note that the page size may vary.
|
||||
The OnCall API returns them in pages. Note that the page size may vary.
|
||||
|
||||
| Parameter | Meaning |
|
||||
|-----------|:-------:|
|
||||
`count` | The total number of items. It can be `0` if a request does not return any data.
|
||||
`next` | A link to the next page. It can be `null` if the next page does not contain any data.
|
||||
`previous` | A link to the previous page. It can be `null` if the previous page does not contain any data.
|
||||
`results` | The data list. Can be `[]` if a request does not return any data.
|
||||
| Parameter | Meaning |
|
||||
| ---------- | :-------------------------------------------------------------------------------------------: |
|
||||
| `count` | The total number of items. It can be `0` if a request does not return any data. |
|
||||
| `next` | A link to the next page. It can be `null` if the next page does not contain any data. |
|
||||
| `previous` | A link to the previous page. It can be `null` if the previous page does not contain any data. |
|
||||
| `results` | The data list. Can be `[]` if a request does not return any data. |
|
||||
|
||||
## Rate Limits
|
||||
|
||||
Grafana OnCall provides rate limits to ensure alert group notifications will be delivered to your Slack workspace even when some integrations produce a large number of alerts.
|
||||
|
||||
### Monitoring integrations Rate Limits
|
||||
|
||||
Rate limited response HTTP status: 429
|
||||
|
||||
|
||||
| Scope | Amount | Time Frame |
|
||||
|------------------------------|:------:|:----------:|
|
||||
| Alerts from each integration | 300 | 5 minutes |
|
||||
| Alerts from the whole team | 500 | 5 minutes |
|
||||
| ---------------------------- | :----: | :--------: |
|
||||
| Alerts from each integration | 300 | 5 minutes |
|
||||
| Alerts from the whole team | 500 | 5 minutes |
|
||||
|
||||
## API rate limits
|
||||
|
||||
You can reduce or increase rate limits depending on platform status.
|
||||
|
||||
| Scope | Amount | Time Frame |
|
||||
|--------------------------|:------:|:--------:|
|
||||
| API requests per API key | 300 | 5 minutes |
|
||||
| ------------------------ | :----: | :--------: |
|
||||
| API requests per API key | 300 | 5 minutes |
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Alert groups HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/alertgroups/"]
|
||||
weight = 400
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/alertgroups/
|
||||
- /docs/oncall/latest/oncall-api-reference/alertgroups/
|
||||
title: Alert groups HTTP API
|
||||
weight: 400
|
||||
---
|
||||
|
||||
# List alert groups
|
||||
|
||||
|
|
@ -10,36 +12,36 @@ weight = 400
|
|||
curl "{{API_URL}}/api/v1/alert_groups/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "I68T24C13IFW1",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"route_id": "RIYGUJXCPFHXY",
|
||||
"alerts_count": 3,
|
||||
"state": "resolved",
|
||||
"created_at": "2020-05-19T12:37:01.430444Z",
|
||||
"resolved_at": "2020-05-19T13:37:01.429805Z",
|
||||
"acknowledged_at": null,
|
||||
"title": "Memory above 90% threshold"
|
||||
}
|
||||
]
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "I68T24C13IFW1",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"route_id": "RIYGUJXCPFHXY",
|
||||
"alerts_count": 3,
|
||||
"state": "resolved",
|
||||
"created_at": "2020-05-19T12:37:01.430444Z",
|
||||
"resolved_at": "2020-05-19T13:37:01.429805Z",
|
||||
"acknowledged_at": null,
|
||||
"title": "Memory above 90% threshold"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
These available filter parameters should be provided as `GET` arguments:
|
||||
|
||||
* `route_id`
|
||||
* `integration_id`
|
||||
- `route_id`
|
||||
- `integration_id`
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -57,12 +59,12 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/" \
|
|||
}'
|
||||
```
|
||||
|
||||
|Parameter | Required | Description |
|
||||
|--------- |:--------:|:------------|
|
||||
`mode` | No | Default setting is `wipe`. `wipe` will remove the payload of all Grafana OnCall group alerts. This is useful if you sent sensitive data to OnCall. All metadata will remain. `DELETE` will trigger the removal of alert groups, alerts, and all related metadata. It will also remove alert group notifications in Slack and other destinations.
|
||||
| Parameter | Required | Description |
|
||||
| --------- | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `mode` | No | Default setting is `wipe`. `wipe` will remove the payload of all Grafana OnCall group alerts. This is useful if you sent sensitive data to OnCall. All metadata will remain. `DELETE` will trigger the removal of alert groups, alerts, and all related metadata. It will also remove alert group notifications in Slack and other destinations. |
|
||||
|
||||
>**NOTE:** `DELETE` can take a few moments to delete alert groups because Grafana OnCall interacts with 3rd party APIs such as Slack. Please check objects using `GET` to be sure the data is removed.
|
||||
> **NOTE:** `DELETE` can take a few moments to delete alert groups because Grafana OnCall interacts with 3rd party APIs such as Slack. Please check objects using `GET` to be sure the data is removed.
|
||||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/alert_groups/<ALERT_GROUP_ID>`
|
||||
`DELETE {{API_URL}}/api/v1/alert_groups/<ALERT_GROUP_ID>`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Alerts HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/alerts"]
|
||||
weight = 100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/alerts/
|
||||
- /docs/oncall/latest/oncall-api-reference/alerts/
|
||||
title: Alerts HTTP API
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# List Alerts
|
||||
|
||||
|
|
@ -10,101 +12,101 @@ weight = 100
|
|||
curl "{{API_URL}}/api/v1/alerts/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 3,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "AA74DN7T4JQB6",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:43Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AR9SSYFKE2PV7",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:54Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AWJQSGEYYUFGH",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:58Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
"count": 3,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "AA74DN7T4JQB6",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:43Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AR9SSYFKE2PV7",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:54Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AWJQSGEYYUFGH",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-05-11T20:07:58Z",
|
||||
"payload": {
|
||||
"state": "alerting",
|
||||
"title": "[Alerting] Test notification",
|
||||
"ruleId": 0,
|
||||
"message": "Someone is testing the alert notification within Grafana.",
|
||||
"ruleUrl": "{{API_URL}}/",
|
||||
"ruleName": "Test notification",
|
||||
"evalMatches": [
|
||||
{
|
||||
"tags": null,
|
||||
"value": 100,
|
||||
"metric": "High value"
|
||||
},
|
||||
{
|
||||
"tags": null,
|
||||
"value": 200,
|
||||
"metric": "Higher Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameters should be provided as `GET` arguments:
|
||||
|
||||
* `alert_group_id`
|
||||
* `search`—string-based inclusion search by alert payload
|
||||
- `alert_group_id`
|
||||
- `search`—string-based inclusion search by alert payload
|
||||
|
||||
**HTTP request**
|
||||
|
||||
`GET {{API_URL}}/api/v1/alerts/`
|
||||
`GET {{API_URL}}/api/v1/alerts/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Escalation Chains HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/escalation_chains"]
|
||||
weight = 200
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/escalation_chains/
|
||||
- /docs/oncall/latest/oncall-api-reference/escalation_chains/
|
||||
title: Escalation Chains HTTP API
|
||||
weight: 200
|
||||
---
|
||||
|
||||
# Create an escalation chain
|
||||
|
||||
|
|
@ -20,16 +22,16 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "FWDL7M6N6I9HE",
|
||||
"name": "example-chain",
|
||||
"team_id": null
|
||||
"id": "FWDL7M6N6I9HE",
|
||||
"name": "example-chain",
|
||||
"team_id": null
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|:--------:|:------------|
|
||||
| name | yes | Name of the escalation chain |
|
||||
| team_id | no | ID of the team |
|
||||
| Parameter | Required | Description |
|
||||
| --------- | :------: | :--------------------------- |
|
||||
| name | yes | Name of the escalation chain |
|
||||
| team_id | no | ID of the team |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -48,9 +50,9 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "F5JU6KJET33FE",
|
||||
"name": "default",
|
||||
"team_id": null
|
||||
"id": "F5JU6KJET33FE",
|
||||
"name": "default",
|
||||
"team_id": null
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -64,23 +66,23 @@ The above command returns JSON structured in the following way:
|
|||
curl "{{API_URL}}/api/v1/escalation_chains/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "F5JU6KJET33FE",
|
||||
"name": "default",
|
||||
"team_id": null
|
||||
}
|
||||
]
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "F5JU6KJET33FE",
|
||||
"name": "default",
|
||||
"team_id": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -99,4 +101,4 @@ curl "{{API_URL}}/api/v1/escalation_chains/F5JU6KJET33FE/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/escalation_chains/<ESCALATION_CHAIN_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/escalation_chains/<ESCALATION_CHAIN_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Escalation Policies HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/escalation_policies"]
|
||||
weight = 300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/escalation_policies/
|
||||
- /docs/oncall/latest/oncall-api-reference/escalation_policies/
|
||||
title: Escalation Policies HTTP API
|
||||
weight: 300
|
||||
---
|
||||
|
||||
# Create an escalation policy
|
||||
|
||||
|
|
@ -22,28 +24,28 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
}
|
||||
```
|
||||
|
||||
|Parameter | Required | Description |
|
||||
|----------|:--------:|:------------|
|
||||
`escalation_chain_id` | Yes | Each escalation policy is assigned to a specific escalation chain.
|
||||
`position` | Optional | Escalation policies execute one after another starting from `position=0`. `Position=-1` will put the escalation policy to the end of the list. A new escalation policy created with a position of an existing escalation policy will move the old one (and all following) down in the list.
|
||||
`type` | Yes | One of: `wait`, `notify_persons`, `notify_person_next_each_time`, `notify_on_call_from_schedule`, `notify_user_group`, `trigger_action`, `resolve`, `notify_whole_channel`, `notify_if_time_from_to`.
|
||||
`duration` | Optional | The duration, in seconds, when type `wait` is chosen.
|
||||
`important` | Optional | Default is `false`. Will assign "important" to personal notification rules if `true`. This can be used to distinguish alerts on which you want to be notified immediately by phone. Applicable for types `notify_persons`, `notify_on_call_from_schedule`, and `notify_user_group`.
|
||||
`action_to_trigger` | If type = `trigger_action` | ID of an action, or webhook.
|
||||
`group_to_notify` | If type = `notify_user_group` | ID of a `User Group`.
|
||||
`persons_to_notify` | If type = `notify_persons` | List of user IDs.
|
||||
`persons_to_notify_next_each_time` | If type = `notify_person_next_each_time` | List of user IDs.
|
||||
`notify_on_call _from_schedule` | If type = `notify_on_call_from_schedule` | ID of a Schedule.
|
||||
`notify_if_time_from` | If type = `notify_if_time_from_to` | UTC time represents the beginning of the time period, for example `09:00:00Z`.
|
||||
`notify_if_time_to` | If type = `notify_if_time_from_to` | UTC time represents the end of the time period, for example `18:00:00Z`.
|
||||
| Parameter | Required | Description |
|
||||
| ---------------------------------- | :--------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `escalation_chain_id` | Yes | Each escalation policy is assigned to a specific escalation chain. |
|
||||
| `position` | Optional | Escalation policies execute one after another starting from `position=0`. `Position=-1` will put the escalation policy to the end of the list. A new escalation policy created with a position of an existing escalation policy will move the old one (and all following) down in the list. |
|
||||
| `type` | Yes | One of: `wait`, `notify_persons`, `notify_person_next_each_time`, `notify_on_call_from_schedule`, `notify_user_group`, `trigger_action`, `resolve`, `notify_whole_channel`, `notify_if_time_from_to`. |
|
||||
| `duration` | Optional | The duration, in seconds, when type `wait` is chosen. |
|
||||
| `important` | Optional | Default is `false`. Will assign "important" to personal notification rules if `true`. This can be used to distinguish alerts on which you want to be notified immediately by phone. Applicable for types `notify_persons`, `notify_on_call_from_schedule`, and `notify_user_group`. |
|
||||
| `action_to_trigger` | If type = `trigger_action` | ID of an action, or webhook. |
|
||||
| `group_to_notify` | If type = `notify_user_group` | ID of a `User Group`. |
|
||||
| `persons_to_notify` | If type = `notify_persons` | List of user IDs. |
|
||||
| `persons_to_notify_next_each_time` | If type = `notify_person_next_each_time` | List of user IDs. |
|
||||
| `notify_on_call _from_schedule` | If type = `notify_on_call_from_schedule` | ID of a Schedule. |
|
||||
| `notify_if_time_from` | If type = `notify_if_time_from_to` | UTC time represents the beginning of the time period, for example `09:00:00Z`. |
|
||||
| `notify_if_time_to` | If type = `notify_if_time_from_to` | UTC time represents the end of the time period, for example `18:00:00Z`. |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -62,11 +64,11 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -80,40 +82,38 @@ The above command returns JSON structured in the following way:
|
|||
curl "{{API_URL}}/api/v1/escalation_policies/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
},
|
||||
{
|
||||
"id": "E5JJTU52M5YM4",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 1,
|
||||
"type": "notify_person_next_each_time",
|
||||
"persons_to_notify_next_each_time": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
}
|
||||
]
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "E3GA6SJETWWJS",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 0,
|
||||
"type": "wait",
|
||||
"duration": 60
|
||||
},
|
||||
{
|
||||
"id": "E5JJTU52M5YM4",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"position": 1,
|
||||
"type": "notify_person_next_each_time",
|
||||
"persons_to_notify_next_each_time": ["U4DNY931HHJS5"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameter should be provided as a `GET` argument:
|
||||
|
||||
* `escalation_chain_id`
|
||||
- `escalation_chain_id`
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -130,4 +130,4 @@ curl "{{API_URL}}/api/v1/escalation_policies/E3GA6SJETWWJS/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/escalation_policies/<ESCALATION_POLICY_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/escalation_policies/<ESCALATION_POLICY_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Integrations HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/integrations/"]
|
||||
weight = 500
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/integrations/
|
||||
- /docs/oncall/latest/oncall-api-reference/integrations/
|
||||
title: Integrations HTTP API
|
||||
weight: 500
|
||||
---
|
||||
|
||||
# Create an integration
|
||||
|
||||
|
|
@ -20,52 +22,52 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Integrations are sources of alerts and alert groups for Grafana OnCall.
|
||||
For example, to learn how to integrate Grafana OnCall with Alertmanager see [Alertmanager]({{< relref "../integrations/add-alertmanager" >}}).
|
||||
For example, to learn how to integrate Grafana OnCall with Alertmanager see [Alertmanager]({{< relref "../integrations/add-alertmanager" >}}).
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -84,51 +86,51 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This endpoint retrieves an integration. Integrations are sources of alerts and alert groups for Grafana OnCall.
|
||||
This endpoint retrieves an integration. Integrations are sources of alerts and alert groups for Grafana OnCall.
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -147,54 +149,54 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
}
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
]
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -226,47 +228,47 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
"id": "CFRPV98RPR1U8",
|
||||
"name": "Grafana :blush:",
|
||||
"team_id": null,
|
||||
"link": "{{API_URL}}/integrations/v1/grafana/mReAoNwDm0eMwKo1mTeTwYo/",
|
||||
"type": "grafana",
|
||||
"default_route": {
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"grouping_key": null,
|
||||
"resolve_signal": null,
|
||||
"slack": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"web": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
},
|
||||
"email": {
|
||||
"title": null,
|
||||
"message": null
|
||||
},
|
||||
"sms": {
|
||||
"title": null
|
||||
},
|
||||
"phone_call": {
|
||||
"title": null
|
||||
},
|
||||
"telegram": {
|
||||
"title": null,
|
||||
"message": null,
|
||||
"image_url": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -275,6 +277,7 @@ The above command returns JSON structured in the following way:
|
|||
`PUT {{API_URL}}/api/v1/integrations/<INTEGRATION_ID>/`
|
||||
|
||||
# Delete integration
|
||||
|
||||
Deleted integrations will stop recording new alerts from monitoring. Integration removal won't trigger removal of related alert groups or alerts.
|
||||
|
||||
```shell
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "OnCall shifts HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/on_call_shifts/"]
|
||||
weight = 600
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/on_call_shifts/
|
||||
- /docs/oncall/latest/oncall-api-reference/on_call_shifts/
|
||||
title: OnCall shifts HTTP API
|
||||
weight: 600
|
||||
---
|
||||
|
||||
# Create an OnCall shift
|
||||
|
||||
|
|
@ -29,38 +31,36 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": ["U4DNY931HHJS5"]
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Unique | Required | Description |
|
||||
|-----------|:------:|:--------:|:------------|
|
||||
`name` | Yes | Yes | On-call shift name.
|
||||
`type` | No | Yes | One of: `single_event`, `recurrent_event`, `rolling_users`.
|
||||
`team_id` | No | ID of the team.
|
||||
`time_zone` | No | Optional | On-call shift time zone. Default is local schedule time zone. **This field will override the schedule time zone if changed**. For more information see [time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
||||
`level` | No | Optional | Priority level. The higher the value, the higher the priority. If two events overlap in one schedule, Grafana OnCall will choose the event with higher level. For example: Alex is on-call from 8AM till 11AM with level 1, Bob is on-call from 9AM till 11AM with level 2. At 10AM Grafana OnCall will notify Bob. At 8AM OnCall will notify Alex.
|
||||
`start` | No | Yes | Start time of the on-call shift. This parameter takes a date format as `yyyy-MM-dd'T'HH:mm:ss` (for example "2020-09-05T08:00:00").
|
||||
`duration` | No | Yes | Duration of the event.
|
||||
`frequency` | No | If type = `recurrent_event` or `rolling_users` | One of: `daily`, `weekly`, `monthly`.
|
||||
`interval` | No | Optional | This parameter takes a positive integer that represents the intervals that the recurrence rule repeats.
|
||||
`week_start` | No | Optional | Start day of the week in iCal format. One of: `SU` (Sunday), `MO` (Monday), `TU` (Tuesday), `WE` (Wednesday), `TH` (Thursday), `FR` (Friday), `SA` (Saturday). Default: `SU`.
|
||||
`by_day` | No | Optional | List of days in iCal format. Valid values are: `SU`, `MO`, `TU`, `WE`, `TH`, `FR`, `SA`.
|
||||
`by_month` | No | Optional | List of months. Valid values are `1` to `12`.
|
||||
`by_monthday` | No | Optional | List of days of the month. Valid values are `1` to `31` or `-31` to `-1`.
|
||||
`users` | No | Optional | List of on-call users.
|
||||
`rolling_users` | No | Optional | List of lists with on-call users (for `rolling_users` event type). Grafana OnCall will iterate over lists of users for every time frame specified in `frequency`. For example: there are two lists of users in `rolling_users` : [[Alex, Bob], [Alice]] and `frequency` = `daily` . This means that the first day Alex and Bob will be notified. The next day: Alice. The day after: Alex and Bob again and so on.
|
||||
`start_rotation_from_user_index` | No | Optional | Index of the list of users in `rolling_users`, from which on-call rotation starts. By default, the start index is `0`
|
||||
| Parameter | Unique | Required | Description |
|
||||
| -------------------------------- | :----: | :--------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | Yes | Yes | On-call shift name. |
|
||||
| `type` | No | Yes | One of: `single_event`, `recurrent_event`, `rolling_users`. |
|
||||
| `team_id` | No | ID of the team. |
|
||||
| `time_zone` | No | Optional | On-call shift time zone. Default is local schedule time zone. **This field will override the schedule time zone if changed**. For more information see [time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). |
|
||||
| `level` | No | Optional | Priority level. The higher the value, the higher the priority. If two events overlap in one schedule, Grafana OnCall will choose the event with higher level. For example: Alex is on-call from 8AM till 11AM with level 1, Bob is on-call from 9AM till 11AM with level 2. At 10AM Grafana OnCall will notify Bob. At 8AM OnCall will notify Alex. |
|
||||
| `start` | No | Yes | Start time of the on-call shift. This parameter takes a date format as `yyyy-MM-dd'T'HH:mm:ss` (for example "2020-09-05T08:00:00"). |
|
||||
| `duration` | No | Yes | Duration of the event. |
|
||||
| `frequency` | No | If type = `recurrent_event` or `rolling_users` | One of: `daily`, `weekly`, `monthly`. |
|
||||
| `interval` | No | Optional | This parameter takes a positive integer that represents the intervals that the recurrence rule repeats. |
|
||||
| `week_start` | No | Optional | Start day of the week in iCal format. One of: `SU` (Sunday), `MO` (Monday), `TU` (Tuesday), `WE` (Wednesday), `TH` (Thursday), `FR` (Friday), `SA` (Saturday). Default: `SU`. |
|
||||
| `by_day` | No | Optional | List of days in iCal format. Valid values are: `SU`, `MO`, `TU`, `WE`, `TH`, `FR`, `SA`. |
|
||||
| `by_month` | No | Optional | List of months. Valid values are `1` to `12`. |
|
||||
| `by_monthday` | No | Optional | List of days of the month. Valid values are `1` to `31` or `-31` to `-1`. |
|
||||
| `users` | No | Optional | List of on-call users. |
|
||||
| `rolling_users` | No | Optional | List of lists with on-call users (for `rolling_users` event type). Grafana OnCall will iterate over lists of users for every time frame specified in `frequency`. For example: there are two lists of users in `rolling_users` : [[Alex, Bob], [Alice]] and `frequency` = `daily` . This means that the first day Alex and Bob will be notified. The next day: Alice. The day after: Alex and Bob again and so on. |
|
||||
| `start_rotation_from_user_index` | No | Optional | Index of the list of users in `rolling_users`, from which on-call rotation starts. By default, the start index is `0` |
|
||||
|
||||
Please see [RFC 5545](https://tools.ietf.org/html/rfc5545#section-3.3.10) for more information about recurrence rules.
|
||||
|
||||
|
|
@ -81,17 +81,15 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": ["U4DNY931HHJS5"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -105,61 +103,53 @@ The above command returns JSON structured in the following way:
|
|||
curl "{{API_URL}}/api/v1/on_call_shifts/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O9WTH7CKM3KZW",
|
||||
"name": "Demo recurrent event",
|
||||
"type": "recurrent_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T16:00:00",
|
||||
"duration": 10800,
|
||||
"frequency": "weekly",
|
||||
"interval": 2,
|
||||
"week_start": "SU",
|
||||
"by_day": [
|
||||
"MO",
|
||||
"WE",
|
||||
"FR"
|
||||
],
|
||||
"by_month": null,
|
||||
"by_monthday": null,
|
||||
"users": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
}
|
||||
]
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": ["U4DNY931HHJS5"]
|
||||
},
|
||||
{
|
||||
"id": "O9WTH7CKM3KZW",
|
||||
"name": "Demo recurrent event",
|
||||
"type": "recurrent_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T16:00:00",
|
||||
"duration": 10800,
|
||||
"frequency": "weekly",
|
||||
"interval": 2,
|
||||
"week_start": "SU",
|
||||
"by_day": ["MO", "WE", "FR"],
|
||||
"by_month": null,
|
||||
"by_monthday": null,
|
||||
"users": ["U4DNY931HHJS5"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameters should be provided as `GET` arguments:
|
||||
|
||||
* `name` (Exact match)
|
||||
* `schedule_id` (Exact match)
|
||||
- `name` (Exact match)
|
||||
- `schedule_id` (Exact match)
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -188,17 +178,15 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": [
|
||||
"U4DNY931HHJS5"
|
||||
]
|
||||
"id": "OH3V5FYQEYJ6M",
|
||||
"name": "Demo single event",
|
||||
"type": "single_event",
|
||||
"team_id": null,
|
||||
"time_zone": null,
|
||||
"level": 0,
|
||||
"start": "2020-09-10T08:00:00",
|
||||
"duration": 10800,
|
||||
"users": ["U4DNY931HHJS5"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -217,4 +205,4 @@ curl "{{API_URL}}/api/v1/on_call_shifts/S3Z477AHDXTMF/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/on_call_shifts/<ON_CALL_SHIFT_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/on_call_shifts/<ON_CALL_SHIFT_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Outgoing webhooks HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/"]
|
||||
weight = 700
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/
|
||||
- /docs/oncall/latest/oncall-api-reference/outgoing_webhooks/
|
||||
title: Outgoing webhooks HTTP API
|
||||
weight: 700
|
||||
---
|
||||
|
||||
# Outgoing webhooks (actions)
|
||||
|
||||
|
|
@ -14,25 +16,25 @@ Used in escalation policies with type `trigger_action`.
|
|||
curl "{{API_URL}}/api/v1/actions/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "KGEFG74LU1D8L",
|
||||
"name": "Publish alert group notification to JIRA"
|
||||
}
|
||||
]
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "KGEFG74LU1D8L",
|
||||
"name": "Publish alert group notification to JIRA"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**HTTP request**
|
||||
|
||||
`GET {{API_URL}}/api/v1/actions/`
|
||||
`GET {{API_URL}}/api/v1/actions/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Personal Notification Rules HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/personal_notification_rules/"]
|
||||
weight = 800
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/personal_notification_rules/
|
||||
- /docs/oncall/latest/oncall-api-reference/personal_notification_rules/
|
||||
title: Personal Notification Rules HTTP API
|
||||
weight: 800
|
||||
---
|
||||
|
||||
# Post a personal notification rule
|
||||
|
||||
|
|
@ -21,21 +23,21 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "NT79GA9I7E4DJ",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": false,
|
||||
"type": "notify_by_sms"
|
||||
"id": "NT79GA9I7E4DJ",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": false,
|
||||
"type": "notify_by_sms"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|:--------:|:------------|
|
||||
`user_id` | Yes | User ID
|
||||
`position` | Optional | Personal notification rules execute one after another starting from `position=0`. `Position=-1` will put the escalation policy to the end of the list. A new escalation policy created with a position of an existing escalation policy will move the old one (and all following) down on the list.
|
||||
`type` | Yes | One of: `wait`, `notify_by_slack`, `notify_by_sms`, `notify_by_phone_call`, `notify_by_telegram`, `notify_by_email`.
|
||||
`duration` | Optional | A time in secs when type `wait` is chosen for `type`.
|
||||
`important` | Optional | Boolean value indicates if a rule is "important". Default is `false`.
|
||||
| Parameter | Required | Description |
|
||||
| ----------- | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `user_id` | Yes | User ID |
|
||||
| `position` | Optional | Personal notification rules execute one after another starting from `position=0`. `Position=-1` will put the escalation policy to the end of the list. A new escalation policy created with a position of an existing escalation policy will move the old one (and all following) down on the list. |
|
||||
| `type` | Yes | One of: `wait`, `notify_by_slack`, `notify_by_sms`, `notify_by_phone_call`, `notify_by_telegram`, `notify_by_email`. |
|
||||
| `duration` | Optional | A time in secs when type `wait` is chosen for `type`. |
|
||||
| `important` | Optional | Boolean value indicates if a rule is "important". Default is `false`. |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -47,19 +49,19 @@ The above command returns JSON structured in the following way:
|
|||
curl "{{API_URL}}/api/v1/personal_notification_rules/ND9EHN5LN1DUU/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "ND9EHN5LN1DUU",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 1,
|
||||
"duration": 300,
|
||||
"important": false,
|
||||
"type": "wait"
|
||||
"id": "ND9EHN5LN1DUU",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 1,
|
||||
"duration": 300,
|
||||
"important": false,
|
||||
"type": "wait"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -67,61 +69,60 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
`GET {{API_URL}}/api/v1/personal_notification_rules/<PERSONAL_NOTIFICATION_RULE_ID>/`
|
||||
|
||||
|
||||
# List personal notification rules
|
||||
|
||||
```shell
|
||||
curl "{{API_URL}}/api/v1/personal_notification_rules/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following ways:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 4,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "NT79GA9I7E4DJ",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": false,
|
||||
"type": "notify_by_sms"
|
||||
},
|
||||
{
|
||||
"id": "ND9EHN5LN1DUU",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 1,
|
||||
"duration": 300,
|
||||
"important": false,
|
||||
"type": "wait"
|
||||
},
|
||||
{
|
||||
"id": "NEF49YQ1HNPDD",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 2,
|
||||
"important": false,
|
||||
"type": "notify_by_phone_call"
|
||||
},
|
||||
{
|
||||
"id": "NWAL6WFJNWDD8",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": true,
|
||||
"type": "notify_by_phone_call"
|
||||
}
|
||||
]
|
||||
"count": 4,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "NT79GA9I7E4DJ",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": false,
|
||||
"type": "notify_by_sms"
|
||||
},
|
||||
{
|
||||
"id": "ND9EHN5LN1DUU",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 1,
|
||||
"duration": 300,
|
||||
"important": false,
|
||||
"type": "wait"
|
||||
},
|
||||
{
|
||||
"id": "NEF49YQ1HNPDD",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 2,
|
||||
"important": false,
|
||||
"type": "notify_by_phone_call"
|
||||
},
|
||||
{
|
||||
"id": "NWAL6WFJNWDD8",
|
||||
"user_id": "U4DNY931HHJS5",
|
||||
"position": 0,
|
||||
"important": true,
|
||||
"type": "notify_by_phone_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameters should be provided as `GET` arguments:
|
||||
|
||||
* `user_id`
|
||||
* `important`
|
||||
- `user_id`
|
||||
- `important`
|
||||
|
||||
**HTTP Request**
|
||||
|
||||
|
|
@ -129,7 +130,6 @@ The following available filter parameters should be provided as `GET` arguments:
|
|||
|
||||
# Delete a personal notification rule
|
||||
|
||||
|
||||
```shell
|
||||
curl "{{API_URL}}/api/v1/personal_notification_rules/NWAL6WFJNWDD8/" \
|
||||
--request DELETE \
|
||||
|
|
@ -139,4 +139,4 @@ curl "{{API_URL}}/api/v1/personal_notification_rules/NWAL6WFJNWDD8/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/personal_notification_rules/<PERSONAL_NOTIFICATION_RULE_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/personal_notification_rules/<PERSONAL_NOTIFICATION_RULE_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
+++
|
||||
title = "Postmortem Messages HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/postmortem_messages/"]
|
||||
weight = 900
|
||||
draft = true
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/postmortem_messages/
|
||||
- /docs/oncall/latest/oncall-api-reference/postmortem_messages/
|
||||
draft: true
|
||||
title: Postmortem Messages HTTP API
|
||||
weight: 900
|
||||
---
|
||||
|
||||
# Create a postmortem message
|
||||
|
||||
|
|
@ -22,14 +24,14 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -48,12 +50,12 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -74,26 +76,25 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameter should be provided as a `GET` argument:
|
||||
|
||||
* `alert_group_id`
|
||||
|
||||
- `alert_group_id`
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -115,12 +116,12 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -138,4 +139,4 @@ curl "{{API_URL}}/api/v1/postmortem_messages/M4BTQUS3PRHYQ/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/postmortem_messages/<POSTMORTEM_MESSAGE_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/postmortem_messages/<POSTMORTEM_MESSAGE_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
+++
|
||||
title = "Postmortem HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/postmortems/"]
|
||||
weight = 1000
|
||||
draft = true
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/postmortems/
|
||||
- /docs/oncall/latest/oncall-api-reference/postmortems/
|
||||
draft: true
|
||||
title: Postmortem HTTP API
|
||||
weight: 1000
|
||||
---
|
||||
|
||||
# Create a postmortem
|
||||
|
||||
|
|
@ -22,12 +24,12 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text"
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text"
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -46,20 +48,20 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text",
|
||||
"postmortem_messages": [
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text",
|
||||
"postmortem_messages": [
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -80,33 +82,33 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text",
|
||||
"postmortem_messages": [
|
||||
{
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text",
|
||||
"postmortem_messages": [
|
||||
{
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
"id": "M4BTQUS3PRHYQ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"author": "U4DNY931HHJS5",
|
||||
"source": "web",
|
||||
"created_at": "2020-06-19T12:40:01.429805Z",
|
||||
"text": "Demo postmortem message"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameter should be provided with a `GET` argument:
|
||||
|
||||
* `alert_group_id`
|
||||
- `alert_group_id`
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -128,10 +130,10 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text"
|
||||
"id": "P658FE5K87EWZ",
|
||||
"alert_group_id": "I68T24C13IFW1",
|
||||
"created_at": "2020-06-19T12:37:01.430444Z",
|
||||
"text": "Demo postmortem text"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -149,4 +151,4 @@ curl "{{API_URL}}/api/v1/postmortems/P658FE5K87EWZ/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/postmortems/<POSTMORTEM_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/postmortems/<POSTMORTEM_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Routes HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/routes/"]
|
||||
weight = 1100
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/routes/
|
||||
- /docs/oncall/latest/oncall-api-reference/routes/
|
||||
title: Routes HTTP API
|
||||
weight: 1100
|
||||
---
|
||||
|
||||
# Create a route
|
||||
|
||||
|
|
@ -26,31 +28,31 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Routes allow you to direct different alerts to different messenger channels and escalation chains. Useful for:
|
||||
|
||||
* Important/non-important alerts
|
||||
* Alerts for different engineering groups
|
||||
* Snoozing spam & debugging alerts
|
||||
- Important/non-important alerts
|
||||
- Alerts for different engineering groups
|
||||
- Snoozing spam & debugging alerts
|
||||
|
||||
| Parameter | Unique | Required | Description |
|
||||
|-----------|:------:|:--------:|:------------|
|
||||
`integration_id` | No | Yes | Each route is assigned to a specific integration.
|
||||
`escalation_chain_id` | No | Yes | Each route is assigned a specific escalation chain.
|
||||
`routing_regex` | Yes | Yes | Python Regex query (use https://regex101.com/ for debugging). OnCall chooses the route for an alert in case there is a match inside the whole alert payload.
|
||||
`position` | Yes | Optional | Route matching is performed one after another starting from position=`0`. Position=`-1` will put the route to the end of the list before `is_the_last_route`. A new route created with a position of an existing route will move the old route (and all following routes) down in the list.
|
||||
`slack` | Yes | Optional | Dictionary with Slack-specific settings for a route.
|
||||
| Parameter | Unique | Required | Description |
|
||||
| --------------------- | :----: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `integration_id` | No | Yes | Each route is assigned to a specific integration. |
|
||||
| `escalation_chain_id` | No | Yes | Each route is assigned a specific escalation chain. |
|
||||
| `routing_regex` | Yes | Yes | Python Regex query (use https://regex101.com/ for debugging). OnCall chooses the route for an alert in case there is a match inside the whole alert payload. |
|
||||
| `position` | Yes | Optional | Route matching is performed one after another starting from position=`0`. Position=`-1` will put the route to the end of the list before `is_the_last_route`. A new route created with a position of an existing route will move the old route (and all following routes) down in the list. |
|
||||
| `slack` | Yes | Optional | Dictionary with Slack-specific settings for a route. |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -69,15 +71,15 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -85,7 +87,6 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
`GET {{API_URL}}/api/v1/routes/<ROUTE_ID>/`
|
||||
|
||||
|
||||
# List routes
|
||||
|
||||
```shell
|
||||
|
|
@ -99,40 +100,40 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": ".*",
|
||||
"position": 1,
|
||||
"is_the_last_route": true,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
}
|
||||
]
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "RVBE4RKQSCGJ2",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": ".*",
|
||||
"position": 1,
|
||||
"is_the_last_route": true,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameters should be provided as `GET` arguments:
|
||||
|
||||
* `integration_id`
|
||||
* `routing_regex` (Exact match)
|
||||
- `integration_id`
|
||||
- `routing_regex` (Exact match)
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -158,15 +159,15 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
"id": "RIYGUJXCPFHXY",
|
||||
"integration_id": "CFRPV98RPR1U8",
|
||||
"escalation_chain_id": "F5JU6KJET33FE",
|
||||
"routing_regex": "us-(east|west)",
|
||||
"position": 0,
|
||||
"is_the_last_route": false,
|
||||
"slack": {
|
||||
"channel_id": "CH23212D"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -185,4 +186,4 @@ curl "{{API_URL}}/api/v1/routes/RIYGUJXCPFHXY/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/routes/<ROUTE_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/routes/<ROUTE_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Schedule HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/schedules/"]
|
||||
weight = 1200
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/schedules/
|
||||
- /docs/oncall/latest/oncall-api-reference/schedules/
|
||||
title: Schedule HTTP API
|
||||
weight: 1200
|
||||
---
|
||||
|
||||
# Create a schedule
|
||||
|
||||
|
|
@ -25,32 +27,30 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": [
|
||||
"U4DNY931HHJS5"
|
||||
],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": ["U4DNY931HHJS5"],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Unique | Required | Description |
|
||||
|-----------|:------:|:--------:|:------------|
|
||||
`name` | Yes | Yes | Schedule name.
|
||||
`type` | No | Yes | Schedule type. May be `ical` (used for iCalendar integration) or `calendar` (used for manually created on-call shifts).
|
||||
`team_id` | No | No | ID of the team.
|
||||
`time_zone` | No | Optional | Schedule time zone. Is used for manually added on-call shifts in Schedules with type `calendar`. Default time zone is `UTC`. For more information about time zones, see [time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
||||
`ical_url_primary` | No | If type = `ical` | URL of external iCal calendar for schedule with type `ical`.
|
||||
`ical_url_overrides` | No | Optional | URL of external iCal calendar for schedule with any type. Events from this calendar override events from primary calendar or from on-call shifts.
|
||||
`slack` | No | Optional | Dictionary with Slack-specific settings for a schedule. Includes `channel_id` and `user_group_id` fields, that take a channel ID and a user group ID from Slack.
|
||||
`shifts` | No | Optional | List of shifts. Used for manually added on-call shifts in Schedules with type `calendar`.
|
||||
| Parameter | Unique | Required | Description |
|
||||
| -------------------- | :----: | :--------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | Yes | Yes | Schedule name. |
|
||||
| `type` | No | Yes | Schedule type. May be `ical` (used for iCalendar integration) or `calendar` (used for manually created on-call shifts). |
|
||||
| `team_id` | No | No | ID of the team. |
|
||||
| `time_zone` | No | Optional | Schedule time zone. Is used for manually added on-call shifts in Schedules with type `calendar`. Default time zone is `UTC`. For more information about time zones, see [time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). |
|
||||
| `ical_url_primary` | No | If type = `ical` | URL of external iCal calendar for schedule with type `ical`. |
|
||||
| `ical_url_overrides` | No | Optional | URL of external iCal calendar for schedule with any type. Events from this calendar override events from primary calendar or from on-call shifts. |
|
||||
| `slack` | No | Optional | Dictionary with Slack-specific settings for a schedule. Includes `channel_id` and `user_group_id` fields, that take a channel ID and a user group ID from Slack. |
|
||||
| `shifts` | No | Optional | List of shifts. Used for manually added on-call shifts in Schedules with type `calendar`. |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -69,19 +69,17 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": [
|
||||
"U4DNY931HHJS5"
|
||||
],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": ["U4DNY931HHJS5"],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -95,58 +93,51 @@ The above command returns JSON structured in the following way:
|
|||
curl "{{API_URL}}/api/v1/schedules/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": [
|
||||
"U4DNY931HHJS5"
|
||||
],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "S3Z477AHDXTMF",
|
||||
"name": "Demo schedule Calendar",
|
||||
"type": "calendar",
|
||||
"team_id": null,
|
||||
"time_zone": "America/New_York",
|
||||
"on_call_now": [
|
||||
"U4DNY931HHJS5"
|
||||
],
|
||||
"shifts": [
|
||||
"OH3V5FYQEYJ6M",
|
||||
"O9WTH7CKM3KZW"
|
||||
],
|
||||
"ical_url_overrides": null,
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
}
|
||||
]
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": ["U4DNY931HHJS5"],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "S3Z477AHDXTMF",
|
||||
"name": "Demo schedule Calendar",
|
||||
"type": "calendar",
|
||||
"team_id": null,
|
||||
"time_zone": "America/New_York",
|
||||
"on_call_now": ["U4DNY931HHJS5"],
|
||||
"shifts": ["OH3V5FYQEYJ6M", "O9WTH7CKM3KZW"],
|
||||
"ical_url_overrides": null,
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameter should be provided as a `GET` argument:
|
||||
|
||||
* `name` (Exact match)
|
||||
- `name` (Exact match)
|
||||
|
||||
**HTTP request**
|
||||
|
||||
|
|
@ -172,19 +163,17 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": [
|
||||
"U4DNY931HHJS5"
|
||||
],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
"id": "SBM7DV7BKFUYU",
|
||||
"name": "Demo schedule iCal",
|
||||
"type": "ical",
|
||||
"team_id": null,
|
||||
"ical_url_primary": "https://example.com/meow_calendar.ics",
|
||||
"ical_url_overrides": "https://example.com/meow_calendar_overrides.ics",
|
||||
"on_call_now": ["U4DNY931HHJS5"],
|
||||
"slack": {
|
||||
"channel_id": "MEOW_SLACK_ID",
|
||||
"user_group_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -203,4 +192,4 @@ curl "{{API_URL}}/api/v1/schedules/SBM7DV7BKFUYU/" \
|
|||
|
||||
**HTTP request**
|
||||
|
||||
`DELETE {{API_URL}}/api/v1/schedules/<SCHEDULE_ID>/`
|
||||
`DELETE {{API_URL}}/api/v1/schedules/<SCHEDULE_ID>/`
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
+++
|
||||
title = "Slack Channels HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/slack_channels/"]
|
||||
weight = 1300
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/slack_channels/
|
||||
- /docs/oncall/latest/oncall-api-reference/slack_channels/
|
||||
title: Slack Channels HTTP API
|
||||
weight: 1300
|
||||
---
|
||||
|
||||
# List Slack Channels
|
||||
|
||||
|
|
@ -10,29 +12,29 @@ weight = 1300
|
|||
curl "{{API_URL}}/api/v1/slack_channels/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"name": "meow_channel",
|
||||
"slack_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
]
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"name": "meow_channel",
|
||||
"slack_id": "MEOW_SLACK_ID"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following available filter parameter should be provided as a `GET` argument:
|
||||
|
||||
* `channel_name`
|
||||
- `channel_name`
|
||||
|
||||
**HTTP Request**
|
||||
|
||||
`GET {{API_URL}}/api/v1/slack_channels/`
|
||||
`GET {{API_URL}}/api/v1/slack_channels/`
|
||||
|
|
|
|||
|
|
@ -1,46 +1,49 @@
|
|||
+++
|
||||
title = "OnCall User Groups HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/user_groups/"]
|
||||
weight = 1400
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/user_groups/
|
||||
- /docs/oncall/latest/oncall-api-reference/user_groups/
|
||||
title: OnCall User Groups HTTP API
|
||||
weight: 1400
|
||||
---
|
||||
|
||||
<!--Used in escalation policies with type = `notify_user_group` and in schedules.-->
|
||||
|
||||
# List user groups
|
||||
|
||||
```shell
|
||||
curl "{{API_URL}}/api/v1/user_groups/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "GPFAPH7J7BKJB",
|
||||
"type": "slack_based",
|
||||
"slack": {
|
||||
"id": "MEOW_SLACK_ID",
|
||||
"name": "Meow Group",
|
||||
"handle": "meow_group"
|
||||
}
|
||||
}
|
||||
]
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "GPFAPH7J7BKJB",
|
||||
"type": "slack_based",
|
||||
"slack": {
|
||||
"id": "MEOW_SLACK_ID",
|
||||
"name": "Meow Group",
|
||||
"handle": "meow_group"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Unique | Description |
|
||||
|-----------|:------:|:------------|
|
||||
`id` | Yes| User Group ID
|
||||
`type` | No | [Slack-defined user groups](https://slack.com/intl/en-ru/help/articles/212906697-Create-a-user-group)
|
||||
`slack` | No | Metadata retrieved from Slack.
|
||||
| Parameter | Unique | Description |
|
||||
| --------- | :----: | :---------------------------------------------------------------------------------------------------- |
|
||||
| `id` | Yes | User Group ID |
|
||||
| `type` | No | [Slack-defined user groups](https://slack.com/intl/en-ru/help/articles/212906697-Create-a-user-group) |
|
||||
| `slack` | No | Metadata retrieved from Slack. |
|
||||
|
||||
**HTTP request**
|
||||
|
||||
`GET {{API_URL}}/api/v1/user_groups/`
|
||||
`GET {{API_URL}}/api/v1/user_groups/`
|
||||
|
|
|
|||
|
|
@ -1,35 +1,37 @@
|
|||
+++
|
||||
title = "Grafana OnCall Users HTTP API"
|
||||
aliases = ["/docs/grafana-cloud/oncall/oncall-api-reference/users/"]
|
||||
weight = 1500
|
||||
+++
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/oncall-api-reference/users/
|
||||
- /docs/oncall/latest/oncall-api-reference/users/
|
||||
title: Grafana OnCall Users HTTP API
|
||||
weight: 1500
|
||||
---
|
||||
|
||||
# Get a user
|
||||
|
||||
This endpoint retrieves the user object.
|
||||
|
||||
```shell
|
||||
````shell
|
||||
```shell
|
||||
curl "{{API_URL}}/api/v1/users/current/" \
|
||||
--request GET \
|
||||
--header "Authorization: meowmeowmeow" \
|
||||
--header "Content-Type: application/json"
|
||||
```
|
||||
````
|
||||
|
||||
The above command returns JSON structured in the following way:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "U4DNY931HHJS5",
|
||||
"email": "public-api-demo-user-1@grafana.com",
|
||||
"slack": [
|
||||
{
|
||||
"user_id": "UALEXSLACKDJPK",
|
||||
"team_id": "TALEXSLACKDJPK"
|
||||
}
|
||||
],
|
||||
"username": "alex",
|
||||
"role": "admin"
|
||||
"id": "U4DNY931HHJS5",
|
||||
"email": "public-api-demo-user-1@grafana.com",
|
||||
"slack": [
|
||||
{
|
||||
"user_id": "UALEXSLACKDJPK",
|
||||
"team_id": "TALEXSLACKDJPK"
|
||||
}
|
||||
],
|
||||
"username": "alex",
|
||||
"role": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -37,15 +39,15 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
`GET {{API_URL}}/api/v1/users/<USER_ID>/`
|
||||
|
||||
Use `{{API_URL}}/api/v1/users/current` to retrieve the current user.
|
||||
Use `{{API_URL}}/api/v1/users/current` to retrieve the current user.
|
||||
|
||||
| Parameter | Unique | Description |
|
||||
|-----------|:------:|:------------|
|
||||
`id` | Yes/org | User ID
|
||||
`email` | Yes/org | User e-mail
|
||||
`slack` | Yes/org | List of user IDs from connected Slack. User linking key is e-mail.
|
||||
`username` | Yes/org | User username
|
||||
`role` | No | One of: `user`, `observer`, `admin`.
|
||||
| Parameter | Unique | Description |
|
||||
| ---------- | :-----: | :----------------------------------------------------------------- |
|
||||
| `id` | Yes/org | User ID |
|
||||
| `email` | Yes/org | User e-mail |
|
||||
| `slack` | Yes/org | List of user IDs from connected Slack. User linking key is e-mail. |
|
||||
| `username` | Yes/org | User username |
|
||||
| `role` | No | One of: `user`, `observer`, `admin`. |
|
||||
|
||||
# List Users
|
||||
|
||||
|
|
@ -60,23 +62,23 @@ The above command returns JSON structured in the following way:
|
|||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "U4DNY931HHJS5",
|
||||
"email": "public-api-demo-user-1@grafana.com",
|
||||
"slack": [
|
||||
{
|
||||
"id": "U4DNY931HHJS5",
|
||||
"email": "public-api-demo-user-1@grafana.com",
|
||||
"slack": [
|
||||
{
|
||||
"user_id": "UALEXSLACKDJPK",
|
||||
"team_id": "TALEXSLACKDJPK"
|
||||
}
|
||||
],
|
||||
"username": "alex",
|
||||
"role": "admin"
|
||||
"user_id": "UALEXSLACKDJPK",
|
||||
"team_id": "TALEXSLACKDJPK"
|
||||
}
|
||||
]
|
||||
],
|
||||
"username": "alex",
|
||||
"role": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -84,8 +86,8 @@ This endpoint retrieves all users.
|
|||
|
||||
The following available filter parameter should be provided as a `GET` argument:
|
||||
|
||||
* `username` (Exact match)
|
||||
- `username` (Exact match)
|
||||
|
||||
**HTTP request**
|
||||
|
||||
`GET {{API_URL}}/api/v1/users/`
|
||||
`GET {{API_URL}}/api/v1/users/`
|
||||
|
|
|
|||
170
docs/sources/open-source.md
Normal file
170
docs/sources/open-source.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
aliases:
|
||||
- /docs/grafana-cloud/oncall/open-source/
|
||||
- /docs/oncall/latest/open-source/
|
||||
keywords:
|
||||
- Open Source
|
||||
title: Open Source
|
||||
weight: 100
|
||||
---
|
||||
|
||||
# Open Source
|
||||
|
||||
We prepared three environments for OSS users:
|
||||
- **Hobby** environment for local usage & playing around: [README.md](https://github.com/grafana/oncall#getting-started).
|
||||
- **Development** environment for contributors: [DEVELOPER.md](https://github.com/grafana/oncall/blob/dev/DEVELOPER.md)
|
||||
- **Production** environment for reliable cloud installation using Helm: [Production Environment](#production-environment)
|
||||
|
||||
## Production Environment
|
||||
|
||||
TBD
|
||||
|
||||
## Slack Setup
|
||||
|
||||
Grafana OnCall Slack integration use a lot of Slack API features:
|
||||
- Subscription on Slack events requires OnCall to be externally available and provide https endpoint.
|
||||
- You will need to register new Slack App.
|
||||
|
||||
1. Make sure your OnCall is up and running.
|
||||
|
||||
2. You need OnCall to be accessible through https. For development purposes we suggest using [localtunnel](https://github.com/localtunnel/localtunnel). For production purposes please consider setting up proper web server with HTTPS termination. For localtunnel:
|
||||
```bash
|
||||
# Choose the unique prefix instead of pretty-turkey-83
|
||||
# Localtunnel will generate an url, e.g. https://pretty-turkey-83.loca.lt
|
||||
# it is referred as <ONCALL_ENGINE_PUBLIC_URL> below
|
||||
lt --port 8000 -s pretty-turkey-83 --print-requests
|
||||
```
|
||||
|
||||
3. If you use localtunnel, open your external URL and click "Continue" to allow requests to bypass the warning page.
|
||||
|
||||
4. [Create a Slack Workspace](https://slack.com/create) for development, or use your company workspace.
|
||||
|
||||
5. Go to https://api.slack.com/apps and click Create New App button
|
||||
|
||||
6. Select `From an app manifest` option and choose the right workspace
|
||||
|
||||
7. Copy and paste the following block with the correct <YOUR_BOT_NAME> and <ONCALL_ENGINE_PUBLIC_URL> fields
|
||||
|
||||
```yaml
|
||||
_metadata:
|
||||
major_version: 1
|
||||
minor_version: 1
|
||||
display_information:
|
||||
name: <YOUR_BOT_NAME>
|
||||
features:
|
||||
app_home:
|
||||
home_tab_enabled: true
|
||||
messages_tab_enabled: true
|
||||
messages_tab_read_only_enabled: false
|
||||
bot_user:
|
||||
display_name: <YOUR_BOT_NAME>
|
||||
always_online: true
|
||||
shortcuts:
|
||||
- name: Create a new incident
|
||||
type: message
|
||||
callback_id: incident_create
|
||||
description: Creates a new OnCall incident
|
||||
- name: Add to postmortem
|
||||
type: message
|
||||
callback_id: add_postmortem
|
||||
description: Add this message to postmortem
|
||||
slash_commands:
|
||||
- command: /oncall
|
||||
url: <ONCALL_ENGINE_PUBLIC_URL>/slack/interactive_api_endpoint/
|
||||
description: oncall
|
||||
should_escape: false
|
||||
oauth_config:
|
||||
redirect_urls:
|
||||
- <ONCALL_ENGINE_PUBLIC_URL>/api/internal/v1/complete/slack-install-free/
|
||||
- <ONCALL_ENGINE_PUBLIC_URL>/api/internal/v1/complete/slack-login/
|
||||
scopes:
|
||||
user:
|
||||
- channels:read
|
||||
- chat:write
|
||||
- identify
|
||||
- users.profile:read
|
||||
bot:
|
||||
- app_mentions:read
|
||||
- channels:history
|
||||
- channels:read
|
||||
- chat:write
|
||||
- chat:write.customize
|
||||
- chat:write.public
|
||||
- commands
|
||||
- files:write
|
||||
- groups:history
|
||||
- groups:read
|
||||
- im:history
|
||||
- im:read
|
||||
- im:write
|
||||
- mpim:history
|
||||
- mpim:read
|
||||
- mpim:write
|
||||
- reactions:write
|
||||
- team:read
|
||||
- usergroups:read
|
||||
- usergroups:write
|
||||
- users.profile:read
|
||||
- users:read
|
||||
- users:read.email
|
||||
- users:write
|
||||
settings:
|
||||
event_subscriptions:
|
||||
request_url: <ONCALL_ENGINE_PUBLIC_URL>/slack/event_api_endpoint/
|
||||
bot_events:
|
||||
- app_home_opened
|
||||
- app_mention
|
||||
- channel_archive
|
||||
- channel_created
|
||||
- channel_deleted
|
||||
- channel_rename
|
||||
- channel_unarchive
|
||||
- member_joined_channel
|
||||
- message.channels
|
||||
- message.im
|
||||
- subteam_created
|
||||
- subteam_members_changed
|
||||
- subteam_updated
|
||||
- user_change
|
||||
interactivity:
|
||||
is_enabled: true
|
||||
request_url: <ONCALL_ENGINE_PUBLIC_URL>/slack/interactive_api_endpoint/
|
||||
org_deploy_enabled: false
|
||||
socket_mode_enabled: false
|
||||
```
|
||||
|
||||
6. Go to your "OnCall" -> "Env Variables" and set:
|
||||
```
|
||||
SLACK_CLIENT_OAUTH_ID = Basic Information -> App Credentials -> Client ID
|
||||
SLACK_CLIENT_OAUTH_SECRET = Basic Information -> App Credentials -> Client Secret
|
||||
SLACK_SIGNING_SECRET = Basic Information -> App Credentials -> Signing Secret
|
||||
SLACK_INSTALL_RETURN_REDIRECT_HOST = << OnCall external URL >>
|
||||
```
|
||||
|
||||
7. Go to "OnCall" -> "ChatOps" -> "Slack" and install Slack Integration
|
||||
|
||||
8. All set!
|
||||
|
||||
## Telegram Setup
|
||||
|
||||
- Telegram integrations requires OnCall to be externally available and provide https endpoint.
|
||||
- Telegram integration in OnCall is designed for collaborative team work. It requires Telegram Group and a Telegram Channel (private) for alerts.
|
||||
|
||||
1. Make sure your OnCall is up and running.
|
||||
|
||||
2. Respectfully ask [BotFather](https://t.me/BotFather) for a key, put it in `TELEGRAM_TOKEN` in "OnCall" -> "Env Variables".
|
||||
|
||||
3. Set `TELEGRAM_WEBHOOK_HOST` with your external url for OnCall.
|
||||
|
||||
4. Go to "OnCall" -> "ChatOps" -> Telegram and enjoy!
|
||||
|
||||
## Grafana OSS-Cloud Setup
|
||||
|
||||
Grafana OSS could be connected to Grafana Cloud for heartbeat and SMS / Phone Calls. We tried our best in making Grafana OSS <-> Cloud self-explanatory. Check "Cloud" page in your OSS OnCall instance.
|
||||
|
||||
Please note that it's possible either to use Grafana Cloud either Twilio for SMS/Phone calls.
|
||||
|
||||
## Twilio Setup
|
||||
|
||||
1. Make sure Grafana OSS <-> Cloud connector is disabled. Set `GRAFANA_CLOUD_NOTIFICATIONS_ENABLED` as False.
|
||||
2. Check "OnCall" -> "Env Variables" and set all variables starting with `TWILIO_`
|
||||
|
|
@ -266,7 +266,7 @@ class EscalationPolicySnapshot:
|
|||
escalation_policy_step=self.step,
|
||||
)
|
||||
else:
|
||||
notify_to_users_list = list_users_to_notify_from_ical(on_call_schedule)
|
||||
notify_to_users_list = list_users_to_notify_from_ical(on_call_schedule, include_viewers=True)
|
||||
if notify_to_users_list is None:
|
||||
log_record = AlertGroupLogRecord(
|
||||
type=AlertGroupLogRecord.TYPE_ESCALATION_FAILED,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import Optional
|
|||
from django.apps import apps
|
||||
from rest_framework import status
|
||||
|
||||
from apps.alerts.tasks import create_contact_points_for_datasource
|
||||
from apps.alerts.tasks import schedule_create_contact_points_for_datasource
|
||||
from apps.grafana_plugin.helpers import GrafanaAPIClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -77,16 +77,15 @@ class GrafanaAlertingSyncManager:
|
|||
# sync other datasource
|
||||
for datasource in datasources:
|
||||
if datasource["type"] == GrafanaAlertingSyncManager.ALERTING_DATASOURCE:
|
||||
if self.create_contact_point(datasource) is None:
|
||||
contact_point = self.create_contact_point(datasource)
|
||||
if contact_point is None:
|
||||
# Failed to create contact point duo to getting wrong alerting config. It is expected behaviour.
|
||||
# Add datasource to list and retry to create contact point for it async
|
||||
datasources_to_create.append(datasource)
|
||||
|
||||
if datasources_to_create:
|
||||
# create other contact points async
|
||||
create_contact_points_for_datasource.apply_async(
|
||||
(self.alert_receive_channel.pk, datasources_to_create),
|
||||
)
|
||||
schedule_create_contact_points_for_datasource(self.alert_receive_channel.pk, datasources_to_create)
|
||||
else:
|
||||
self.alert_receive_channel.is_finished_alerting_setup = True
|
||||
self.alert_receive_channel.save(update_fields=["is_finished_alerting_setup"])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from apps.alerts.incident_appearance.templaters.alert_templater import AlertTemplater
|
||||
from common.utils import clean_markup
|
||||
from common.utils import clean_markup, escape_for_twilio_phone_call
|
||||
|
||||
|
||||
class AlertPhoneCallTemplater(AlertTemplater):
|
||||
|
|
@ -24,8 +24,4 @@ class AlertPhoneCallTemplater(AlertTemplater):
|
|||
return sf.format(data)
|
||||
|
||||
def _escape(self, data):
|
||||
# https://www.twilio.com/docs/api/errors/12100
|
||||
data = data.replace("&", "&")
|
||||
data = data.replace(">", ">")
|
||||
data = data.replace("<", "<")
|
||||
return data
|
||||
return escape_for_twilio_phone_call(data)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ from django.db import migrations, models
|
|||
import django.db.models.deletion
|
||||
import django.db.models.manager
|
||||
|
||||
from apps.alerts.integration_options_mixin import IntegrationOptionsMixin
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
|
@ -132,7 +134,7 @@ class Migration(migrations.Migration):
|
|||
('public_primary_key', models.CharField(default=apps.alerts.models.alert_receive_channel.generate_public_primary_key_for_alert_receive_channel, max_length=20, unique=True, validators=[django.core.validators.MinLengthValidator(13)])),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('integration', models.CharField(choices=[('alertmanager', 'AlertManager'), ('grafana', 'Grafana'), ('grafana_alerting', 'Grafana Alerting'), ('formatted_webhook', 'Formatted Webhook'), ('webhook', 'Webhook'), ('amazon_sns', 'Amazon SNS'), ('heartbeat', 'Heartbeat'), ('inbound_email', 'Inboubd Email'), ('maintenance', 'Maintenance'), ('manual', 'Manual'), ('slack_channel', 'Slack Channel'), ('stackdriver', 'Stackdriver'), ('curler', 'Curler'), ('datadog', 'Datadog'), ('demo', 'Demo'), ('elastalert', 'Elastalert'), ('fabric', 'Fabric'), ('kapacitor', 'Kapacitor'), ('newrelic', 'New Relic'), ('pagerduty', 'Pagerduty'), ('pingdom', 'Pingdom'), ('prtg', 'PRTG'), ('sentry', 'Sentry'), ('uptimerobot', 'UptimeRobot'), ('zabbix', 'Zabbix')], default='grafana', max_length=100)),
|
||||
('integration', models.CharField(choices=IntegrationOptionsMixin.INTEGRATION_CHOICES,default=IntegrationOptionsMixin.DEFAULT_INTEGRATION, max_length=100)),
|
||||
('allow_source_based_resolving', models.BooleanField(default=True)),
|
||||
('token', models.CharField(db_index=True, default=apps.alerts.models.alert_receive_channel.random_token_generator, max_length=30)),
|
||||
('smile_code', models.TextField(default=':slightly_smiling_face:')),
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:42
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from django.utils import timezone, dateparse
|
||||
from apps.alerts.models.alert_receive_channel import number_to_smiles_translator
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
TYPE_SINGLE_EVENT = 0
|
||||
TYPE_RECURRENT_EVENT = 1
|
||||
FREQUENCY_WEEKLY = 1
|
||||
SOURCE_TERRAFORM = 3
|
||||
STEP_WAIT = 0
|
||||
STEP_NOTIFY_USERS_QUEUE = 12
|
||||
SOURCE_WEB = 1
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
Organization = apps.get_model('user_management', 'Organization')
|
||||
AlertReceiveChannel = apps.get_model('alerts', 'AlertReceiveChannel')
|
||||
EscalationChain = apps.get_model('alerts', 'EscalationChain')
|
||||
ChannelFilter = apps.get_model('alerts', 'ChannelFilter')
|
||||
EscalationPolicy = apps.get_model('alerts', 'EscalationPolicy')
|
||||
OnCallScheduleICal = apps.get_model('schedules', 'OnCallScheduleICal')
|
||||
AlertGroup = apps.get_model('alerts', 'AlertGroup')
|
||||
Alert = apps.get_model('alerts', 'Alert')
|
||||
CustomButton = apps.get_model("alerts", "CustomButton")
|
||||
CustomOnCallShift = apps.get_model('schedules', 'CustomOnCallShift')
|
||||
|
||||
organization = Organization.objects.get(public_primary_key=public_api_constants.DEMO_ORGANIZATION_ID)
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
alert_receive_channel, _ = AlertReceiveChannel.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_INTEGRATION_ID,
|
||||
defaults=dict(
|
||||
integration=0,
|
||||
author=user,
|
||||
organization=organization,
|
||||
smile_code=number_to_smiles_translator(0)
|
||||
)
|
||||
)
|
||||
escalation_chain, _ = EscalationChain.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_CHAIN_ID,
|
||||
defaults=dict(
|
||||
name="default",
|
||||
organization=organization,
|
||||
)
|
||||
)
|
||||
|
||||
channel_filter_1, _ = ChannelFilter.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ROUTE_ID_1,
|
||||
defaults=dict(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
slack_channel_id=public_api_constants.DEMO_SLACK_CHANNEL_FOR_ROUTE_ID,
|
||||
filtering_term='us-(east|west)',
|
||||
order=0,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
ChannelFilter.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ROUTE_ID_2,
|
||||
defaults=dict(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
slack_channel_id=public_api_constants.DEMO_SLACK_CHANNEL_FOR_ROUTE_ID,
|
||||
filtering_term='.*',
|
||||
order=1,
|
||||
is_default=True,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
|
||||
EscalationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_POLICY_ID_1,
|
||||
defaults=dict(
|
||||
step=STEP_WAIT,
|
||||
wait_delay=timezone.timedelta(minutes=1),
|
||||
order=0,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
|
||||
escalation_policy_1, _ = EscalationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_POLICY_ID_2,
|
||||
defaults=dict(
|
||||
step=STEP_NOTIFY_USERS_QUEUE,
|
||||
order=1,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
escalation_policy_1.notify_to_users_queue.add(user)
|
||||
|
||||
schedule, _ = OnCallScheduleICal.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_SCHEDULE_ID_ICAL,
|
||||
defaults=dict(
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_SCHEDULE_NAME_ICAL,
|
||||
ical_url_overrides=public_api_constants.DEMO_SCHEDULE_ICAL_URL_OVERRIDES,
|
||||
channel=public_api_constants.DEMO_SLACK_CHANNEL_SLACK_ID,
|
||||
)
|
||||
)
|
||||
|
||||
alert_group, _ = AlertGroup.all_objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_INCIDENT_ID,
|
||||
defaults=dict(
|
||||
channel=alert_receive_channel,
|
||||
channel_filter=channel_filter_1,
|
||||
resolved=True,
|
||||
resolved_at=dateparse.parse_datetime(public_api_constants.DEMO_INCIDENT_RESOLVED_AT),
|
||||
)
|
||||
)
|
||||
alert_group.started_at = dateparse.parse_datetime(public_api_constants.DEMO_INCIDENT_CREATED_AT)
|
||||
alert_group.save(update_fields=['started_at'])
|
||||
|
||||
for id, created_at in public_api_constants.DEMO_ALERT_IDS:
|
||||
alert, _ = Alert.objects.get_or_create(
|
||||
public_primary_key=id,
|
||||
defaults=dict(
|
||||
group=alert_group,
|
||||
raw_request_data=public_api_constants.DEMO_ALERT_PAYLOAD,
|
||||
title='Memory above 90% threshold',
|
||||
)
|
||||
)
|
||||
alert.created_at = dateparse.parse_datetime(created_at)
|
||||
alert.save(update_fields=['created_at'])
|
||||
|
||||
CustomButton.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_CUSTOM_ACTION_ID,
|
||||
defaults=dict(
|
||||
name=public_api_constants.DEMO_CUSTOM_ACTION_NAME,
|
||||
organization=organization,
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_1, _ = CustomOnCallShift.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ON_CALL_SHIFT_ID_1,
|
||||
defaults=dict(
|
||||
type=TYPE_SINGLE_EVENT,
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_ON_CALL_SHIFT_NAME_1,
|
||||
start=dateparse.parse_datetime(public_api_constants.DEMO_ON_CALL_SHIFT_START_1),
|
||||
duration=timezone.timedelta(seconds=public_api_constants.DEMO_ON_CALL_SHIFT_DURATION),
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_1.users.add(user)
|
||||
|
||||
on_call_shift_2, _ = CustomOnCallShift.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ON_CALL_SHIFT_ID_2,
|
||||
defaults=dict(
|
||||
type=TYPE_RECURRENT_EVENT,
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_ON_CALL_SHIFT_NAME_2,
|
||||
start=dateparse.parse_datetime(public_api_constants.DEMO_ON_CALL_SHIFT_START_2),
|
||||
duration=timezone.timedelta(seconds=public_api_constants.DEMO_ON_CALL_SHIFT_DURATION),
|
||||
frequency=FREQUENCY_WEEKLY,
|
||||
interval=2,
|
||||
by_day=public_api_constants.DEMO_ON_CALL_SHIFT_BY_DAY,
|
||||
source=SOURCE_TERRAFORM,
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_2.users.add(user)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances'),
|
||||
('schedules', '0002_squashed_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -113,20 +113,7 @@ class ChannelFilter(OrderedModel):
|
|||
return satisfied_filter
|
||||
|
||||
def is_satisfying(self, raw_request_data, title, message=None):
|
||||
AlertReceiveChannel = apps.get_model("alerts", "AlertReceiveChannel")
|
||||
|
||||
return (
|
||||
self.is_default
|
||||
or self.check_filter(json.dumps(raw_request_data))
|
||||
or self.check_filter(str(title))
|
||||
or
|
||||
# Special case for Amazon SNS
|
||||
(
|
||||
self.check_filter(str(message))
|
||||
if self.alert_receive_channel.integration == AlertReceiveChannel.INTEGRATION_AMAZON_SNS
|
||||
else False
|
||||
)
|
||||
)
|
||||
return self.is_default or self.check_filter(json.dumps(raw_request_data)) or self.check_filter(str(title))
|
||||
|
||||
def check_filter(self, value):
|
||||
return re.search(self.filtering_term, value)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from .calculcate_escalation_finish_time import calculate_escalation_finish_time
|
|||
from .call_ack_url import call_ack_url # noqa: F401
|
||||
from .check_escalation_finished import check_escalation_finished_task # noqa: F401
|
||||
from .create_contact_points_for_datasource import create_contact_points_for_datasource # noqa: F401
|
||||
from .create_contact_points_for_datasource import schedule_create_contact_points_for_datasource # noqa: F401
|
||||
from .custom_button_result import custom_button_result # noqa: F401
|
||||
from .delete_alert_group import delete_alert_group # noqa: F401
|
||||
from .distribute_alert import distribute_alert # noqa: F401
|
||||
|
|
|
|||
|
|
@ -1,9 +1,32 @@
|
|||
import logging
|
||||
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.apps import apps
|
||||
from django.core.cache import cache
|
||||
from rest_framework import status
|
||||
|
||||
from apps.grafana_plugin.helpers import GrafanaAPIClient
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id):
|
||||
CACHE_KEY_PREFIX = "create_contact_points_for_datasource"
|
||||
return f"{CACHE_KEY_PREFIX}_{alert_receive_channel_id}"
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task
|
||||
def schedule_create_contact_points_for_datasource(alert_receive_channel_id, datasource_list):
|
||||
CACHE_LIFETIME = 600
|
||||
START_TASK_DELAY = 3
|
||||
task = create_contact_points_for_datasource.apply_async(
|
||||
args=[alert_receive_channel_id, datasource_list], countdown=START_TASK_DELAY
|
||||
)
|
||||
cache_key = get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id)
|
||||
cache.set(cache_key, task.id, timeout=CACHE_LIFETIME)
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=10)
|
||||
def create_contact_points_for_datasource(alert_receive_channel_id, datasource_list):
|
||||
|
|
@ -11,6 +34,11 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
Try to create contact points for other datasource.
|
||||
Restart task for datasource, for which contact point was not created.
|
||||
"""
|
||||
cache_key = get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id)
|
||||
cached_task_id = cache.get(cache_key)
|
||||
current_task_id = create_contact_points_for_datasource.request.id
|
||||
if cached_task_id is not None and current_task_id != cached_task_id:
|
||||
return
|
||||
|
||||
AlertReceiveChannel = apps.get_model("alerts", "AlertReceiveChannel")
|
||||
|
||||
|
|
@ -21,7 +49,7 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
api_token=alert_receive_channel.organization.api_token,
|
||||
)
|
||||
# list of datasource for which contact point creation was failed
|
||||
datasource_to_create = []
|
||||
datasources_to_create = []
|
||||
for datasource in datasource_list:
|
||||
contact_point = None
|
||||
config, response_info = client.get_alerting_config(datasource["id"])
|
||||
|
|
@ -29,16 +57,22 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
if response_info.get("status_code") == status.HTTP_404_NOT_FOUND:
|
||||
client.get_alertmanager_status_with_config(datasource["id"])
|
||||
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
|
||||
elif response_info.get("status_code") == status.HTTP_400_BAD_REQUEST:
|
||||
logger.warning(
|
||||
f"Failed to create contact point for integration {alert_receive_channel_id}, "
|
||||
f"datasource info: {datasource}; response: {response_info}"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
|
||||
if contact_point is None:
|
||||
# Failed to create contact point duo to getting wrong alerting config.
|
||||
# Add datasource to list and retry to create contact point for it again
|
||||
datasource_to_create.append(datasource)
|
||||
datasources_to_create.append(datasource)
|
||||
|
||||
# if some contact points were not created, restart task for them
|
||||
if datasource_to_create:
|
||||
create_contact_points_for_datasource.apply_async((alert_receive_channel_id, datasource_to_create), countdown=5)
|
||||
if datasources_to_create:
|
||||
schedule_create_contact_points_for_datasource(alert_receive_channel_id, datasources_to_create)
|
||||
else:
|
||||
alert_receive_channel.is_finished_alerting_setup = True
|
||||
alert_receive_channel.save(update_fields=["is_finished_alerting_setup"])
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from apps.alerts.constants import NEXT_ESCALATION_DELAY
|
|||
from apps.alerts.incident_appearance.renderers.web_renderer import AlertGroupWebRenderer
|
||||
from apps.alerts.signals import user_notification_action_triggered_signal
|
||||
from apps.base.messaging import get_messaging_backend_from_id
|
||||
from apps.base.utils import live_settings
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
from .task_logger import task_logger
|
||||
|
|
@ -56,6 +57,13 @@ def notify_user_task(
|
|||
|
||||
if not user.is_notification_allowed:
|
||||
task_logger.info(f"notify_user_task: user {user.pk} notification is not allowed for role {user.role}")
|
||||
UserNotificationPolicyLogRecord(
|
||||
author=user,
|
||||
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
||||
reason=f"notification is not allowed for user with role {user.role}",
|
||||
alert_group=alert_group,
|
||||
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
|
||||
).save()
|
||||
return
|
||||
|
||||
user_has_notification, _ = UserHasNotification.objects.get_or_create(
|
||||
|
|
@ -257,11 +265,31 @@ def perform_notification(log_record_pk):
|
|||
).save()
|
||||
return
|
||||
|
||||
if not user.is_notification_allowed:
|
||||
UserNotificationPolicyLogRecord(
|
||||
author=user,
|
||||
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
||||
reason=f"notification is not allowed for user with role {user.role}",
|
||||
alert_group=alert_group,
|
||||
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
|
||||
).save()
|
||||
return
|
||||
|
||||
if notification_channel == UserNotificationPolicy.NotificationChannel.SMS:
|
||||
SMSMessage.send_sms(user, alert_group, notification_policy)
|
||||
SMSMessage.send_sms(
|
||||
user,
|
||||
alert_group,
|
||||
notification_policy,
|
||||
is_cloud_notification=live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED,
|
||||
)
|
||||
|
||||
elif notification_channel == UserNotificationPolicy.NotificationChannel.PHONE_CALL:
|
||||
PhoneCall.make_call(user, alert_group, notification_policy)
|
||||
PhoneCall.make_call(
|
||||
user,
|
||||
alert_group,
|
||||
notification_policy,
|
||||
is_cloud_notification=live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED,
|
||||
)
|
||||
|
||||
elif notification_channel == UserNotificationPolicy.NotificationChannel.TELEGRAM:
|
||||
if alert_group.notify_in_telegram_enabled is True:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
|
||||
from apps.alerts.incident_appearance.templaters import AlertSlackTemplater
|
||||
from apps.alerts.models import AlertGroup
|
||||
from apps.integrations.metadata.configuration import grafana
|
||||
from config_integrations import grafana
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ from apps.alerts.incident_appearance.templaters import (
|
|||
AlertWebTemplater,
|
||||
)
|
||||
from apps.alerts.models import Alert, AlertReceiveChannel
|
||||
from apps.integrations.metadata.configuration import grafana
|
||||
from common.jinja_templater import jinja_template_env
|
||||
from common.utils import getattrd
|
||||
from config_integrations import grafana
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from apps.alerts.escalation_snapshot.utils import eta_for_escalation_step_notify
|
|||
from apps.alerts.models import AlertGroupLogRecord, EscalationPolicy
|
||||
from apps.schedules.ical_utils import list_users_to_notify_from_ical
|
||||
from apps.schedules.models import CustomOnCallShift, OnCallScheduleCalendar
|
||||
from common.constants.role import Role
|
||||
|
||||
|
||||
def get_escalation_policy_snapshot_from_model(escalation_policy):
|
||||
|
|
@ -200,6 +201,55 @@ def test_escalation_step_notify_on_call_schedule(
|
|||
assert mocked_execute_tasks.called
|
||||
|
||||
|
||||
@patch("apps.alerts.escalation_snapshot.snapshot_classes.EscalationPolicySnapshot._execute_tasks", return_value=None)
|
||||
@pytest.mark.django_db
|
||||
def test_escalation_step_notify_on_call_schedule_viewer_user(
|
||||
mocked_execute_tasks,
|
||||
escalation_step_test_setup,
|
||||
make_user_for_organization,
|
||||
make_escalation_policy,
|
||||
make_schedule,
|
||||
make_on_call_shift,
|
||||
):
|
||||
organization, user, _, channel_filter, alert_group, reason = escalation_step_test_setup
|
||||
viewer = make_user_for_organization(organization=organization, role=Role.VIEWER)
|
||||
|
||||
schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar)
|
||||
# create on_call_shift with user to notify
|
||||
data = {
|
||||
"start": timezone.datetime.now().replace(microsecond=0),
|
||||
"duration": timezone.timedelta(seconds=7200),
|
||||
}
|
||||
on_call_shift = make_on_call_shift(
|
||||
organization=organization, shift_type=CustomOnCallShift.TYPE_SINGLE_EVENT, **data
|
||||
)
|
||||
on_call_shift.users.add(viewer)
|
||||
schedule.custom_on_call_shifts.add(on_call_shift)
|
||||
|
||||
notify_schedule_step = make_escalation_policy(
|
||||
escalation_chain=channel_filter.escalation_chain,
|
||||
escalation_policy_step=EscalationPolicy.STEP_NOTIFY_SCHEDULE,
|
||||
notify_schedule=schedule,
|
||||
)
|
||||
escalation_policy_snapshot = get_escalation_policy_snapshot_from_model(notify_schedule_step)
|
||||
expected_eta = timezone.now() + timezone.timedelta(seconds=NEXT_ESCALATION_DELAY)
|
||||
result = escalation_policy_snapshot.execute(alert_group, reason)
|
||||
expected_result = EscalationPolicySnapshot.StepExecutionResultData(
|
||||
eta=result.eta,
|
||||
stop_escalation=False,
|
||||
pause_escalation=False,
|
||||
start_from_beginning=False,
|
||||
)
|
||||
assert expected_eta + timezone.timedelta(seconds=15) > result.eta > expected_eta - timezone.timedelta(seconds=15)
|
||||
assert result == expected_result
|
||||
assert notify_schedule_step.log_records.filter(type=AlertGroupLogRecord.TYPE_ESCALATION_TRIGGERED).exists()
|
||||
assert list(escalation_policy_snapshot.notify_to_users_queue) == list(
|
||||
list_users_to_notify_from_ical(schedule, include_viewers=True)
|
||||
)
|
||||
assert list(escalation_policy_snapshot.notify_to_users_queue) == [viewer]
|
||||
assert mocked_execute_tasks.called
|
||||
|
||||
|
||||
@patch("apps.alerts.escalation_snapshot.snapshot_classes.EscalationPolicySnapshot._execute_tasks", return_value=None)
|
||||
@pytest.mark.django_db
|
||||
def test_escalation_step_notify_user_group(
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from apps.alerts.tasks.notify_user import perform_notification
|
||||
from apps.alerts.tasks.notify_user import notify_user_task, perform_notification
|
||||
from apps.base.models.user_notification_policy import UserNotificationPolicy
|
||||
from apps.base.models.user_notification_policy_log_record import UserNotificationPolicyLogRecord
|
||||
from common.constants.role import Role
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -118,3 +119,62 @@ def test_notify_user_missing_data_errors(
|
|||
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
||||
assert error_log_record.reason == "Expected data is missing"
|
||||
assert error_log_record.notification_error_code is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notify_user_perform_notification_error_if_viewer(
|
||||
make_organization,
|
||||
make_user,
|
||||
make_user_notification_policy,
|
||||
make_alert_receive_channel,
|
||||
make_alert_group,
|
||||
make_user_notification_policy_log_record,
|
||||
):
|
||||
organization = make_organization()
|
||||
user_1 = make_user(organization=organization, role=Role.VIEWER, _verified_phone_number="1234567890")
|
||||
user_notification_policy = make_user_notification_policy(
|
||||
user=user_1,
|
||||
step=UserNotificationPolicy.Step.NOTIFY,
|
||||
notify_by=UserNotificationPolicy.NotificationChannel.SMS,
|
||||
)
|
||||
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
||||
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
||||
log_record = make_user_notification_policy_log_record(
|
||||
author=user_1,
|
||||
alert_group=alert_group,
|
||||
notification_policy=user_notification_policy,
|
||||
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED,
|
||||
)
|
||||
|
||||
perform_notification(log_record.pk)
|
||||
|
||||
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
||||
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
||||
assert error_log_record.reason == f"notification is not allowed for user with role {user_1.role}"
|
||||
assert (
|
||||
error_log_record.notification_error_code
|
||||
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notify_user_error_if_viewer(
|
||||
make_organization,
|
||||
make_user,
|
||||
make_alert_receive_channel,
|
||||
make_alert_group,
|
||||
):
|
||||
organization = make_organization()
|
||||
user_1 = make_user(organization=organization, role=Role.VIEWER, _verified_phone_number="1234567890")
|
||||
alert_receive_channel = make_alert_receive_channel(organization=organization)
|
||||
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
|
||||
|
||||
notify_user_task(user_1.pk, alert_group.pk)
|
||||
|
||||
error_log_record = UserNotificationPolicyLogRecord.objects.last()
|
||||
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
|
||||
assert error_log_record.reason == f"notification is not allowed for user with role {user_1.role}"
|
||||
assert (
|
||||
error_log_record.notification_error_code
|
||||
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.api.serializers.telegram import TelegramToUserConnectorSerializer
|
||||
from apps.base.constants import ADMIN_PERMISSIONS, ALL_ROLES_PERMISSIONS, EDITOR_PERMISSIONS
|
||||
from apps.base.messaging import get_messaging_backends
|
||||
from apps.base.models import UserNotificationPolicy
|
||||
from apps.base.utils import live_settings
|
||||
from apps.oss_installation.utils import cloud_user_identity_status
|
||||
from apps.twilioapp.utils import check_phone_number_is_valid
|
||||
from apps.user_management.models import User
|
||||
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
|
||||
|
|
@ -30,6 +33,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
|
||||
permissions = serializers.SerializerMethodField()
|
||||
notification_chain_verbal = serializers.SerializerMethodField()
|
||||
cloud_connection_status = serializers.SerializerMethodField()
|
||||
|
||||
SELECT_RELATED = ["telegram_verification_code", "telegram_connection", "organization", "slack_user_identity"]
|
||||
|
||||
|
|
@ -50,6 +54,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
"messaging_backends",
|
||||
"permissions",
|
||||
"notification_chain_verbal",
|
||||
"cloud_connection_status",
|
||||
]
|
||||
read_only_fields = [
|
||||
"email",
|
||||
|
|
@ -88,6 +93,15 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
default, important = UserNotificationPolicy.get_short_verbals_for_user(user=obj)
|
||||
return {"default": " - ".join(default), "important": " - ".join(important)}
|
||||
|
||||
def get_cloud_connection_status(self, obj):
|
||||
if settings.OSS_INSTALLATION and live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
connector = self.context.get("connector", None)
|
||||
identities = self.context.get("cloud_identities", {})
|
||||
identity = identities.get(obj.email, None)
|
||||
status, _ = cloud_user_identity_status(connector, identity)
|
||||
return status
|
||||
return None
|
||||
|
||||
|
||||
class UserHiddenFieldsSerializer(UserSerializer):
|
||||
available_for_all_roles_fields = [
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ from django.urls import reverse
|
|||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from apps.api.views.features import FEATURE_LIVE_SETTINGS, FEATURE_SLACK, FEATURE_TELEGRAM
|
||||
from apps.api.views.features import (
|
||||
FEATURE_GRAFANA_CLOUD_CONNECTION,
|
||||
FEATURE_GRAFANA_CLOUD_NOTIFICATIONS,
|
||||
FEATURE_LIVE_SETTINGS,
|
||||
FEATURE_SLACK,
|
||||
FEATURE_TELEGRAM,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -30,15 +36,24 @@ def test_select_features_all_enabled(
|
|||
make_user_auth_headers,
|
||||
):
|
||||
organization, user, token = make_organization_and_user_with_plugin_token()
|
||||
settings.OSS_INSTALLATION = True
|
||||
settings.FEATURE_SLACK_INTEGRATION_ENABLED = True
|
||||
settings.FEATURE_TELEGRAM_INTEGRATION_ENABLED = True
|
||||
settings.FEATURE_LIVE_SETTINGS_ENABLED = True
|
||||
settings.FEATURE_GRAFANA_CLOUD_CONNECTION = True
|
||||
settings.FEATURE_GRAFANA_CLOUD_NOTIFICATIONS = True
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:features")
|
||||
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json() == [FEATURE_SLACK, FEATURE_TELEGRAM, FEATURE_LIVE_SETTINGS]
|
||||
assert response.json() == [
|
||||
FEATURE_SLACK,
|
||||
FEATURE_TELEGRAM,
|
||||
FEATURE_GRAFANA_CLOUD_CONNECTION,
|
||||
FEATURE_LIVE_SETTINGS,
|
||||
FEATURE_GRAFANA_CLOUD_NOTIFICATIONS,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -48,9 +63,12 @@ def test_select_features_all_disabled(
|
|||
make_user_auth_headers,
|
||||
):
|
||||
organization, user, token = make_organization_and_user_with_plugin_token()
|
||||
settings.OSS_INSTALLATION = False
|
||||
settings.FEATURE_SLACK_INTEGRATION_ENABLED = False
|
||||
settings.FEATURE_TELEGRAM_INTEGRATION_ENABLED = False
|
||||
settings.FEATURE_LIVE_SETTINGS_ENABLED = False
|
||||
settings.FEATURE_GRAFANA_CLOUD_CONNECTION = False
|
||||
settings.FEATURE_GRAFANA_CLOUD_NOTIFICATIONS = FEATURE_GRAFANA_CLOUD_NOTIFICATIONS
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:features")
|
||||
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ def test_update_user_cant_change_email_and_username(
|
|||
"user": admin.username,
|
||||
}
|
||||
},
|
||||
"cloud_connection_status": 0,
|
||||
"permissions": ADMIN_PERMISSIONS,
|
||||
"notification_chain_verbal": {"default": "", "important": ""},
|
||||
"slack_user_identity": None,
|
||||
|
|
@ -124,6 +125,7 @@ def test_list_users(
|
|||
"notification_chain_verbal": {"default": "", "important": ""},
|
||||
"slack_user_identity": None,
|
||||
"avatar": admin.avatar_url,
|
||||
"cloud_connection_status": 0,
|
||||
},
|
||||
{
|
||||
"pk": editor.public_primary_key,
|
||||
|
|
@ -144,6 +146,7 @@ def test_list_users(
|
|||
"notification_chain_verbal": {"default": "", "important": ""},
|
||||
"slack_user_identity": None,
|
||||
"avatar": editor.avatar_url,
|
||||
"cloud_connection_status": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.base.utils import live_settings
|
||||
|
||||
FEATURE_SLACK = "slack"
|
||||
FEATURE_TELEGRAM = "telegram"
|
||||
FEATURE_LIVE_SETTINGS = "live_settings"
|
||||
MOBILE_APP_PUSH_NOTIFICATIONS = "mobile_app"
|
||||
FEATURE_GRAFANA_CLOUD_NOTIFICATIONS = "grafana_cloud_notifications"
|
||||
FEATURE_GRAFANA_CLOUD_CONNECTION = "grafana_cloud_connection"
|
||||
|
||||
|
||||
class FeaturesAPIView(APIView):
|
||||
|
|
@ -31,9 +34,6 @@ class FeaturesAPIView(APIView):
|
|||
if settings.FEATURE_TELEGRAM_INTEGRATION_ENABLED:
|
||||
enabled_features.append(FEATURE_TELEGRAM)
|
||||
|
||||
if settings.FEATURE_LIVE_SETTINGS_ENABLED:
|
||||
enabled_features.append(FEATURE_LIVE_SETTINGS)
|
||||
|
||||
if settings.MOBILE_APP_PUSH_NOTIFICATIONS_ENABLED:
|
||||
DynamicSetting = apps.get_model("base", "DynamicSetting")
|
||||
mobile_app_settings = DynamicSetting.objects.get_or_create(
|
||||
|
|
@ -48,4 +48,12 @@ class FeaturesAPIView(APIView):
|
|||
if request.auth.organization.pk in mobile_app_settings.json_value["org_ids"]:
|
||||
enabled_features.append(MOBILE_APP_PUSH_NOTIFICATIONS)
|
||||
|
||||
if settings.OSS_INSTALLATION:
|
||||
# Features below should be enabled only in OSS
|
||||
enabled_features.append(FEATURE_GRAFANA_CLOUD_CONNECTION)
|
||||
if settings.FEATURE_LIVE_SETTINGS_ENABLED:
|
||||
enabled_features.append(FEATURE_LIVE_SETTINGS)
|
||||
if live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
enabled_features.append(FEATURE_GRAFANA_CLOUD_NOTIFICATIONS)
|
||||
|
||||
return enabled_features
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from apps.api.serializers.live_setting import LiveSettingSerializer
|
|||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.base.models import LiveSetting
|
||||
from apps.base.utils import live_settings
|
||||
from apps.oss_installation.tasks import sync_users_with_cloud
|
||||
from apps.slack.tasks import unpopulate_slack_user_identities
|
||||
from apps.telegram.client import TelegramClient
|
||||
from apps.telegram.tasks import register_telegram_webhook
|
||||
|
|
@ -32,13 +33,19 @@ class LiveSettingViewSet(PublicPrimaryKeyMixin, viewsets.ModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
LiveSetting.populate_settings_if_needed()
|
||||
return LiveSetting.objects.filter(name__in=LiveSetting.AVAILABLE_NAMES).order_by("name")
|
||||
queryset = LiveSetting.objects.filter(name__in=LiveSetting.AVAILABLE_NAMES).order_by("name")
|
||||
search = self.request.query_params.get("search", None)
|
||||
if search:
|
||||
queryset = queryset.filter(name=search)
|
||||
return queryset
|
||||
|
||||
def perform_update(self, serializer):
|
||||
new_value = serializer.validated_data["value"]
|
||||
self._update_hook(new_value)
|
||||
|
||||
super().perform_update(serializer)
|
||||
instance = serializer.save()
|
||||
sync_users = self.request.query_params.get("sync_users", "true") == "true"
|
||||
if instance.name == "GRAFANA_CLOUD_ONCALL_TOKEN" and sync_users:
|
||||
sync_users_with_cloud.apply_async()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
new_value = instance.default_value
|
||||
|
|
@ -66,6 +73,17 @@ class LiveSettingViewSet(PublicPrimaryKeyMixin, viewsets.ModelViewSet):
|
|||
if sti is not None:
|
||||
unpopulate_slack_user_identities.apply_async((sti.pk, True), countdown=0)
|
||||
|
||||
if instance.name == "GRAFANA_CLOUD_ONCALL_TOKEN":
|
||||
from apps.oss_installation.models import CloudConnector
|
||||
|
||||
try:
|
||||
old_token = live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
except ImproperlyConfigured:
|
||||
old_token = None
|
||||
|
||||
if old_token != new_value:
|
||||
CloudConnector.remove_sync()
|
||||
|
||||
def _reset_telegram_integration(self, new_token):
|
||||
# tell Telegram to cancel sending events from old bot
|
||||
with suppress(ImproperlyConfigured, error.InvalidToken, error.Unauthorized):
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from apps.auth_token.models import UserScheduleExportAuthToken
|
|||
from apps.auth_token.models.mobile_app_auth_token import MobileAppAuthToken
|
||||
from apps.auth_token.models.mobile_app_verification_token import MobileAppVerificationToken
|
||||
from apps.base.messaging import get_messaging_backend_from_id
|
||||
from apps.base.utils import live_settings
|
||||
from apps.telegram.client import TelegramClient
|
||||
from apps.telegram.models import TelegramVerificationCode
|
||||
from apps.twilioapp.phone_manager import PhoneManager
|
||||
|
|
@ -56,7 +57,19 @@ class CurrentUserView(APIView):
|
|||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request):
|
||||
serializer = UserSerializer(request.user, context={"request": self.request})
|
||||
context = {"request": self.request, "format": self.format_kwarg, "view": self}
|
||||
|
||||
if settings.OSS_INSTALLATION and live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
|
||||
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
cloud_identities = list(CloudUserIdentity.objects.filter(email__in=[request.user.email]))
|
||||
cloud_identities = {cloud_identity.email: cloud_identity for cloud_identity in cloud_identities}
|
||||
context["cloud_identities"] = cloud_identities
|
||||
context["connector"] = connector
|
||||
|
||||
serializer = UserSerializer(request.user, context=context)
|
||||
return Response(serializer.data)
|
||||
|
||||
def put(self, request):
|
||||
|
|
@ -179,6 +192,46 @@ class UserView(
|
|||
|
||||
return queryset.order_by("id")
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
context = {"request": self.request, "format": self.format_kwarg, "view": self}
|
||||
if settings.OSS_INSTALLATION:
|
||||
if live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
|
||||
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
emails = list(queryset.values_list("email", flat=True))
|
||||
cloud_identities = list(CloudUserIdentity.objects.filter(email__in=emails))
|
||||
cloud_identities = {cloud_identity.email: cloud_identity for cloud_identity in cloud_identities}
|
||||
context["cloud_identities"] = cloud_identities
|
||||
context["connector"] = connector
|
||||
serializer = self.get_serializer(page, many=True, context=context)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
context = {"request": self.request, "format": self.format_kwarg, "view": self}
|
||||
instance = self.get_object()
|
||||
|
||||
if settings.OSS_INSTALLATION and live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
|
||||
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
cloud_identities = list(CloudUserIdentity.objects.filter(email__in=[instance.email]))
|
||||
cloud_identities = {cloud_identity.email: cloud_identity for cloud_identity in cloud_identities}
|
||||
context["cloud_identities"] = cloud_identities
|
||||
context["connector"] = connector
|
||||
|
||||
serializer = self.get_serializer(instance, context=context)
|
||||
return Response(serializer.data)
|
||||
|
||||
def current(self, request):
|
||||
serializer = UserSerializer(self.get_queryset().get(pk=self.request.user.pk))
|
||||
return Response(serializer.data)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
|
|||
from rest_framework.request import Request
|
||||
|
||||
from apps.grafana_plugin.helpers.gcom import check_token
|
||||
from apps.public_api import constants as public_api_constants
|
||||
from apps.user_management.models import User
|
||||
from apps.user_management.models.organization import Organization
|
||||
from common.constants.role import Role
|
||||
|
|
@ -29,12 +28,6 @@ class ApiTokenAuthentication(BaseAuthentication):
|
|||
|
||||
def authenticate(self, request):
|
||||
auth = get_authorization_header(request).decode("utf-8")
|
||||
|
||||
if auth == public_api_constants.DEMO_AUTH_TOKEN:
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
auth_token = user.auth_tokens.first()
|
||||
return user, auth_token
|
||||
|
||||
user, auth_token = self.authenticate_credentials(auth)
|
||||
|
||||
if user.role != Role.ADMIN:
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 13:02
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
|
||||
from apps.auth_token import constants
|
||||
from apps.auth_token import crypto
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
Organization = apps.get_model('user_management', 'Organization')
|
||||
ApiAuthToken = apps.get_model('auth_token', 'ApiAuthToken')
|
||||
|
||||
organization = Organization.objects.get(public_primary_key=public_api_constants.DEMO_ORGANIZATION_ID)
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
token_string = crypto.generate_token_string()
|
||||
digest = crypto.hash_token_string(token_string)
|
||||
|
||||
ApiAuthToken.objects.get_or_create(
|
||||
name=public_api_constants.DEMO_AUTH_TOKEN,
|
||||
user=user,
|
||||
organization=organization,
|
||||
defaults=dict(token_key=token_string[:constants.TOKEN_KEY_LENGTH], digest=digest)
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth_token', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:45
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from django.utils import timezone
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
STEP_WAIT = 0
|
||||
STEP_NOTIFY = 1
|
||||
NOTIFY_BY_SMS = 1
|
||||
NOTIFY_BY_PHONE = 2
|
||||
FIVE_MINUTES = timezone.timedelta(minutes=5)
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
UserNotificationPolicy = apps.get_model("base", "UserNotificationPolicy")
|
||||
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_1,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
notify_by=NOTIFY_BY_SMS,
|
||||
step=STEP_NOTIFY,
|
||||
order=0,
|
||||
)
|
||||
)
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_2,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
step=STEP_WAIT,
|
||||
wait_delay=FIVE_MINUTES,
|
||||
order=1,
|
||||
)
|
||||
)
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_3,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
step=STEP_NOTIFY,
|
||||
notify_by=NOTIFY_BY_PHONE,
|
||||
order=2,
|
||||
)
|
||||
)
|
||||
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_4,
|
||||
defaults=dict(
|
||||
important=True,
|
||||
user=user,
|
||||
notify_by=NOTIFY_BY_PHONE,
|
||||
order=0,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -38,35 +38,45 @@ class LiveSetting(models.Model):
|
|||
"TWILIO_NUMBER",
|
||||
"TWILIO_VERIFY_SERVICE_SID",
|
||||
"TELEGRAM_TOKEN",
|
||||
"TELEGRAM_WEBHOOK_HOST",
|
||||
"SLACK_CLIENT_OAUTH_ID",
|
||||
"SLACK_CLIENT_OAUTH_SECRET",
|
||||
"SLACK_SIGNING_SECRET",
|
||||
"SLACK_INSTALL_RETURN_REDIRECT_HOST",
|
||||
"SEND_ANONYMOUS_USAGE_STATS",
|
||||
"GRAFANA_CLOUD_ONCALL_TOKEN",
|
||||
"GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED",
|
||||
"GRAFANA_CLOUD_NOTIFICATIONS_ENABLED",
|
||||
)
|
||||
|
||||
DESCRIPTIONS = {
|
||||
"SLACK_SIGNING_SECRET": (
|
||||
"Check <a href='"
|
||||
"https://github.com/grafana/amixr/blob/main/DEVELOPER.md#slack-application-setup"
|
||||
"'>this instruction</a> for details how to set up Slack. "
|
||||
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
||||
"'>instruction</a> for details how to set up Slack. "
|
||||
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
||||
"after you update Slack credentials."
|
||||
"after you update them."
|
||||
),
|
||||
"SLACK_CLIENT_OAUTH_SECRET": (
|
||||
"Check <a href='"
|
||||
"https://github.com/grafana/amixr/blob/main/DEVELOPER.md#slack-application-setup"
|
||||
"'>this instruction</a> for details how to set up Slack. "
|
||||
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
||||
"'>instruction</a> for details how to set up Slack. "
|
||||
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
||||
"after you update Slack credentials."
|
||||
"after you update them."
|
||||
),
|
||||
"SLACK_CLIENT_OAUTH_ID": (
|
||||
"Check <a href='"
|
||||
"https://github.com/grafana/amixr/blob/main/DEVELOPER.md#slack-application-setup"
|
||||
"'>this instruction</a> for details how to set up Slack. "
|
||||
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
||||
"'>instruction</a> for details how to set up Slack. "
|
||||
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
||||
"after you update Slack credentials."
|
||||
"after you update them."
|
||||
),
|
||||
"SLACK_INSTALL_RETURN_REDIRECT_HOST": (
|
||||
"Check <a href='"
|
||||
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
||||
"'>instruction</a> for details how to set up Slack. "
|
||||
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
||||
"after you update them."
|
||||
),
|
||||
"TWILIO_ACCOUNT_SID": (
|
||||
"Twilio username to allow amixr send sms and make phone calls, "
|
||||
|
|
@ -99,13 +109,17 @@ class LiveSetting(models.Model):
|
|||
"TELEGRAM_TOKEN": (
|
||||
"Secret token for Telegram bot, you can get one via " "<a href='https://t.me/BotFather'>BotFather</a>."
|
||||
),
|
||||
"TELEGRAM_WEBHOOK_HOST": (
|
||||
"Externally available URL for Telegram to make requests. Please restart OnCall backend after after update."
|
||||
),
|
||||
"SEND_ANONYMOUS_USAGE_STATS": (
|
||||
"Grafana OnCall will send anonymous, but uniquely-identifiable usage analytics to Grafana Labs."
|
||||
" These statistics are sent to https://stats.grafana.org/. For more information on what's sent, look at"
|
||||
"https://github.com/..." # TODO: add url to usage stats code
|
||||
" https://github.com/grafana/oncall/blob/dev/engine/apps/oss_installation/usage_stats.py#L29"
|
||||
),
|
||||
"GRAFANA_CLOUD_ONCALL_TOKEN": "Secret token for Grafana Cloud OnCall instance.",
|
||||
"GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED": "Enable hearbeat integration with Grafana Cloud OnCall.",
|
||||
"GRAFANA_CLOUD_NOTIFICATIONS_ENABLED": "Enable SMS/call notifications via Grafana Cloud OnCall",
|
||||
}
|
||||
|
||||
SECRET_SETTING_NAMES = (
|
||||
|
|
@ -171,4 +185,5 @@ class LiveSetting(models.Model):
|
|||
)
|
||||
|
||||
self.error = LiveSettingValidator(live_setting=self).get_error()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ class UserNotificationPolicyLogRecord(models.Model):
|
|||
ERROR_NOTIFICATION_IN_SLACK_CHANNEL_IS_ARCHIVED,
|
||||
ERROR_NOTIFICATION_IN_SLACK_RATELIMIT,
|
||||
ERROR_NOTIFICATION_MESSAGING_BACKEND_ERROR,
|
||||
) = range(25)
|
||||
ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
|
||||
) = range(26)
|
||||
|
||||
# for this errors we want to send message to general log channel
|
||||
ERRORS_TO_SEND_IN_SLACK_CHANNEL = [
|
||||
|
|
@ -266,6 +267,10 @@ class UserNotificationPolicyLogRecord(models.Model):
|
|||
result += f"failed to notify {user_verbal} in Slack, because channel is archived"
|
||||
elif self.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_RATELIMIT:
|
||||
result += f"failed to notify {user_verbal} in Slack due to Slack rate limit"
|
||||
elif (
|
||||
self.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
|
||||
):
|
||||
result += f"failed to notify {user_verbal}, not allowed role"
|
||||
else:
|
||||
# TODO: handle specific backend errors
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -94,6 +94,13 @@ class LiveSettingValidator:
|
|||
except Exception as e:
|
||||
return f"Telegram error: {str(e)}"
|
||||
|
||||
@classmethod
|
||||
def _check_grafana_cloud_oncall_token(cls, grafana_oncall_token):
|
||||
from apps.oss_installation.models import CloudConnector
|
||||
|
||||
_, err = CloudConnector.sync_with_cloud(grafana_oncall_token)
|
||||
return err
|
||||
|
||||
@staticmethod
|
||||
def _is_email_valid(email):
|
||||
return re.match(r"^[^@]+@[^@]+\.[^@]+$", email)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.utils import timezone
|
|||
|
||||
from apps.grafana_plugin.helpers import GcomAPIClient
|
||||
from apps.grafana_plugin.helpers.gcom import get_active_instance_ids
|
||||
from apps.public_api.constants import DEMO_ORGANIZATION_ID
|
||||
from apps.user_management.models import Organization
|
||||
from apps.user_management.sync import sync_organization
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
|
@ -23,9 +22,7 @@ SYNC_PERIOD = timezone.timedelta(minutes=25)
|
|||
def start_sync_organizations():
|
||||
sync_threshold = timezone.now() - SYNC_PERIOD
|
||||
|
||||
organization_qs = Organization.objects.exclude(public_primary_key=DEMO_ORGANIZATION_ID).filter(
|
||||
last_time_synced__lte=sync_threshold
|
||||
)
|
||||
organization_qs = Organization.objects.filter(last_time_synced__lte=sync_threshold)
|
||||
|
||||
active_instance_ids, is_cloud_configured = get_active_instance_ids()
|
||||
if is_cloud_configured:
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
# Main
|
||||
enabled = True
|
||||
title = "Amazon SNS"
|
||||
slug = "amazon_sns"
|
||||
short_description = None
|
||||
is_displayed_on_web = True
|
||||
description = None
|
||||
is_featured = False
|
||||
is_able_to_autoresolve = True
|
||||
is_demo_alert_enabled = True
|
||||
|
||||
description = None
|
||||
|
||||
# Default templates
|
||||
slack_title = """\
|
||||
{% if payload|length == 0 -%}
|
||||
{% set title = payload.get("AlarmName", "Alert") %}
|
||||
{%- else -%}
|
||||
{% set title = "Alert" %}
|
||||
{%- endif %}
|
||||
|
||||
*<{{ grafana_oncall_link }}|#{{ grafana_oncall_incident_id }} {{ title }}>* via {{ integration_name }}
|
||||
{% if source_link %}
|
||||
(*<{{ source_link }}|source>*)
|
||||
{%- endif %}"""
|
||||
|
||||
slack_message = """\
|
||||
{% if payload|length == 1 and "message" in payload -%}
|
||||
{{ payload.get("message", "Non-JSON payload received. Please make sure you publish monitoring Alarms to SNS, not logs: https://docs.amixr.io/#/integrations/amazon_sns") }}
|
||||
{%- else -%}
|
||||
*State* {{ payload.get("NewStateValue", "NO") }}
|
||||
Region: {{ payload.get("Region", "Undefined") }}
|
||||
_Description_: {{ payload.get("AlarmDescription", "Undefined") }}
|
||||
{%- endif %}
|
||||
"""
|
||||
|
||||
slack_image_url = None
|
||||
|
||||
web_title = """\
|
||||
{% if payload|length == 0 -%}
|
||||
{{ payload.get("AlarmName", "Alert")}}
|
||||
{%- else -%}
|
||||
Alert
|
||||
{%- endif %}"""
|
||||
|
||||
web_message = """\
|
||||
{% if payload|length == 1 and "message" in payload -%}
|
||||
{{ payload.get("message", "Non-JSON payload received. Please make sure you publish monitoring Alarms to SNS, not logs: https://docs.amixr.io/#/integrations/amazon_sns") }}
|
||||
{%- else -%}
|
||||
**State** {{ payload.get("NewStateValue", "NO") }}
|
||||
Region: {{ payload.get("Region", "Undefined") }}
|
||||
*Description*: {{ payload.get("AlarmDescription", "Undefined") }}
|
||||
{%- endif %}
|
||||
"""
|
||||
|
||||
web_image_url = slack_image_url
|
||||
|
||||
sms_title = web_title
|
||||
|
||||
phone_call_title = web_title
|
||||
|
||||
email_title = web_title
|
||||
|
||||
email_message = "{{ payload|tojson_pretty }}"
|
||||
|
||||
telegram_title = sms_title
|
||||
|
||||
telegram_message = """\
|
||||
{% if payload|length == 1 and "message" in payload -%}
|
||||
{{ payload.get("message", "Non-JSON payload received. Please make sure you publish monitoring Alarms to SNS, not logs: https://docs.amixr.io/#/integrations/amazon_sns") }}
|
||||
{%- else -%}
|
||||
<b>State</b> {{ payload.get("NewStateValue", "NO") }}
|
||||
Region: {{ payload.get("Region", "Undefined") }}
|
||||
<i>Description</i>: {{ payload.get("AlarmDescription", "Undefined") }}
|
||||
{%- endif %}
|
||||
"""
|
||||
|
||||
telegram_image_url = slack_image_url
|
||||
|
||||
source_link = """\
|
||||
{% if payload|length == 0 -%}
|
||||
{% if payload.get("Trigger", {}).get("Namespace") == "AWS/ElasticBeanstalk" -%}
|
||||
https://console.aws.amazon.com/elasticbeanstalk/home?region={{ payload.get("TopicArn").split(":")[3] }}
|
||||
{%- else -%}
|
||||
https://console.aws.amazon.com/cloudwatch//home?region={{ payload.get("TopicArn").split(":")[3] }}
|
||||
{%- endif %}
|
||||
{%- endif %}"""
|
||||
|
||||
grouping_id = web_title
|
||||
|
||||
resolve_condition = """\
|
||||
{{ payload.get("NewStateValue", "") == "OK" }}
|
||||
"""
|
||||
|
||||
acknowledge_condition = None
|
||||
|
||||
group_verbose_name = web_title
|
||||
|
||||
example_payload = {"foo": "bar"}
|
||||
110
engine/apps/oss_installation/cloud_heartbeat.py
Normal file
110
engine/apps/oss_installation/cloud_heartbeat.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import logging
|
||||
import random
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
|
||||
from apps.base.utils import live_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_heartbeat_integration(name=None):
|
||||
"""Setup Grafana Cloud OnCall heartbeat integration."""
|
||||
CloudHeartbeat = apps.get_model("oss_installation", "CloudHeartbeat")
|
||||
|
||||
cloud_heartbeat = None
|
||||
api_token = live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
if not live_settings.GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED or not api_token:
|
||||
return cloud_heartbeat
|
||||
# don't specify a team in the data, so heartbeat integration will be created in the General.
|
||||
name = name or f"OnCall Cloud Heartbeat {settings.BASE_URL}"
|
||||
data = {"type": "formatted_webhook", "name": name}
|
||||
url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "/api/v1/integrations/")
|
||||
try:
|
||||
headers = {"Authorization": api_token}
|
||||
r = requests.post(url=url, data=data, headers=headers, timeout=5)
|
||||
if r.status_code == status.HTTP_201_CREATED:
|
||||
response_data = r.json()
|
||||
cloud_heartbeat, _ = CloudHeartbeat.objects.update_or_create(
|
||||
defaults={"integration_id": response_data["id"], "integration_url": response_data["heartbeat"]["link"]}
|
||||
)
|
||||
if r.status_code == status.HTTP_400_BAD_REQUEST:
|
||||
response_data = r.json()
|
||||
error = response_data["detail"]
|
||||
if error == "Integration with this name already exists":
|
||||
response = requests.get(url=f"{url}?name={name}", headers=headers)
|
||||
integrations = response.json().get("results", [])
|
||||
if len(integrations) == 1:
|
||||
integration = integrations[0]
|
||||
cloud_heartbeat, _ = CloudHeartbeat.objects.update_or_create(
|
||||
defaults={
|
||||
"integration_id": integration["id"],
|
||||
"integration_url": integration["heartbeat"]["link"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
setup_heartbeat_integration(f"{name} { random.randint(1, 1024)}")
|
||||
except requests.Timeout:
|
||||
logger.warning("Unable to create cloud heartbeat integration. Request timeout.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to create cloud heartbeat integration. Request exception {str(e)}.")
|
||||
return cloud_heartbeat
|
||||
|
||||
|
||||
def send_cloud_heartbeat():
|
||||
CloudHeartbeat = apps.get_model("oss_installation", "CloudHeartbeat")
|
||||
CloudConnector = apps.get_model("oss_installation", "CloudConnector")
|
||||
"""Send heartbeat to Grafana Cloud OnCall integration."""
|
||||
if not live_settings.GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED or not live_settings.GRAFANA_CLOUD_ONCALL_TOKEN:
|
||||
logger.info(
|
||||
"Unable to send cloud heartbeat. Check values for GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED and GRAFANA_CLOUD_ONCALL_TOKEN."
|
||||
)
|
||||
return
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is None:
|
||||
logger.info("Unable to send cloud heartbeat. Cloud is not connected")
|
||||
return
|
||||
logger.info("Start send cloud heartbeat")
|
||||
try:
|
||||
cloud_heartbeat = CloudHeartbeat.objects.get()
|
||||
except CloudHeartbeat.DoesNotExist:
|
||||
cloud_heartbeat = setup_heartbeat_integration()
|
||||
|
||||
if cloud_heartbeat is None:
|
||||
logger.warning("Unable to setup cloud heartbeat integration.")
|
||||
return
|
||||
cloud_heartbeat.success = False
|
||||
try:
|
||||
response = requests.get(cloud_heartbeat.integration_url, timeout=5)
|
||||
logger.info(f"Send cloud heartbeat with response {response.status_code}")
|
||||
except requests.Timeout:
|
||||
logger.warning("Unable to send cloud heartbeat. Request timeout.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to send cloud heartbeat. Request exception {str(e)}.")
|
||||
else:
|
||||
if response.status_code == status.HTTP_200_OK:
|
||||
cloud_heartbeat.success = True
|
||||
logger.info("Successfully send cloud heartbeat")
|
||||
elif response.status_code == status.HTTP_403_FORBIDDEN:
|
||||
# check for 403 because AlertChannelDefiningMixin returns 403 if no integration was found.
|
||||
logger.info("Failed to send cloud heartbeat. Integration was not created yet")
|
||||
# force re-creation on next run
|
||||
cloud_heartbeat.delete()
|
||||
else:
|
||||
logger.info(f"Failed to send cloud heartbeat. response {response.status_code}")
|
||||
# save result of cloud heartbeat if it wasn't deleted
|
||||
if cloud_heartbeat.pk is not None:
|
||||
cloud_heartbeat.save()
|
||||
logger.info("Finish send cloud heartbeat")
|
||||
|
||||
|
||||
def get_heartbeat_link(connector, heartbeat):
|
||||
if connector is None:
|
||||
return None
|
||||
if heartbeat is None:
|
||||
return None
|
||||
return urljoin(connector.cloud_url, f"a/grafana-oncall-app/?page=integrations&id={heartbeat.integration_id}")
|
||||
4
engine/apps/oss_installation/constants.py
Normal file
4
engine/apps/oss_installation/constants.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
CLOUD_NOT_SYNCED = 0
|
||||
CLOUD_SYNCED_USER_NOT_FOUND = 1
|
||||
CLOUD_SYNCED_PHONE_NOT_VERIFIED = 2
|
||||
CLOUD_SYNCED_PHONE_VERIFIED = 3
|
||||
|
|
@ -30,4 +30,20 @@ class Migration(migrations.Migration):
|
|||
('report_sent_at', models.DateTimeField(default=None, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CloudConnector',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('cloud_url', models.URLField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CloudUserIdentity',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('phone_number_verified', models.BooleanField(default=False)),
|
||||
('cloud_id', models.CharField(max_length=20)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
from .heartbeat import CloudHeartbeat # noqa: F401
|
||||
from .cloud_connector import CloudConnector # noqa: F401
|
||||
from .cloud_heartbeat import CloudHeartbeat # noqa: F401
|
||||
from .cloud_user_identity import CloudUserIdentity # noqa: F401
|
||||
from .oss_installation import OssInstallation # noqa: F401
|
||||
|
|
|
|||
155
engine/apps/oss_installation/models/cloud_connector.py
Normal file
155
engine/apps/oss_installation/models/cloud_connector.py
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import logging
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from django.db import models, transaction
|
||||
|
||||
from apps.base.utils import live_settings
|
||||
from apps.oss_installation.models.cloud_user_identity import CloudUserIdentity
|
||||
from apps.user_management.models import User
|
||||
from common.constants.role import Role
|
||||
from settings.base import GRAFANA_CLOUD_ONCALL_API_URL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CloudConnector(models.Model):
|
||||
"""
|
||||
CloudOrganizationConnector model represents connection between oss organization and cloud organization.
|
||||
"""
|
||||
|
||||
cloud_url = models.URLField()
|
||||
|
||||
@classmethod
|
||||
def sync_with_cloud(cls, token=None):
|
||||
"""
|
||||
sync_with_cloud sync organization with cloud organization defined by provided GRAFANA_CLOUD_ONCALL_TOKEN.
|
||||
"""
|
||||
sync_status = False
|
||||
error_msg = None
|
||||
|
||||
api_token = token or live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
if api_token is None:
|
||||
logger.warning("Unable to sync with cloud. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
|
||||
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
|
||||
else:
|
||||
info_url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/info/")
|
||||
try:
|
||||
r = requests.get(info_url, headers={"AUTHORIZATION": api_token}, timeout=5)
|
||||
if r.status_code == 200:
|
||||
connector, _ = cls.objects.get_or_create()
|
||||
connector.cloud_url = r.json()["url"]
|
||||
connector.save()
|
||||
elif r.status_code == 403:
|
||||
logger.warning("Unable to sync with cloud. GRAFANA_CLOUD_ONCALL_TOKEN is invalid")
|
||||
error_msg = "Invalid token"
|
||||
else:
|
||||
error_msg = f"Non-200 HTTP code. Got {r.status_code}"
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to sync with cloud. Request exception {str(e)}")
|
||||
error_msg = f"Unable to sync with cloud"
|
||||
|
||||
return sync_status, error_msg
|
||||
|
||||
def sync_users_with_cloud(self) -> tuple[bool, str]:
|
||||
sync_status = False
|
||||
error_msg = None
|
||||
|
||||
api_token = live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
if api_token is None:
|
||||
logger.warning("Unable to sync with cloud. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
|
||||
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
|
||||
|
||||
existing_emails = list(User.objects.filter(role__in=(Role.ADMIN, Role.EDITOR)).values_list("email", flat=True))
|
||||
matching_users = []
|
||||
users_url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/users")
|
||||
|
||||
fetch_next_page = True
|
||||
users_fetched = True
|
||||
page = 1
|
||||
while fetch_next_page:
|
||||
try:
|
||||
url = urljoin(users_url, f"?page={page}&?short=true")
|
||||
r = requests.get(url, headers={"AUTHORIZATION": api_token}, timeout=5)
|
||||
if r.status_code != 200:
|
||||
logger.warning(
|
||||
f"Unable to fetch page {page} while sync_users_with_cloud. Response status code {r.status_code}"
|
||||
)
|
||||
error_msg = f"Non-200 HTTP code. Got {r.status_code}"
|
||||
users_fetched = False
|
||||
break
|
||||
data = r.json()
|
||||
matching_users.extend(list(filter(lambda u: (u["email"] in existing_emails), data["results"])))
|
||||
page += 1
|
||||
if data["next"] is None:
|
||||
fetch_next_page = False
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to sync users with cloud. Request exception {str(e)}")
|
||||
error_msg = f"Unable to sync with cloud"
|
||||
users_fetched = False
|
||||
break
|
||||
|
||||
if users_fetched:
|
||||
with transaction.atomic():
|
||||
cloud_users_identities_to_create = []
|
||||
for user in matching_users:
|
||||
cloud_users_identities_to_create.append(
|
||||
CloudUserIdentity(
|
||||
cloud_id=user["id"],
|
||||
email=user["email"],
|
||||
phone_number_verified=user["is_phone_number_verified"],
|
||||
)
|
||||
)
|
||||
|
||||
CloudUserIdentity.objects.all().delete()
|
||||
CloudUserIdentity.objects.bulk_create(cloud_users_identities_to_create, batch_size=1000)
|
||||
sync_status = True
|
||||
return sync_status, error_msg
|
||||
|
||||
def sync_user_with_cloud(self, user):
|
||||
sync_status = False
|
||||
error_msg = None
|
||||
|
||||
api_token = live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
if api_token is None:
|
||||
logger.warning(f"Unable to sync_user_with cloud user_id {user.id}. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
|
||||
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
|
||||
else:
|
||||
url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, f"api/v1/users/?email={user.email}")
|
||||
try:
|
||||
r = requests.get(url, headers={"AUTHORIZATION": api_token}, timeout=5)
|
||||
if r.status_code != 200:
|
||||
logger.warning(
|
||||
f"Unable to sync_user_with_cloud user_id {user.id}. Response status code {r.status_code}"
|
||||
)
|
||||
error_msg = f"Non-200 HTTP code. Got {r.status_code}"
|
||||
else:
|
||||
data = r.json()
|
||||
if len(data["results"]) != 0:
|
||||
cloud_used_data = data["results"][0]
|
||||
with transaction.atomic():
|
||||
CloudUserIdentity.objects.filter(email=user.email).delete()
|
||||
CloudUserIdentity.objects.create(
|
||||
email=user.email,
|
||||
phone_number_verified=cloud_used_data["is_phone_number_verified"],
|
||||
cloud_id=cloud_used_data["id"],
|
||||
)
|
||||
sync_status = True
|
||||
else:
|
||||
logger.warning(
|
||||
f"Unable to sync_user_with_cloud user_id {user.id}. User with {user.email} not found"
|
||||
)
|
||||
error_msg = f"User with email not found {user.email}"
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to sync_user_with cloud user_id {user.id}. Request exception {str(e)}")
|
||||
error_msg = f"Unable to sync with cloud"
|
||||
|
||||
return sync_status, error_msg
|
||||
|
||||
@classmethod
|
||||
def remove_sync(cls):
|
||||
from apps.oss_installation.models import CloudHeartbeat
|
||||
|
||||
cls.objects.all().delete()
|
||||
CloudUserIdentity.objects.all().delete()
|
||||
CloudHeartbeat.objects.all().delete()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class CloudUserIdentity(models.Model):
|
||||
phone_number_verified = models.BooleanField(default=False)
|
||||
cloud_id = models.CharField(max_length=20)
|
||||
email = models.EmailField()
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
import logging
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OssInstallation(models.Model):
|
||||
"""
|
||||
OssInstallation is model to track installation of OSS OnCall version.
|
||||
"""
|
||||
|
||||
installation_id = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
created_at = models.DateTimeField(auto_now=True)
|
||||
report_sent_at = models.DateTimeField(null=True, default=None)
|
||||
|
|
|
|||
1
engine/apps/oss_installation/serializers/__init__.py
Normal file
1
engine/apps/oss_installation/serializers/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .cloud_user import CloudUserSerializer # noqa: F401
|
||||
20
engine/apps/oss_installation/serializers/cloud_user.py
Normal file
20
engine/apps/oss_installation/serializers/cloud_user.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
|
||||
from apps.oss_installation.utils import cloud_user_identity_status
|
||||
from apps.user_management.models import User
|
||||
|
||||
|
||||
class CloudUserSerializer(serializers.ModelSerializer):
|
||||
cloud_data = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["cloud_data"]
|
||||
|
||||
def get_cloud_data(self, obj):
|
||||
connector = CloudConnector.objects.filter().first()
|
||||
cloud_user_identity = CloudUserIdentity.objects.filter(email=obj.email).first()
|
||||
status, link = cloud_user_identity_status(connector, cloud_user_identity)
|
||||
cloud_data = {"status": status, "link": link}
|
||||
return cloud_data
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.conf import settings
|
||||
from django.apps import apps
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
|
||||
from apps.base.utils import live_settings
|
||||
from apps.oss_installation.models import CloudHeartbeat, OssInstallation
|
||||
from apps.oss_installation.cloud_heartbeat import send_cloud_heartbeat
|
||||
from apps.oss_installation.usage_stats import UsageStatsService
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
|
|
@ -17,6 +13,8 @@ logger = get_task_logger(__name__)
|
|||
@shared_dedicated_queue_retry_task()
|
||||
def send_usage_stats_report():
|
||||
logger.info("Start send_usage_stats_report")
|
||||
OssInstallation = apps.get_model("oss_installation", "OssInstallation")
|
||||
|
||||
installation = OssInstallation.objects.get_or_create()[0]
|
||||
enabled = live_settings.SEND_ANONYMOUS_USAGE_STATS
|
||||
if enabled:
|
||||
|
|
@ -30,66 +28,24 @@ def send_usage_stats_report():
|
|||
logger.info("Finish send_usage_stats_report")
|
||||
|
||||
|
||||
def _setup_heartbeat_integration():
|
||||
"""Setup Grafana Cloud OnCall heartbeat integration."""
|
||||
cloud_heartbeat = None
|
||||
api_token = live_settings.GRAFANA_CLOUD_ONCALL_TOKEN
|
||||
# don't specify a team in the data, so heartbeat integration will be created in the General.
|
||||
data = {"type": "formatted_webhook", "name": f"OnCall {settings.BASE_URL}"}
|
||||
url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "/api/v1/integrations/")
|
||||
try:
|
||||
headers = {"Authorization": api_token}
|
||||
r = requests.post(url=url, data=data, headers=headers, timeout=5)
|
||||
if r.status_code == status.HTTP_201_CREATED:
|
||||
response_data = r.json()
|
||||
cloud_heartbeat, _ = CloudHeartbeat.objects.update_or_create(
|
||||
defaults={"integration_id": response_data["id"], "integration_url": response_data["heartbeat"]["link"]}
|
||||
)
|
||||
except requests.Timeout:
|
||||
logger.warning("Unable to create cloud heartbeat integration. Request timeout.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to create cloud heartbeat integration. Request exception {str(e)}.")
|
||||
return cloud_heartbeat
|
||||
@shared_dedicated_queue_retry_task()
|
||||
def send_cloud_heartbeat_task():
|
||||
send_cloud_heartbeat()
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task()
|
||||
def send_cloud_heartbeat():
|
||||
"""Send heartbeat to Grafana Cloud OnCall integration."""
|
||||
if not live_settings.GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED or not live_settings.GRAFANA_CLOUD_ONCALL_TOKEN:
|
||||
logger.info(
|
||||
"Unable to send cloud heartbeat. Check values for GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED and GRAFANA_CLOUD_ONCALL_TOKEN."
|
||||
)
|
||||
return
|
||||
|
||||
logger.info("Start send cloud heartbeat")
|
||||
try:
|
||||
cloud_heartbeat = CloudHeartbeat.objects.get()
|
||||
except CloudHeartbeat.DoesNotExist:
|
||||
cloud_heartbeat = _setup_heartbeat_integration()
|
||||
|
||||
if cloud_heartbeat is None:
|
||||
logger.warning("Unable to setup cloud heartbeat integration.")
|
||||
return
|
||||
cloud_heartbeat.success = False
|
||||
try:
|
||||
response = requests.get(cloud_heartbeat.integration_url, timeout=5)
|
||||
logger.info(f"Send cloud heartbeat with response {response.status_code}")
|
||||
except requests.Timeout:
|
||||
logger.warning("Unable to send cloud heartbeat. Request timeout.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Unable to send cloud heartbeat. Request exception {str(e)}.")
|
||||
else:
|
||||
if response.status_code == status.HTTP_200_OK:
|
||||
cloud_heartbeat.success = True
|
||||
logger.info("Successfully send cloud heartbeat")
|
||||
elif response.status_code == status.HTTP_403_FORBIDDEN:
|
||||
# check for 403 because AlertChannelDefiningMixin returns 403 if no integration was found.
|
||||
logger.info("Failed to send cloud heartbeat. Integration was not created yet")
|
||||
# force re-creation on next run
|
||||
cloud_heartbeat.delete()
|
||||
def sync_users_with_cloud():
|
||||
CloudConnector = apps.get_model("oss_installation", "CloudConnector")
|
||||
logger.info("Start sync_users_with_cloud")
|
||||
if live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
status, error = connector.sync_users_with_cloud()
|
||||
log_message = "Users synced. Status {status}."
|
||||
if error:
|
||||
log_message += f" Error {error}"
|
||||
logger.info(log_message)
|
||||
else:
|
||||
logger.info(f"Failed to send cloud heartbeat. response {response.status_code}")
|
||||
# save result of cloud heartbeat if it wasn't deleted
|
||||
if cloud_heartbeat.pk is not None:
|
||||
cloud_heartbeat.save()
|
||||
logger.info("Finish send cloud heartbeat")
|
||||
logger.info("Grafana Cloud is not connected")
|
||||
else:
|
||||
logger.info("GRAFANA_CLOUD_NOTIFICATIONS_ENABLED is not enabled")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
from common.api_helpers.optional_slash_router import optional_slash_path
|
||||
from django.urls import include, path
|
||||
|
||||
from .views import CloudHeartbeatStatusView
|
||||
from common.api_helpers.optional_slash_router import OptionalSlashRouter, optional_slash_path
|
||||
|
||||
from .views import CloudConnectionView, CloudHeartbeatView, CloudUsersView, CloudUserView
|
||||
|
||||
router = OptionalSlashRouter()
|
||||
router.register("cloud_users", CloudUserView, basename="cloud-users")
|
||||
|
||||
urlpatterns = [
|
||||
optional_slash_path("cloud_heartbeat_status", CloudHeartbeatStatusView.as_view(), name="cloud_heartbeat_status"),
|
||||
path("", include(router.urls)),
|
||||
optional_slash_path("cloud_users", CloudUsersView.as_view(), name="cloud-users-list"),
|
||||
optional_slash_path("cloud_connection", CloudConnectionView.as_view(), name="cloud-connection-status"),
|
||||
optional_slash_path("cloud_heartbeat", CloudHeartbeatView.as_view(), name="cloud-heartbeat"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import platform
|
|||
from dataclasses import asdict, dataclass
|
||||
|
||||
import requests
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.db.models import Sum
|
||||
|
||||
from apps.alerts.models import AlertGroupCounter
|
||||
from apps.oss_installation.models import OssInstallation
|
||||
from apps.oss_installation.utils import active_oss_users_count
|
||||
|
||||
USAGE_STATS_URL = "https://stats.grafana.org/oncall-usage-report"
|
||||
|
|
@ -27,9 +27,12 @@ class UsageStatsReport:
|
|||
|
||||
class UsageStatsService:
|
||||
def get_usage_stats_report(self):
|
||||
OssInstallation = apps.get_model("oss_installation", "OssInstallation")
|
||||
metrics = {}
|
||||
metrics["active_users_count"] = active_oss_users_count()
|
||||
total_alert_groups = AlertGroupCounter.objects.aggregate(Sum("value")).get("value__sum", 0)
|
||||
total_alert_groups = AlertGroupCounter.objects.aggregate(Sum("value")).get("value__sum", None)
|
||||
if total_alert_groups is None:
|
||||
total_alert_groups = 0
|
||||
metrics["alert_groups_count"] = total_alert_groups
|
||||
|
||||
usage_stats_id = OssInstallation.objects.get_or_create()[0].installation_id
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
from contextlib import suppress
|
||||
import logging
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.alerts.models import AlertGroupLogRecord, EscalationPolicy
|
||||
from apps.base.models import UserNotificationPolicyLogRecord
|
||||
from apps.public_api.constants import DEMO_USER_ID
|
||||
from apps.oss_installation import constants as oss_constants
|
||||
from apps.schedules.ical_utils import list_users_to_notify_from_ical_for_period
|
||||
from apps.schedules.models import OnCallSchedule
|
||||
from apps.user_management.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def active_oss_users_count():
|
||||
"""
|
||||
active_oss_users_count returns count of active users of oss installation.
|
||||
"""
|
||||
OnCallSchedule = apps.get_model("schedules", "OnCallSchedule")
|
||||
AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord")
|
||||
EscalationPolicy = apps.get_model("alerts", "EscalationPolicy")
|
||||
UserNotificationPolicyLogRecord = apps.get_model("base", "UserNotificationPolicyLogRecord")
|
||||
|
||||
# Take logs for previous 24 hours
|
||||
start = timezone.now() - timezone.timedelta(hours=24)
|
||||
|
|
@ -62,9 +66,21 @@ def active_oss_users_count():
|
|||
for user in users_from_schedule:
|
||||
unique_active_users.add(user.pk)
|
||||
|
||||
# Remove demo user from active users
|
||||
with suppress(User.DoesNotExist):
|
||||
demo_user = User.objects.get(public_primary_key=DEMO_USER_ID)
|
||||
with suppress(KeyError):
|
||||
unique_active_users.remove(demo_user.pk)
|
||||
return len(unique_active_users)
|
||||
|
||||
|
||||
def cloud_user_identity_status(connector, identity):
|
||||
link = None
|
||||
if connector is None:
|
||||
status = oss_constants.CLOUD_NOT_SYNCED
|
||||
elif identity is None:
|
||||
status = oss_constants.CLOUD_SYNCED_USER_NOT_FOUND
|
||||
link = connector.cloud_url
|
||||
else:
|
||||
if identity.phone_number_verified:
|
||||
status = oss_constants.CLOUD_SYNCED_PHONE_VERIFIED
|
||||
else:
|
||||
status = oss_constants.CLOUD_SYNCED_PHONE_NOT_VERIFIED
|
||||
|
||||
link = urljoin(connector.cloud_url, f"a/grafana-oncall-app/?page=users&p=1&id={identity.cloud_id}")
|
||||
return status, link
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
from .cloud_heartbeat_status import CloudHeartbeatStatusView # noqa: F401
|
||||
from .cloud_connection import CloudConnectionView # noqa: F401
|
||||
from .cloud_heartbeat import CloudHeartbeatView # noqa: F401
|
||||
from .cloud_users import CloudUsersView, CloudUserView # noqa: F401
|
||||
|
|
|
|||
39
engine/apps/oss_installation/views/cloud_connection.py
Normal file
39
engine/apps/oss_installation/views/cloud_connection.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.api.permissions import IsAdmin
|
||||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.base.models import LiveSetting
|
||||
from apps.base.utils import live_settings
|
||||
from apps.oss_installation.cloud_heartbeat import get_heartbeat_link
|
||||
from apps.oss_installation.models import CloudConnector, CloudHeartbeat
|
||||
|
||||
|
||||
class CloudConnectionView(APIView):
|
||||
authentication_classes = (PluginAuthentication,)
|
||||
permission_classes = (IsAuthenticated, IsAdmin)
|
||||
|
||||
def get(self, request):
|
||||
connector = CloudConnector.objects.first()
|
||||
heartbeat = CloudHeartbeat.objects.first()
|
||||
response = {
|
||||
"cloud_connection_status": connector is not None,
|
||||
"cloud_notifications_enabled": live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED,
|
||||
"cloud_heartbeat_enabled": live_settings.GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED,
|
||||
"cloud_heartbeat_link": get_heartbeat_link(connector, heartbeat),
|
||||
"cloud_heartbeat_status": heartbeat is not None and heartbeat.success,
|
||||
}
|
||||
return Response(response)
|
||||
|
||||
def delete(self, request):
|
||||
s = LiveSetting.objects.filter(name="GRAFANA_CLOUD_ONCALL_TOKEN").first()
|
||||
if s is not None:
|
||||
s.value = None
|
||||
s.save()
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is None:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
connector.remove_sync()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
27
engine/apps/oss_installation/views/cloud_heartbeat.py
Normal file
27
engine/apps/oss_installation/views/cloud_heartbeat.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.api.permissions import IsAdmin
|
||||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.oss_installation.cloud_heartbeat import get_heartbeat_link, setup_heartbeat_integration
|
||||
from apps.oss_installation.models import CloudConnector, CloudHeartbeat
|
||||
|
||||
|
||||
class CloudHeartbeatView(APIView):
|
||||
authentication_classes = (PluginAuthentication,)
|
||||
permission_classes = (IsAuthenticated, IsAdmin)
|
||||
|
||||
def post(self, request):
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
try:
|
||||
CloudHeartbeat.objects.get()
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "Cloud heartbeat already exists"})
|
||||
except CloudHeartbeat.DoesNotExist:
|
||||
heartbeat = setup_heartbeat_integration()
|
||||
link = get_heartbeat_link(connector, heartbeat)
|
||||
return Response(status=status.HTTP_200_OK, data={"link": link})
|
||||
else:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "Grafana Cloud is not connected"})
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.oss_installation.models import CloudHeartbeat
|
||||
|
||||
|
||||
class CloudHeartbeatStatusView(APIView):
|
||||
authentication_classes = (PluginAuthentication,)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request):
|
||||
response = {"status": CloudHeartbeat.status()}
|
||||
return Response(response)
|
||||
107
engine/apps/oss_installation/views/cloud_users.py
Normal file
107
engine/apps/oss_installation/views/cloud_users.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from rest_framework import mixins, status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.api.permissions import ActionPermission, AnyRole, IsAdmin, IsOwnerOrAdmin
|
||||
from apps.auth_token.auth import PluginAuthentication
|
||||
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
|
||||
from apps.oss_installation.serializers import CloudUserSerializer
|
||||
from apps.oss_installation.utils import cloud_user_identity_status
|
||||
from apps.user_management.models import User
|
||||
from common.api_helpers.mixins import PublicPrimaryKeyMixin
|
||||
from common.api_helpers.paginators import HundredPageSizePaginator
|
||||
from common.constants.role import Role
|
||||
|
||||
|
||||
class CloudUsersView(HundredPageSizePaginator, APIView):
|
||||
authentication_classes = (PluginAuthentication,)
|
||||
permission_classes = (IsAuthenticated, IsAdmin)
|
||||
|
||||
def get(self, request):
|
||||
organization = request.user.organization
|
||||
|
||||
queryset = User.objects.filter(organization=organization, role__in=[Role.ADMIN, Role.EDITOR])
|
||||
|
||||
if request.user.current_team is not None:
|
||||
queryset = queryset.filter(teams=request.user.current_team).distinct()
|
||||
emails = list(queryset.values_list("email", flat=True))
|
||||
|
||||
results = self.paginate_queryset(queryset, request, view=self)
|
||||
|
||||
cloud_identities = list(CloudUserIdentity.objects.filter(email__in=emails))
|
||||
cloud_identities = {cloud_identity.email: cloud_identity for cloud_identity in cloud_identities}
|
||||
|
||||
response = []
|
||||
|
||||
connector = CloudConnector.objects.first()
|
||||
|
||||
for user in results:
|
||||
cloud_identity = cloud_identities.get(user.email, None)
|
||||
status, link = cloud_user_identity_status(connector, cloud_identity)
|
||||
response.append(
|
||||
{
|
||||
"id": user.public_primary_key,
|
||||
"email": user.email,
|
||||
"username": user.username,
|
||||
"cloud_data": {"status": status, "link": link},
|
||||
}
|
||||
)
|
||||
|
||||
return self.get_paginated_response_with_matched_users_count(response, len(cloud_identities))
|
||||
|
||||
def get_paginated_response_with_matched_users_count(self, data, matched_users_count):
|
||||
return Response(
|
||||
OrderedDict(
|
||||
[
|
||||
("count", self.page.paginator.count),
|
||||
("matched_users_count", matched_users_count),
|
||||
("next", self.get_next_link()),
|
||||
("previous", self.get_previous_link()),
|
||||
("results", data),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
sync_status, err = connector.sync_users_with_cloud()
|
||||
return Response(status=status.HTTP_200_OK, data={"status": sync_status, "error": err})
|
||||
else:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "Grafana Cloud is not connected"})
|
||||
|
||||
|
||||
class CloudUserView(
|
||||
PublicPrimaryKeyMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
authentication_classes = (PluginAuthentication,)
|
||||
permission_classes = (IsAuthenticated, ActionPermission)
|
||||
|
||||
action_permissions = {
|
||||
AnyRole: ("retrieve",),
|
||||
IsAdmin: ("sync",),
|
||||
}
|
||||
action_object_permissions = {
|
||||
IsOwnerOrAdmin: ("retrieve", "sync"),
|
||||
}
|
||||
serializer_class = CloudUserSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = User.objects.filter(organization=self.request.user.organization)
|
||||
return queryset
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def sync(self, request, pk):
|
||||
user = self.get_object()
|
||||
connector = CloudConnector.objects.first()
|
||||
if connector is not None:
|
||||
sync_status, err = connector.sync_user_with_cloud(user)
|
||||
return Response(status=status.HTTP_200_OK, data={"status": sync_status, "error": err})
|
||||
else:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "Grafana Cloud is not connected"})
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue