diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 72036a42..d8108344 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,4 +4,14 @@ CHANGELOG.md /grafana-plugin @grafana/grafana-oncall-frontend + /docs @grafana/docs-gops + +# `make docs` procedure is owned by @jdbaldry of @grafana/docs-squad. +/.github/workflows/update-make-docs.yml @jdbaldry +/.github/workflows/publish-technical-documentation-next.yml @jdbaldry +/.github/workflows/publish-technical-documentation-release.yml @jdbaldry +/docs/Makefile @jdbaldry +/docs/docs.mk @jdbaldry +/docs/make-docs @jdbaldry +/docs/variables.mk @jdbaldry diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index 7d1e298a..ac5db291 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -83,8 +83,9 @@ jobs: # -e HUGO_REFLINKSERRORLEVEL=ERROR prevents merging broken refs with the downside # that no refs to external content can be used as these refs will not resolve in the # docs-base image. + # Use alternative image (dbd975af06) until make-docs 3.0.0 is rolled out everywhere. run: | - docker run -v ${PWD}/docs/sources:/hugo/content/docs/oncall/latest -e HUGO_REFLINKSERRORLEVEL=ERROR --rm grafana/docs-base:latest /bin/bash -c 'make hugo' + docker run -v ${PWD}/docs/sources:/hugo/content/docs/oncall/latest -e HUGO_REFLINKSERRORLEVEL=ERROR --rm grafana/docs-base:dbd975af06 /bin/bash -c 'echo -e "---\\nredirectURL: /hugo/content/docs/oncall/latest/\\ntype: redirect\\nversioned: true\\n---\\n" > /hugo/content/docs/oncall/_index.md; make hugo' lint-migrations-backend-mysql-rabbitmq: name: "Lint database migrations" diff --git a/.github/workflows/update-make-docs.yml b/.github/workflows/update-make-docs.yml new file mode 100644 index 00000000..57d60d47 --- /dev/null +++ b/.github/workflows/update-make-docs.yml @@ -0,0 +1,27 @@ +name: Update `make docs` procedure +on: + schedule: + - cron: '0 7 * * 1-5' +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Update procedure + if: github.repository != 'grafana/writers-toolkit' + run: | + curl -s -Lo docs/docs.mk https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk + curl -s -Lo docs/make-docs https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs + if git diff --exit-code; then exit 0; fi + BRANCH="$(date +%Y-%m-%d)/update-make-docs" + git checkout -b "${BRANCH}" + git add . + git config --local user.email "bot@grafana.com" + git config --local user.name "grafanabot" + git commit -m "Update \`make docs\` procedure" + git push -v origin "refs/heads/${BRANCH}" + gh pr create --fill + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d86df0f6..6706b246 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -107,5 +107,5 @@ repos: name: markdownlint entry: markdownlint --fix --ignore grafana-plugin/node_modules --ignore grafana-plugin/dist --ignore docs **/*.md - id: markdownlint - name: markdownlint - docs - entry: markdownlint --fix -c ./docs/.markdownlint.json ./docs/**/*.md + name: markdownlint - docs/sources + entry: markdownlint --fix --ignore README.md -c ./docs/.markdownlint.json ./docs/sources/**/*.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b945cff8..4b70a384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## v1.3.11 (2023-07-13) + +### Added + +- Release new webhooks functionality by @mderynck @matiasb @maskin25 @teodosii @raphael-batte ([#1830](https://github.com/grafana/oncall/pull/1830)) + +### Changed + +- Custom button webhooks are deprecated, they will be automatically migrated to new webhooks. ([#1830](https://github.com/grafana/oncall/pull/1830)) + ## v1.3.10 (2023-07-13) ### Added diff --git a/docs/.markdownlint.json b/docs/.markdownlint.json index a51e7c7e..06e4a216 100644 --- a/docs/.markdownlint.json +++ b/docs/.markdownlint.json @@ -4,5 +4,8 @@ "line_length": "160" }, "MD025": false, - "MD036": false + "MD033": false, + "MD036": false, + "MD052": false, + "MD053": false } diff --git a/docs/docs.mk b/docs/docs.mk index 7c707208..4c729c57 100644 --- a/docs/docs.mk +++ b/docs/docs.mk @@ -1,3 +1,5 @@ +# The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk. +# 4.0.0 (2023-06-06) include variables.mk -include variables.mk.local @@ -52,6 +54,11 @@ ifeq ($(origin DOC_VALIDATOR_IMAGE), undefined) export DOC_VALIDATOR_IMAGE := grafana/doc-validator:latest endif +# Container image used for vale linting. +ifeq ($(origin VALE_IMAGE), undefined) +export VALE_IMAGE := grafana/vale:latest +endif + # PATH-like list of directories within which to find projects. # If all projects are checked out into the same directory, ~/repos/ for example, then the default should work. ifeq ($(origin REPOS_PATH), undefined) @@ -91,17 +98,17 @@ docs-no-pull: make-docs .PHONY: docs-debug docs-debug: ## Run Hugo web server with debugging enabled. TODO: support all SERVER_FLAGS defined in website Makefile. docs-debug: make-docs - WEBSITE_EXEC='hugo server --debug' $(PWD)/make-docs $(PROJECTS) + WEBSITE_EXEC='hugo server --bind 0.0.0.0 --port 3002 --debug' $(PWD)/make-docs $(PROJECTS) .PHONY: doc-validator -doc-validator: ## Run docs-validator on the entire docs folder. +doc-validator: ## Run doc-validator on the entire docs folder. doc-validator: make-docs DOCS_IMAGE=$(DOC_VALIDATOR_IMAGE) $(PWD)/make-docs $(PROJECTS) -.PHONY: doc-validator/% -doc-validator/%: ## Run doc-validator on a specific path. To lint the path /docs/sources/administration, run 'make doc-validator/administration'. -doc-validator/%: make-docs - DOCS_IMAGE=$(DOC_VALIDATOR_IMAGE) DOC_VALIDATOR_INCLUDE=$(subst doc-validator/,,$@) $(PWD)/make-docs $(PROJECTS) +.PHONY: vale +vale: ## Run vale on the entire docs folder. +vale: make-docs + DOCS_IMAGE=$(VALE_IMAGE) $(PWD)/make-docs $(PROJECTS) .PHONY: update update: ## Fetch the latest version of this Makefile and the `make-docs` script from Writers' Toolkit. diff --git a/docs/make-docs b/docs/make-docs index b8350109..5090c1c9 100755 --- a/docs/make-docs +++ b/docs/make-docs @@ -1,5 +1,6 @@ #!/bin/sh # The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs. +# 4.1.0 (2023-06-16) set -ef @@ -8,9 +9,11 @@ readonly DOCS_HOST_PORT="${DOCS_HOST_PORT:-3002}" readonly DOCS_IMAGE="${DOCS_IMAGE:-grafana/docs-base:latest}" readonly DOC_VALIDATOR_INCLUDE="${DOC_VALIDATOR_INCLUDE:-.+\.md$}" +readonly DOC_VALIDATOR_SKIP_CHECKS="${DOC_VALIDATOR_SKIP_CHECKS:-^image-}" readonly HUGO_REFLINKSERRORLEVEL="${HUGO_REFLINKSERRORLEVEL:-WARNING}" -readonly WEBSITE_EXEC="${WEBSITE_EXEC:-make server}" +readonly VALE_MINALERTLEVEL="${VALE_MINALERTLEVEL:-error}" +readonly WEBSITE_EXEC="${WEBSITE_EXEC:-make server-docs}" # If set, the docs-base image will run a prebuild script that sets up Hugo mounts. readonly WEBSITE_MOUNTS="${WEBSITE_MOUNTS:-}" @@ -59,23 +62,33 @@ fi SOURCES_as_code='as-code-docs' SOURCES_enterprise_metrics='backend-enterprise' SOURCES_enterprise_metrics_='backend-enterprise' -SOURCES_grafana_cloud='cloud-docs' +SOURCES_grafana_cloud='website' SOURCES_grafana_cloud_k6='k6-docs' +SOURCES_grafana_cloud_data_configuration_integrations='cloud-onboarding' +SOURCES_grafana_cloud_frontend_observability_faro_web_sdk='faro-web-sdk' +SOURCES_grafana_cloud_machine_learning='machine-learning' SOURCES_helm_charts_mimir_distributed='mimir' SOURCES_helm_charts_tempo_distributed='tempo' SOURCES_opentelemetry='opentelemetry-docs' +SOURCES_plugins_grafana_splunk_datasource='splunk-datasource' VERSIONS_as_code='UNVERSIONED' VERSIONS_grafana_cloud='UNVERSIONED' VERSIONS_grafana_cloud_k6='UNVERSIONED' +VERSIONS_grafana_cloud_data_configuration_integrations='UNVERSIONED' +VERSIONS_grafana_cloud_frontend_observability_faro_web_sdk='UNVERSIONED' +VERSIONS_grafana_cloud_machine_learning='UNVERSIONED' VERSIONS_opentelemetry='UNVERSIONED' VERSIONS_technical_documentation='UNVERSIONED' +VERSIONS_website='UNVERSIONED' VERSIONS_writers_toolkit='UNVERSIONED' +PATHS_grafana_cloud='content/docs/grafana-cloud' PATHS_helm_charts_mimir_distributed='docs/sources/helm-charts/mimir-distributed' PATHS_helm_charts_tempo_distributed='docs/sources/helm-charts/tempo-distributed' PATHS_mimir='docs/sources/mimir' PATHS_tempo='docs/sources/tempo' +PATHS_website='content/docs' # identifier STR # Replace characters that are not valid in an identifier with underscores. @@ -138,6 +151,13 @@ proj_url() { $1 POSIX_HERESTRING + if [ "${_project}" = 'website' ]; then + echo "http://localhost:${DOCS_HOST_PORT}/docs/" + + unset _project _version + return + fi + if [ -z "${_version}" ] || [ "${_version}" = 'UNVERSIONED' ]; then echo "http://localhost:${DOCS_HOST_PORT}/docs/${_project}/" else @@ -165,14 +185,43 @@ proj_dst() { $1 POSIX_HERESTRING + if [ "${_project}" = 'website' ]; then + echo '/hugo/content/docs' + + unset _project _version + return + fi + if [ -z "${_version}" ] || [ "${_version}" = 'UNVERSIONED' ]; then echo "/hugo/content/docs/${_project}" else echo "/hugo/content/docs/${_project}/${_version}" fi + unset _project _version } +# repo_path returns the host path to the project repository. +# It looks for the provided repository name in each of the paths specified in the REPOS_PATH environment variable. +repo_path() { + _repo="$1" + IFS=: + for lookup in ${REPOS_PATH}; do + if [ -d "${lookup}/${_repo}" ]; then + echo "${lookup}/${_repo}" + unset _path _repo + return + fi + done + unset IFS + + echo "ERRR: could not find project '${_repo}' in any of the paths in REPOS_PATH '${REPOS_PATH}'." >&2 + echo "NOTE: you must have a checkout of the project '${_repo}' at '${REPOS_PATH##:*}/${_repo}'." >&2 + echo "NOTE: if you have cloned the repository into a directory with a different name, consider changing it to ${_repo}." >&2 + unset _repo + exit 1 +} + # proj_src returns the host path to content source for a project. # It expects a complete project structure as input. # It looks for the provided repository name in each of the paths specified in the REPOS_PATH environment variable. @@ -181,21 +230,10 @@ proj_src() { $1 POSIX_HERESTRING - IFS=: - for lookup in ${REPOS_PATH}; do - if [ -d "${lookup}/${_repo}" ]; then - echo "${lookup}/${_repo}/${_path}" - unset _path _repo - return - fi - done - unset IFS + _repo="$(repo_path "${_repo}")" + echo "${_repo}/${_path}" - echo "ERRR: could not find project '${_repo}' in any of the paths in REPOS_PATH '${REPOS_PATH}'." >&2 - echo "NOTE: you must have a checkout of the project '${_repo}' at '${REPOS_PATH##:*}/${_repo}'." >&2 - echo "NOTE: if you have cloned the repository into a directory with a different name, consider changing it to ${_repo}." >&2 unset _path _repo - exit 1 } # proj_canonical returns the canonical absolute path partial URI for a project. @@ -205,11 +243,19 @@ proj_canonical() { $1 POSIX_HERESTRING + if [ "${_project}" = 'website' ]; then + echo '/docs' + + unset _project _version + return + fi + if [ -z "${_version}" ] || [ "${_version}" = 'UNVERSIONED' ]; then echo "/docs/${_project}" else echo "/docs/${_project}/${_version}" fi + unset _project _version } @@ -262,6 +308,19 @@ url_src_dst_vers="$(url_src_dst_vers "$@")" volumes="" redirects="" +for arg in "$@"; do + IFS=: read -r _project _ _repo _ </tmp/make-docs-entrypoint +case "${image}" in + 'grafana/doc-validator') + proj="$(new_proj "$1")" + echo + "${PODMAN}" run \ + --init \ + --interactive \ + --name "${DOCS_CONTAINER}" \ + --platform linux/amd64 \ + --rm \ + --tty \ + ${volumes} \ + "${DOCS_IMAGE}" \ + "--include=${DOC_VALIDATOR_INCLUDE}" \ + "--skip-checks=${DOC_VALIDATOR_SKIP_CHECKS}" \ + /hugo/content/docs \ + "$(proj_canonical "${proj}")" | sed "s#$(proj_dst "${proj}")#sources#" + ;; + 'grafana/vale') + proj="$(new_proj "$1")" + echo + "${PODMAN}" run \ + --init \ + --interactive \ + --name "${DOCS_CONTAINER}" \ + --platform linux/amd64 \ + --rm \ + --tty \ + ${volumes} \ + "${DOCS_IMAGE}" \ + "--minAlertLevel=${VALE_MINALERTLEVEL}" \ + --config=/etc/vale/.vale.ini \ + --output=line \ + /hugo/content/docs | sed "s#$(proj_dst "${proj}")#sources#" + ;; + *) + tempfile="$(mktemp -t make-docs.XXX)" + cat <"${tempfile}" #!/usr/bin/env bash for redirect in ${redirects}; do IFS='^' read -r path ver <<<"\${redirect}" - echo -e "---\\nredirectURL: \"\${path/\/hugo\/content/}\"\\ntype: redirect\\n---\\n" > "\${path/\${ver}/_index.md}" - - if [[ -n "${WEBSITE_MOUNTS}" ]]; then - unset WEBSITE_SKIP_MOUNTS - fi + echo -e "---\\nredirectURL: \"\${path/\/hugo\/content/}\"\\ntype: redirect\\nversioned: true\\n---\\n" > "\${path/\${ver}/_index.md}" done +for x in "${url_src_dst_vers}"; do + IFS='^' read -r _ _ dst _ <<<"\${x}" + + while [[ -n "\${dst}" ]]; do + touch "\${dst}/_index.md" + dst="\${dst%/*}" + done +done + +if [[ -n "${WEBSITE_MOUNTS}" ]]; then + unset WEBSITE_SKIP_MOUNTS +fi + ${WEBSITE_EXEC} EOF - chmod +x /tmp/make-docs-entrypoint - volumes="${volumes} --volume=/tmp/make-docs-entrypoint:/entrypoint" - readonly volumes + chmod +x "${tempfile}" + volumes="${volumes} --volume=$(realpath "${tempfile}"):/entrypoint" + readonly volumes - echo - echo "Documentation will be served at the following URLs:" - for x in ${url_src_dst_vers}; do - IFS='^' read -r url _ _ <}}) +group. A route's ["Routing Templates"][routing-template] are evaluated for each alert and **the first matching route** is used to determine the escalation chain and chatops channels. @@ -27,7 +27,7 @@ escalation chain and chatops channels. 2. Click **Add route** button to create a new route 3. Click **Edit** button to edit `Routing Template`. The routing template must evaluate to `True` for it to apply 4. Select channels in **Publish to Chatops** section - > **Note:** If **Publish to Chatops** section does not exist, connect Chatops integrations first, see more in [docs]({{< relref notify >}}) + > **Note:** If **Publish to Chatops** section does not exist, connect Chatops integrations first, see more in [docs][notify] 5. Select **Escalation Chain** from the list 6. If **Escalation Chain** does not exist, click **Add new escalation chain** button to create a new one, it will open in a new tab. 7. Once created, **Reload list**, and select the new escalation chain @@ -45,7 +45,7 @@ Users can create escalation chains to configure different type of escalation wor For example, you can create a chain that will notify on-call users with high priopity, and another chain that will only send a message into a Slack channel. -Escalation chains determine Who and When to notify. [How to notify]({{< relref notify >}}) is set by the user, based on their own preferences. +Escalation chains determine Who and When to notify. [How to notify][notify] is set by the user, based on their own preferences. ### Types of escalation steps @@ -59,14 +59,9 @@ from an on-call schedule. * `Notify whole slack channel` - send a notification to a slack channel (not recommended to use as it will spam the channel). * `Notify Slack User Group` - send a notification to a slack user group. -* `Trigger outgoing webhook` - trigger an [outgoing webhook]({{< relref outgoing-webhooks - ->}}). - - +* `Trigger outgoing webhook` - trigger an [outgoing webhook][outgoing-webhooks]. * `Notify users one by one (round robin)` - each notification will be sent to a group of -users one by one, in sequential order in [round robin fashion]( -wiki/Round-robin_item_allocation). +users one by one, in sequential order in [round robin fashion](https://en.wikipedia.org/wiki/Round-robin_item_allocation). * `Continue escalation if current time is in range` - continue escalation only if current time is in specified range. It will wait for the specfied time to continue escalation. Useful when you want to get escalation only during working hours @@ -87,7 +82,7 @@ User can configure two types of personal notification chains: In the escalation step, user can select which type of notification to use. -Check more information on [Personal Notification Preferences]({{< relref notify >}}) page. +Check more information on [Personal Notification Preferences][notify] page. ### Manage Escalation Chains @@ -102,3 +97,14 @@ Check more information on [Personal Notification Preferences]({{< relref notify > **Important:** Linked Integrations and Routes are displayed in the right panel. Any change in the Escalation Chain will affect all linked Integrations and Routes. + +{{% docs/reference %}} +[notify]: "/docs/oncall/ -> /docs/oncall//notify" +[notify]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/notify" + +[outgoing-webhooks]: "/docs/oncall/ -> /docs/oncall//outgoing-webhooks" +[outgoing-webhooks]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/outgoing-webhooks" + +[routing-template]: "/docs/oncall/ -> /docs/oncall//jinja2-templating#routing-template" +[routing-template]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating#routing-template" +{{% /docs/reference %}} diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 61bea54d..d7e38cbc 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -30,21 +30,21 @@ Grafana OnCall is available both in Grafana Cloud and Grafana Open Source. OnCall is available in Grafana Cloud automatically: -1. Create or log in into [Grafana Cloud account](https://grafana.com/auth/sign-up/create-user) +1. Create or log in into [Grafana Cloud account](/auth/sign-up/create-user) 2. Sign in to your Grafana stack 3. Choose **Alerts and IRM** from the left menu 4. Click **OnCall** to access Grafana OnCall -Otherwise you'll need to install [Open Source Grafana OnCall]({{< relref "../open-source" >}}) on your own. +Otherwise you'll need to install [Open Source Grafana OnCall][open-source] on your own. ## How to configure Grafana OnCall -* Users with [Admin role]({{< relref "user-and-team-management" >}}) can configure Alert rules (Integrations, Routes, etc) +* Users with [Admin role][user-and-team-management] can configure Alert rules (Integrations, Routes, etc) to define **when and which users to notify** -* OnCall users with [Editor role]({{< relref "user-and-team-management" >}}) can work with Alerts Groups and set up personal settings, +* OnCall users with [Editor role][user-and-team-management] can work with Alerts Groups and set up personal settings, e.g. **how to notify**. -> **Note:** If your role is **Editor**, you can skip to [**Learn Alert Workflow**]({{< relref "#learn-about-the-alert-workflow" >}}) section +> **Note:** If your role is **Editor**, you can skip to [**Learn Alert Workflow**](#learn-about-the-alert-workflow) section of this doc ## Get alerts into Grafana OnCall and configure rules @@ -73,19 +73,19 @@ send a demo alert. 4. Acknowledge and resolve the test alert group For more information on Grafana OnCall integrations and further configuration guidance, refer to -[Grafana OnCall integrations]({{< relref "../integrations" >}}) +[Grafana OnCall integrations][integrations] ### Review and modify alert templates Review and customize templates to interpret monitoring alerts and minimize noise. Group alerts, enable auto-resolution, customize visualizations and notifications by extracting data from alerts. See more details in the -[Jinja2 templating]({{< relref "../jinja2-templating" >}}) section. +[Jinja2 templating][jinja2-templating] section. ### Configure scalation Chains Escalation chains are a set of steps that define who to notify, and when. -See more details in the [Escalation Chains]({{< relref "../escalation-chains-and-routes#escalation-chains" >}}) section. +See more details in the [Escalation Chains][escalation-chains] section. Escalation Chains are customizable automated alert routing steps that enable you to specify who is notified for a certain alert. In addition to escalation chains, you can configure Routes to send alerts to different escalation chains depending @@ -106,14 +106,14 @@ Alerts from this integration will now follow the escalation steps configured in For more information on Escalation Chains and more ways to customize them, refer to -[Configure and manage Escalation Chains]({{< relref "escalation-chains-and-routes" >}}) +[Configure and manage Escalation Chains][escalation-chains-and-routes] Routes define which messenger channels and escalation chains to use for notifications. See more details in -the [Routes]({{< relref "../escalation-chains-and-routes#routes" >}}) section. +the [Routes][routes] section. ### Learn about the Alert Workflow -* All Alerts in OnCall are grouped into Alert Groups ([read more about Grouping ID]({{< relref "../jinja2-templating" >}})). +* All Alerts in OnCall are grouped into Alert Groups ([read more about Grouping ID][jinja2-templating]). An Alert Group can have the following, mutually exclusive states: * **Firing:** Once Alert Group is registered, Escalation Policy associated with it is getting started. Escalation policy will work while Alert Group is in this status. @@ -150,7 +150,7 @@ Personal notification policies determine how a user is notified for a certain ty phone call, Slack mentions, or mobile push notification. Administrators can configure how users receive notifications for certain types of alerts. For more information on personal notification policies, refer to -[Manage users and teams for Grafana OnCall]({{< relref "user-and-team-management" >}}) +[Manage users and teams for Grafana OnCall][user-and-team-management] To configure users personal notification policies: @@ -174,9 +174,9 @@ To configure Slack for Grafana OnCall: 6. Ensure users verify their Slack accounts in their user profile in Grafana OnCall. For further instruction on connecting to your Slack workspace, refer to -[Slack integration for Grafana OnCall]({{< relref "../notify/slack/" >}}) +[Slack integration for Grafana OnCall][slack] -Grafana OnCall also supports other ChatOps integration like [Microsoft Teams and Telegram]({{< relref "../notify" >}}). +Grafana OnCall also supports other ChatOps integration like [Microsoft Teams and Telegram][notify]. ### Add your on-call schedule @@ -189,4 +189,36 @@ To integrate your on-call calendar with Grafana OnCall: 2. Provide a schedule name. 3. Configure the rest of the schedule settings and click Create Schedule -[More information on on-call schedules.]({{< relref "on-call-schedules" >}}) +[More information on on-call schedules.][on-call-schedules] + +{{% docs/reference %}} +[escalation-chains-and-routes]: "/docs/oncall/ -> /docs/oncall//escalation-chains-and-routes" +[escalation-chains-and-routes]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/escalation-chains-and-routes" + +[escalation-chains]: "/docs/oncall/ -> /docs/oncall//escalation-chains-and-routes#escalation-chains" +[escalation-chains]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/escalation-chains-and-routes#escalation-chains" + +[integrations]: "/docs/oncall/ -> /docs/oncall//integrations" +[integrations]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/integrations" + +[jinja2-templating]: "/docs/oncall/ -> /docs/oncall//jinja2-templating" +[jinja2-templating]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating" + +[notify]: "/docs/oncall/ -> /docs/oncall//notify" +[notify]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/notify" + +[on-call-schedules]: "/docs/oncall/ -> /docs/oncall//on-call-schedules" +[on-call-schedules]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/on-call-schedules" + +[open-source]: "/docs/oncall/ -> /docs/oncall//open-source" +[open-source]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/open-source" + +[routes]: "/docs/oncall/ -> /docs/oncall//escalation-chains-and-routes#routes" +[routes]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/escalation-chains-and-routes#routes" + +[slack]: "/docs/oncall/ -> /docs/oncall//notify/slack" +[slack]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/notify/slack" + +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/insights-and-metrics/_index.md b/docs/sources/insights-and-metrics/_index.md index c9fbe9dd..5f606306 100644 --- a/docs/sources/insights-and-metrics/_index.md +++ b/docs/sources/insights-and-metrics/_index.md @@ -246,7 +246,7 @@ Resource IDs are used a lot in insight logs. You can find them in web ui (exampl 3. The URL looks like `https:///a/grafana-oncall-app/integrations/C5VXMIFKKP67K`. 4. Integration ID is `C5VXMIFKKP67K`. -Alternatively you can find resource ID using public [API](https://grafana.com/docs/oncall/latest/oncall-api-reference/) or browser dev tools. +Alternatively you can find resource ID using public [API][oncall-api-reference] or browser dev tools. Actions performed by user: @@ -277,3 +277,8 @@ Actions performed with slack chatops integration: ```logql {instance_type="oncall"} | logfmt | __error__=`` | action_type = `chat_ops` and chat_ops_type=`slack` ``` + +{{% docs/reference %}} +[oncall-api-reference]: "/docs/oncall/ -> /docs/oncall//oncall-api-reference" +[oncall-api-reference]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/oncall-api-reference" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/_index.md b/docs/sources/integrations/_index.md index 5f82637d..82a28171 100644 --- a/docs/sources/integrations/_index.md +++ b/docs/sources/integrations/_index.md @@ -18,20 +18,20 @@ An "Integration" is a main entry point for alerts being consumed by Grafana OnCa Integrations receive alerts on a unique API URL, interprets them using a set of templates tailored for the monitoring system, and starts escalations. -Read more about Jinja2 templating used in OnCall [here]({{< relref "../jinja2-templating" >}}). +Read more about Jinja2 templating used in OnCall [here][jinja2-templating]. ## Learn Alert Flow Within Integration 1. An Alert is received on an integration's **Unique URL** as an HTTP POST request with a JSON payload (or via [e-mail]({{< relref "inbound-email" >}}), for inbound e-mail integrations) -1. Routing is determined for the incoming alert, by applying the [Routing Template]({{< relref "jinja2-templating#routing-template" >}}) -1. Alert Grouping is determined based on [Grouping Id Template]({{< relref "jinja2-templating#behavioral-template" >}}) +1. Routing is determined for the incoming alert, by applying the [Routing Template][routing-template] +1. Alert Grouping is determined based on [Grouping Id Template][behavioral-template] 1. An Alert Group may be acknowledged or resolved with status `_ by source` based on -[Behaviour Templates]({{< relref "jinja2-templating#behavioral-template" >}}) +[Behaviour Templates][behavioral-template] 1. The Alert Group is available in Web, and can be published to messengers, based on the Route's **Publish to Chatops** configuration. -It is rendered using [Appearance Templates]({{< relref "jinja2-templating#appearance-template" >}}) +It is rendered using [Appearance Templates][appearance-template] 1. The Alert Group is escalated to uers based on the Escalation Chains selected for the Route -1. Users can perform actions listed in [Learn Alert Workflow]({{< relref "get-started#learn-alert-workflow" >}}) section +1. Users can perform actions listed in [Learn Alert Workflow][learn-alert-workflow] section ## Configure and manage integrations @@ -43,7 +43,7 @@ describe how to configure and customize your integrations to ensure alerts are t To configure an integration for Grafana OnCall: 1. In Grafana OnCall, navigate to the **Integrations** tab and click **+ New integration**. -1. Select an integration type from the [list of available integrations]({{< relref "#list-of-available-integrations" >}}). +1. Select an integration type from the [list of available integrations](#list-of-available-integrations). If the integration you want isn’t listed, then select **Webhook**. 1. Fill in a title and a description for your integration, assign it to a team, and click **Create Integration**. 1. The Integration page will open. Here you will see details about the Integration. @@ -54,13 +54,13 @@ Click the **How to connect** link for more information. ### Complete the integration configuration -- Review and customise grouping, autoresolution, autoacknowledge, etc [templates]({{< relref "../jinja2-templating" >}}) +- Review and customise grouping, autoresolution, autoacknowledge, etc [templates][jinja2-templating] if you want to customise alert behaviour for your team -- Review and customise [other templates]({{< relref "../jinja2-templating" >}}) to change how alert groups are displayed +- Review and customise [other templates][jinja2-templating] to change how alert groups are displayed in different parts of Grafana OnCall: UI, messengers, emails, notifications, etc. - Add routes to your integration to route alerts to different users and teams based on labels or other data - Connect your escalation chains to routes to notify the right people, at the right time -- Learn [how to start Maintenance Mode]({{< relref "#maintenance-mode" >}}) for an integration +- Learn [how to start Maintenance Mode](#maintenance-mode) for an integration - Send demo alerts to an integration to make sure routes, templates, and escalations, are working as expected. Consider using `Debug Maintenance mode` to avoid sending real notifications to your team @@ -103,7 +103,7 @@ More specific instructions can be found in a specific integration's documentatio "Integration templates" are Jinja2 templates which are applied to each alert to define it's rendering and behaviour. -Read more in [Templates guide]({{< relref jinja2-templating>}}) +Read more in [Templates guide][jinja2-templating] For templates editor: @@ -143,3 +143,20 @@ To edit the name of an integration: ## List of available integrations {{< section >}} + +{{% docs/reference %}} +[appearance-template]: "/docs/oncall/ -> /docs/oncall//jinja2-templating#appearance-template" +[appearance-template]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating#appearance-template" + +[behavioral-template]: "/docs/oncall/ -> /docs/oncall//jinja2-templating#behavioral-template" +[behavioral-template]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating#behavioral-template" + +[jinja2-templating]: "/docs/oncall/ -> /docs/oncall//jinja2-templating" +[jinja2-templating]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating" + +[learn-alert-workflow]: "/docs/oncall/ -> /docs/oncall//get-started#learn-alert-workflow" +[learn-alert-workflow]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/get-started#learn-alert-workflow" + +[routing-template]: "/docs/oncall/ -> /docs/oncall//jinja2-templating#routing-template" +[routing-template]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating#routing-template" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/alertmanager/index.md b/docs/sources/integrations/alertmanager/index.md index 051f66f4..b349b3f5 100644 --- a/docs/sources/integrations/alertmanager/index.md +++ b/docs/sources/integrations/alertmanager/index.md @@ -15,7 +15,7 @@ weight: 300 # Alertmanager integration for Grafana OnCall -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. The Alertmanager integration handles alerts from [Prometheus Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/). This integration is the recommended way to send alerts from Prometheus deployed in your infrastructure, to Grafana OnCall. @@ -59,7 +59,7 @@ receivers: ## Complete the Integration Configuration Complete configuration by setting routes, templates, maintenances, etc. Read more in -[this section]({{< relref "../../integrations/#complete-the-integration-configuration" >}}) +[this section][complete-the-integration-configuration] ## Configuring OnCall Heartbeats (optional) @@ -111,5 +111,12 @@ Add receiver configuration to `prometheus.yaml` with the **OnCall Heartbeat URL* webhook_configs: - url: https://oncall-dev-us-central-0.grafana.net/oncall/integrations/v1/alertmanager/1234567890/heartbeat/ send_resolved: false - - ``` +``` + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" + +[complete-the-integration-configuration]: "/docs/oncall/ -> /docs/oncall//integrations#complete-the-integration-configuration" +[complete-the-integration-configuration]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/integrations#complete-the-integration-configuration" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/amazon-sns/index.md b/docs/sources/integrations/amazon-sns/index.md index 0157b7f2..ff6d810d 100644 --- a/docs/sources/integrations/amazon-sns/index.md +++ b/docs/sources/integrations/amazon-sns/index.md @@ -18,7 +18,7 @@ weight: 500 The Amazon SNS integration for Grafana OnCall handles ticket events sent from Amazon SNS webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Amazon SNS @@ -35,3 +35,8 @@ in Grafana OnCall. 2. Open this topic, then create a new subscription 3. Choose the protocol HTTPS 4. Add the **OnCall Integration URL** to the Amazon SNS Endpoint + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/appdynamics/index.md b/docs/sources/integrations/appdynamics/index.md index 54c76515..596fd9ed 100644 --- a/docs/sources/integrations/appdynamics/index.md +++ b/docs/sources/integrations/appdynamics/index.md @@ -18,7 +18,7 @@ weight: 500 The AppDynamics integration for Grafana OnCall handles health rule violation events sent from AppDynamics actions. The integration provides grouping and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from AppDynamics @@ -105,4 +105,12 @@ Grafana OnCall provides grouping and auto-resolve logic for the AppDynamics inte ## Complete the Integration Configuration Complete configuration by setting routes, templates, maintenances, etc. Read more in -[this section]({{< relref "../../integrations/#complete-the-integration-configuration" >}}) +[this section][complete-the-integration-configuration] + +{{% docs/reference %}} +[complete-the-integration-configuration]: "/docs/oncall/ -> /docs/oncall//integrations#complete-the-integration-configuration" +[complete-the-integration-configuration]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/integrations#complete-the-integration-configuration" + +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/datadog/index.md b/docs/sources/integrations/datadog/index.md index 74d69fca..59d8de25 100644 --- a/docs/sources/integrations/datadog/index.md +++ b/docs/sources/integrations/datadog/index.md @@ -18,7 +18,7 @@ weight: 500 The Datadog integration for Grafana OnCall handles ticket events sent from Datadog webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Datadog @@ -36,3 +36,8 @@ The integration provides grouping, auto-acknowledge and auto-resolve logic via c 5. Navigate to the Events page from the sidebar to send the test alert 6. Type @webhook-grafana-oncall-alerts test alert 7. Click the post button + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/elastalert/index.md b/docs/sources/integrations/elastalert/index.md index ba896b86..d8cc7219 100644 --- a/docs/sources/integrations/elastalert/index.md +++ b/docs/sources/integrations/elastalert/index.md @@ -18,7 +18,7 @@ weight: 500 The ElastAlert integration for Grafana OnCall handles ticket events sent from ElastAlert webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from ElastAlert @@ -74,3 +74,8 @@ Add the following rule to ElastAlert alert_text: elastalert is still running alert_text_type: alert_text_only ``` + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/fabric/index.md b/docs/sources/integrations/fabric/index.md index 48af068d..739e9fd2 100644 --- a/docs/sources/integrations/fabric/index.md +++ b/docs/sources/integrations/fabric/index.md @@ -18,7 +18,7 @@ weight: 500 The Fabric integration for Grafana OnCall handles ticket events sent from Fabric webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Fabric @@ -35,3 +35,8 @@ The integration provides grouping, auto-acknowledge and auto-resolve logic via c 4. Enter URL: **OnCall Integration URL** 5. Click Verify 6. Choose "SEND IMPACT CHANGE ALERTS" and "ALSO SEND NON-FATAL ALERTS" + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/inbound-email/index.md b/docs/sources/integrations/inbound-email/index.md index 4ed40e9d..24cd06be 100644 --- a/docs/sources/integrations/inbound-email/index.md +++ b/docs/sources/integrations/inbound-email/index.md @@ -27,7 +27,7 @@ You must have an Admin role to create integrations in Grafana OnCall. ## Grouping and auto-resolve Alert groups will be grouped by email subject and auto-resolved if the email message text equals "OK". - This behaviour can be modified via [custom templates]({{< relref "jinja2-templating" >}}). + This behaviour can be modified via [custom templates][jinja2-templating]. Alerts from Inbound Email integration have the following payload: @@ -38,3 +38,8 @@ Alerts from Inbound Email integration have the following payload: "sender": "" } ``` + +{{% docs/reference %}} +[jinja2-templating]: "/docs/oncall/ -> /docs/oncall//jinja2-templating" +[jinja2-templating]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/jira/index.md b/docs/sources/integrations/jira/index.md index ae46875d..3c7b4c70 100644 --- a/docs/sources/integrations/jira/index.md +++ b/docs/sources/integrations/jira/index.md @@ -8,6 +8,7 @@ keywords: - Alerts - Notifications - on-call + - webhooks - Jira title: Jira weight: 500 @@ -18,7 +19,7 @@ weight: 500 The Jira integration for Grafana OnCall handles issue events sent from Jira webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Jira @@ -49,3 +50,128 @@ Grafana OnCall provides grouping, auto-acknowledge and auto-resolve logic for th - Alert groups are auto-resolved when the issue is closed or deleted To customize this behaviour, consider modifying alert templates in integration settings. + +## Configuring Grafana OnCall to send data to Jira + +Grafana OnCall can automatically create and resolve issues in Jira via [outgoing webhooks]({{< relref "_index.md" >}}). +This guide provides example webhook configurations for common use cases, as well as information on how to set up a user in Jira to be used by Grafana OnCall. + +### Prerequisites + +1. Create a new user in Jira to be used by Grafana OnCall. [Obtain an API token for the user](https://id.atlassian.com/manage-profile/security/api-tokens), +these credentials will be used to communicate with Jira REST API. +2. Make sure the user has appropriate permissions to create and update issues in Jira. + +### Create issues in Jira + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically create +issues in Jira from Grafana OnCall alert groups. + +Create a new Outgoing Webhook in Grafana OnCall, and configure it as follows: + +- Trigger type: `Alert Group Created` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `POST` + +- Webhook URL: + +```text +https://.atlassian.net/rest/api/2/issue +``` + +Replace `` with your Jira instance. + +- Username: Email of the [Jira user](#prerequisites) + +- Password: API token of the [Jira user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "fields": { + "project": { + "key": "" + }, + "issuetype": { + "name": "[System] Incident" + }, + "summary": "{{alert_group.title}}", + "description": "This issue is created automatically by Grafana OnCall. Alert group {{alert_group.id}}: {{alert_group.permalinks.web}}" + } +} +``` + +Replace `` with the key of the project in Jira. + +>**Note**: You might want to use a different `issuetype.name` depending on your Jira instance configuration and use case. + +### Resolve issues in Jira + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically resolve +issues in Jira when an alert group is resolved in Grafana OnCall. + +- Trigger type: `Resolved` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `POST` + +- Webhook URL: + +```text +https://.atlassian.net/rest/api/2/issue/{{responses..key}}/transitions +``` + +Replace `` with your Jira instance, and `` with the ID of the [webhook used for creating issues](#create-issues-in-jira). + +- Username: Email of the [Jira user](#prerequisites) + +- Password: API token of the [Jira user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "transition": { + "id": "" + }, + "fields": { + "resolution": { + "name": "Done" + } + }, + "update": { + "comment": [ + { + "add": { + "body": "Resolved by Grafana OnCall.", + "public": false + } + } + ] + } +} +``` + +Replace `` with the ID of the transition specific to your Jira instance. +See [here](https://community.atlassian.com/t5/Jira-questions/How-to-fine-transition-ID-of-JIRA/qaq-p/1207483#M385834) +for more info on how to find the transition ID in Jira UI, or use the +[REST API endpoint](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-transitions-get) +to get the list of available transitions. + +### Advanced usage + +The examples above describe how to create outgoing webhooks in Grafana OnCall that will allow to automatically create and resolve issues in Jira. + +Consider modifying example templates to fit your use case (e.g. to include more information on alert groups). +Refer to [outgoing webhooks documentation]({{< relref "_index.md" >}}) for more information on available template variables and webhook configuration. + +For more information on Jira REST API, refer to [Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues). + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/kapacitor/index.md b/docs/sources/integrations/kapacitor/index.md index e5108cf6..85550191 100644 --- a/docs/sources/integrations/kapacitor/index.md +++ b/docs/sources/integrations/kapacitor/index.md @@ -18,7 +18,7 @@ weight: 500 The Kapacitor integration for Grafana OnCall handles ticket events sent from Kapacitor webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Kapacitor @@ -59,3 +59,8 @@ To send an alert from Kapacitor, you can follow these steps: When the condition defined in the TICKscript is met, Kapacitor will trigger the alert and send a POST request to the specified webhook URL with the necessary information. Make sure your webhook endpoint is configured to receive and process the incoming alerts from Kapacitor. + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/newrelic/index.md b/docs/sources/integrations/newrelic/index.md index 40899b81..319823e5 100644 --- a/docs/sources/integrations/newrelic/index.md +++ b/docs/sources/integrations/newrelic/index.md @@ -18,7 +18,7 @@ weight: 500 The New Relic integration for Grafana OnCall handles ticket events sent from New Relic webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from New Relic @@ -34,3 +34,8 @@ The integration provides grouping, auto-acknowledge and auto-resolve logic via c 3. Create "Webhook" notification channel. 4. Set the following URL: **OnCall Integration URL** 5. Check "Payload type" is JSON. + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/pingdom/index.md b/docs/sources/integrations/pingdom/index.md index a90828fe..2b658427 100644 --- a/docs/sources/integrations/pingdom/index.md +++ b/docs/sources/integrations/pingdom/index.md @@ -18,7 +18,7 @@ weight: 500 The Pingdom integration for Grafana OnCall handles ticket events sent from Pingdom webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Pingdom @@ -35,3 +35,8 @@ The integration provides grouping, auto-acknowledge and auto-resolve logic via c 4. Go to "Reports" -> "Uptime" -> "Edit Check". 5. Select `Grafana OnCall` integration in the bottom. 6. Click "Modify Check" to save. + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/prtg/index.md b/docs/sources/integrations/prtg/index.md index 26bf0a93..f16585b1 100644 --- a/docs/sources/integrations/prtg/index.md +++ b/docs/sources/integrations/prtg/index.md @@ -18,7 +18,7 @@ weight: 500 The PRTG integration for Grafana OnCall handles ticket events sent from PRTG webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from PRTG @@ -111,3 +111,8 @@ Catch } ``` + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/sentry/index.md b/docs/sources/integrations/sentry/index.md index f370f93c..5d404a20 100644 --- a/docs/sources/integrations/sentry/index.md +++ b/docs/sources/integrations/sentry/index.md @@ -18,7 +18,7 @@ weight: 500 The Sentry integration for Grafana OnCall handles ticket events sent from Sentry webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Sentry @@ -54,3 +54,8 @@ level, event frequency, or specific tags. 8. Save the alert rule. Once the alert conditions are met, Sentry will trigger the webhook action and send a request to the Grafana OnCall. + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/servicenow/index.md b/docs/sources/integrations/servicenow/index.md new file mode 100644 index 00000000..4f839efb --- /dev/null +++ b/docs/sources/integrations/servicenow/index.md @@ -0,0 +1,140 @@ +--- +aliases: + - servicenow/ + - /docs/oncall/latest/integrations/available-integrations/configure-servicenow/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/configure-servicenow/ +keywords: + - Grafana Cloud + - Alerts + - Notifications + - on-call + - webhooks + - ServiceNow +title: ServiceNow +weight: 500 +--- + +# Integrate Grafana OnCall with ServiceNow + +Grafana OnCall can automatically create, assign and resolve incidents in ServiceNow via [outgoing webhooks]({{< relref "_index.md" >}}). +This guide provides example webhook configurations for common use cases, as well as information on how to set up a user in ServiceNow to be used by Grafana OnCall. + +## Prerequisites + +1. Create a new user in ServiceNow to be used by Grafana OnCall. Obtain the username and password for the user, +these credentials will be used to communicate with ServiceNow REST API. +2. Make sure the user has appropriate permissions to create and update incidents in ServiceNow. By default, the user will need to have the `sn_incident_write` role. + +## Create incidents in ServiceNow + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically create +incidents in ServiceNow from Grafana OnCall alert groups. + +Create a new Outgoing Webhook in Grafana OnCall, and configure it as follows: + +- Trigger type: `Alert Group Created` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `POST` + +- Webhook URL: + +```text +https://.service-now.com/api/now/table/incident +``` + +Replace `` with your ServiceNow instance. + +- Username: Username of the [ServiceNow user](#prerequisites) + +- Password: Password of the [ServiceNow user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "short_description": "{{alert_group.title}}", + "description": "This incident is created automatically by Grafana OnCall.", + "work_notes": "Grafana OnCall alert group: [code]{{alert_group.id}}[/code]", + "category": "Software" +} +``` + +## Assign incidents in ServiceNow + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically assign incidents in ServiceNow. +The assignment will be performed when an alert group is acknowledged in Grafana OnCall. + +- Trigger type: `Acknowledged` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `PUT` + +- Webhook URL: + +```text +https://.service-now.com/api/now/table/incident/{{responses..result.sys_id}} +``` + +Replace `` with your ServiceNow instance, and `` with the ID of the [webhook used for creating incidents](#create-incidents-in-servicenow). + +- Username: Username of the [ServiceNow user](#prerequisites) + +- Password: Password of the [ServiceNow user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "assigned_to": "{{user.email}}" +} +``` + +>**Note**: The incident will be assigned to the user that acknowledged the alert group in Grafana OnCall. +The assignment will fail if the user email does not exist in ServiceNow. + +## Resolve incidents in ServiceNow + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically close +incidents in ServiceNow when an alert group is resolved in Grafana OnCall. + +- Trigger type: `Resolved` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `PUT` + +- Webhook URL: + +```text +https://.service-now.com/api/now/table/incident/{{responses..result.sys_id}} +``` + +Replace `` with your ServiceNow instance, and `` with the ID of the [webhook used for creating incidents](#create-incidents-in-servicenow). + +- Username: Username of the [ServiceNow user](#prerequisites) + +- Password: Password of the [ServiceNow user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "state": 6, + "close_code": "Resolved by caller", + "close_notes": "Resolved by Grafana OnCall." +} +``` + +>**Note**: Values for fields `state` and `close_code` may be different for your ServiceNow instance, please check and update the values accordingly. + +## Advanced usage + +The examples above describe how to create outgoing webhooks in Grafana OnCall that will allow to automatically create, assign and resolve incidents in ServiceNow. + +Consider modifying example templates to fit your use case (e.g. to include more information on alert groups). +Refer to [outgoing webhooks documentation]({{< relref "_index.md" >}}) for more information on available template variables and webhook configuration. + +For more information on ServiceNow REST API, refer to [ServiceNow REST API documentation](https://developer.servicenow.com/dev.do#!/reference/api/sandiego/rest). diff --git a/docs/sources/integrations/stackdriver/index.md b/docs/sources/integrations/stackdriver/index.md index 1d9dc042..19f5c659 100644 --- a/docs/sources/integrations/stackdriver/index.md +++ b/docs/sources/integrations/stackdriver/index.md @@ -18,7 +18,7 @@ weight: 500 The Stackdriver integration for Grafana OnCall handles ticket events sent from Stackdriver webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Stackdriver @@ -32,3 +32,8 @@ The integration provides grouping, auto-acknowledge and auto-resolve logic via c 1. Create a notification channel in Stackdriver by navigating to Workspace Settings -> WEBHOOKS -> Add Webhook **OnCall Integration URL** 2. Create and alert in Stackdriver by navigating to Alerting -> Policies -> Add Policy -> Choose Notification Channel using the channel set up in step 1 + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/uptimerobot/index.md b/docs/sources/integrations/uptimerobot/index.md index dce8f84b..e8f01f1a 100644 --- a/docs/sources/integrations/uptimerobot/index.md +++ b/docs/sources/integrations/uptimerobot/index.md @@ -18,7 +18,7 @@ weight: 500 The UptimeRobot integration for Grafana OnCall handles ticket events sent from UptimeRobot webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from UptimeRobot @@ -59,3 +59,8 @@ POST Value (JSON Format): 1. Set URL to or any other non-existent domain 1. Click Checkbox next to Amixr Alert Contact (created in the previous step) 1. Click Create Monitor + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/webhook/index.md b/docs/sources/integrations/webhook/index.md index 66c03496..1a559f2d 100644 --- a/docs/sources/integrations/webhook/index.md +++ b/docs/sources/integrations/webhook/index.md @@ -60,4 +60,9 @@ https://a-prod-us-central-0.grafana.net/integrations/v1/formatted_webhook/m12xmI ``` To learn how to use custom alert templates for formatted webhooks, see -[Configure alerts templates]({{< relref "jinja2-templating" >}}). +[Configure alerts templates][jinja2-templating]. + +{{% docs/reference %}} +[jinja2-templating]: "/docs/oncall/ -> /docs/oncall//jinja2-templating" +[jinja2-templating]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/jinja2-templating" +{{% /docs/reference %}} diff --git a/docs/sources/integrations/zendesk/index.md b/docs/sources/integrations/zendesk/index.md index 3ba369d8..708b0aee 100644 --- a/docs/sources/integrations/zendesk/index.md +++ b/docs/sources/integrations/zendesk/index.md @@ -8,6 +8,7 @@ keywords: - Alerts - Notifications - on-call + - webhooks - Zendesk title: Zendesk weight: 500 @@ -18,7 +19,7 @@ weight: 500 The Zendesk integration for Grafana OnCall handles ticket events sent from Zendesk webhooks. The integration provides grouping, auto-acknowledge and auto-resolve logic via customizable alert templates. -> You must have the [role of Admin]({{< relref "user-and-team-management" >}}) to be able to create integrations in Grafana OnCall. +> You must have the [role of Admin][user-and-team-management] to be able to create integrations in Grafana OnCall. ## Configuring Grafana OnCall to Receive Alerts from Zendesk @@ -64,3 +65,105 @@ Grafana OnCall provides grouping, auto-acknowledge and auto-resolve logic for th - Alert groups are auto-resolved when the ticket status is set to "Solved" To customize this behaviour, consider modifying alert templates in integration settings. + +## Configuring Grafana OnCall to send data to Zendesk + +Grafana OnCall can automatically create and resolve tickets in Zendesk via [outgoing webhooks]({{< relref "_index.md" >}}). +This guide provides example webhook configurations for common use cases, as well as information on how to set up a user in Zendesk to be used by Grafana OnCall. + +### Prerequisites + +1. Create a new user in Zendesk to be used by Grafana OnCall. +[Obtain an API token for the user](https://support.zendesk.com/hc/en-us/articles/4408889192858-Generating-a-new-API-token), +these credentials will be used to communicate with Zendesk API. +2. Make sure the user has appropriate permissions to create and update tickets in Zendesk. + +### Create tickets in Zendesk + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically create +tickets in Zendesk from Grafana OnCall alert groups. + +Create a new Outgoing Webhook in Grafana OnCall, and configure it as follows: + +- Trigger type: `Alert Group Created` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `POST` + +- Webhook URL: + +```text +https://.zendesk.com/api/v2/tickets +``` + +Replace `` with your Zendesk instance. + +- Username: Username of the [Zendesk user](#prerequisites), followed by `/token` (e.g. `user@example.com/token`) + +- Password: API token of the [Zendesk user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "ticket": { + "type": "incident", + "subject": "{{alert_group.title}}", + "comment": { + "body": "This ticket is created automatically by Grafana OnCall. Alert group {{alert_group.id}}: {{alert_group.permalinks.web}}" + } + } +} +``` + +### Resolve tickets in Zendesk + +The steps below describe how to create an outgoing webhook in Grafana OnCall that will allow to automatically resolve +tickets in Zendesk when an alert group is resolved in Grafana OnCall. + +- Trigger type: `Resolved` + +- Integrations: Select integrations that will trigger the webhook + +- HTTP method: `PUT` + +- Webhook URL: + +```text +https://.zendesk.com/api/v2/tickets/{{responses..ticket.id}} +``` + +Replace `` with your Zendesk instance, and `` with the ID of the [webhook used for creating tickets](#create-tickets-in-zendesk). + +- Username: Username of the [Zendesk user](#prerequisites), followed by `/token` (e.g. `user@example.com/token`) + +- Password: API token of the [Zendesk user](#prerequisites) + +Use the following JSON template as webhook data: + +```json +{ + "ticket": { + "status": "solved", + "comment": { + "body": "Resolved by Grafana OnCall.", + "public": false + } + } +} +``` + +### Advanced usage + +The examples above describe how to create outgoing webhooks in Grafana OnCall that will allow to automatically create and resolve tickets in Zendesk. + +Consider modifying example templates to fit your use case (e.g. to include more information on alert groups). +Refer to [outgoing webhooks documentation]({{< relref "_index.md" >}}) for more information on available template variables and webhook configuration. + +For more information on Zendesk API, refer to [Zendesk API documentation](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/). + +{{% docs/reference %}} +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" +{{% /docs/reference %}} diff --git a/docs/sources/notify/_index.md b/docs/sources/notify/_index.md index 0e45fb00..7c234d4b 100644 --- a/docs/sources/notify/_index.md +++ b/docs/sources/notify/_index.md @@ -18,7 +18,7 @@ weight: 800 # Notify people Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and -Telegram. You can use [outgoing webhooks]({{< relref "outgoing-webhooks" >}}) for applications that aren't directly +Telegram. You can use [outgoing webhooks][outgoing-webhooks] for applications that aren't directly supported. To configure supported messaging apps, see the following topics: @@ -49,3 +49,8 @@ To configure a users notification policy: 1. Click **Add notification step** and use the dropdowns to specify the notification method and frequency. Notification steps will be followed in the order they are listed. + +{{% docs/reference %}} +[outgoing-webhooks]: "/docs/oncall/ -> /docs/oncall//outgoing-webhooks" +[outgoing-webhooks]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/outgoing-webhooks" +{{% /docs/reference %}} diff --git a/docs/sources/notify/phone-calls-sms/index.md b/docs/sources/notify/phone-calls-sms/index.md index af1e0966..c38fbe67 100644 --- a/docs/sources/notify/phone-calls-sms/index.md +++ b/docs/sources/notify/phone-calls-sms/index.md @@ -17,7 +17,7 @@ weight: 100 # Phone Calls and SMS notifications -Grafana OnCall Cloud includes SMS and Phone notifications, OSS users [could leverage]({{< relref "open-source" >}}) Grafana Cloud as a relay or +Grafana OnCall Cloud includes SMS and Phone notifications, OSS users [could leverage][open-source] Grafana Cloud as a relay or configure other providers like Twilio. ## Are there additional costs for outgoing calls/sms? @@ -41,4 +41,12 @@ In order to learn the phone number used by OnCall, make a test call at the "Phon There are cases when OnCall is not able to make phone calls or send SMS to certain regions or specific phone numbers. We're working hard to fix such cases, but kindly asking to test your personal notification chain to make sure OnCall is able to notify you. Also we suggest to back up Phone Calls and SMS with other notification methods such as -[Mobile App]({{< relref "mobile-app" >}}). +[Mobile App][mobile-app]. + +{{% docs/reference %}} +[mobile-app]: "/docs/oncall/ -> /docs/oncall//mobile-app" +[mobile-app]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/mobile-app" + +[open-source]: "/docs/oncall/ -> /docs/oncall//open-source" +[open-source]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/open-source/" +{{% /docs/reference %}} diff --git a/docs/sources/notify/slack/index.md b/docs/sources/notify/slack/index.md index 9ad7594a..8d85eb13 100644 --- a/docs/sources/notify/slack/index.md +++ b/docs/sources/notify/slack/index.md @@ -29,7 +29,7 @@ To install the Slack integration, you must have Admin permissions in your Grafan that you’d like to integrate with. For Open Source Grafana OnCall Slack installation guidance, refer to -[Open Source Grafana OnCall]({{< relref "open-source" >}}). +[Open Source Grafana OnCall][open-source]. ## Install Slack integration for Grafana OnCall @@ -117,3 +117,8 @@ Use message shortcuts to add resolution notes directly from Slack. Message short 1. Hover over the message and select **More actions** from the menu options. 1. Select **Add as resolution note**. 1. The Grafana OnCall app will react to the message in Slack with the memo emoji and add the message to the alert group timeline. + +{{% docs/reference %}} +[open-source]: "/docs/oncall/ -> /docs/oncall//open-source" +[open-source]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/open-source" +{{% /docs/reference %}} diff --git a/docs/sources/on-call-schedules/_index.md b/docs/sources/on-call-schedules/_index.md index 65850392..2493a55f 100644 --- a/docs/sources/on-call-schedules/_index.md +++ b/docs/sources/on-call-schedules/_index.md @@ -19,14 +19,14 @@ weight: 700 - Users with Viewer role cannot receive alert notifications, therefore, cannot be on-call. For more information about permissions, refer to -[Manage users and teams for Grafana OnCall]({{< relref "user-and-team-management" >}}) +[Manage users and teams for Grafana OnCall][user-and-team-management] ### Web-based schedule Configure and manage on-call schedules directly in the Grafana OnCall plugin. Easily configure and preview rotations, see teammates' time zones, and add overrides. -Learn more about [Web-based schedules]({{< relref "web-schedule" >}}) +Learn more about [Web-based schedules][web-schedule] ### iCal import @@ -34,7 +34,7 @@ Use any calendar service that uses the iCal format to manage and customize on-ca shifts from your calendar app to Grafana OnCall for widely accessible scheduling. iCal imports appear in Grafana OnCall as read-only schedules but can be leveraged similarly to a web-based schedule. -Learn more about [iCal import schedules]({{< relref "ical-schedules" >}}) +Learn more about [iCal import schedules][ical-schedules] ### Terraform @@ -44,3 +44,14 @@ read-only and cannot be edited from the UI. To learn more, read our [Get started with Grafana OnCall and Terraform]( ) blog post. + +{{% docs/reference %}} +[ical-schedules]: "/docs/oncall/ -> /docs/oncall//on-call-schedules/ical-schedules" +[ical-schedules]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/on-call-schedules/ical-schedules" + +[user-and-team-management]: "/docs/oncall/ -> /docs/oncall//user-and-team-management" +[user-and-team-management]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/user-and-team-management" + +[web-schedule]: "/docs/oncall/ -> /docs/oncall//on-call-schedules/web-schedule" +[web-schedule]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/on-call-schedules/web-schedule" +{{% /docs/reference %}} diff --git a/docs/sources/on-call-schedules/api-terraform-schedule/_index.md b/docs/sources/on-call-schedules/api-terraform-schedule/_index.md index e008838e..69c5e637 100644 --- a/docs/sources/on-call-schedules/api-terraform-schedule/_index.md +++ b/docs/sources/on-call-schedules/api-terraform-schedule/_index.md @@ -18,4 +18,9 @@ teams in the org, we suggest considering storing schedules as code. - [Get started with Grafana OnCall and Terraform (blogpost)](https://grafana.com/blog/2022/08/29/get-started-with-grafana-oncall-and-terraform/) - [Grafana Terraform provider reference (OnCall resources are managed using this provider)](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/oncall_schedule) -- [OnCall API]({{< relref "oncall-api-reference" >}}) +- [OnCall API][oncall-api-reference] + +{{% docs/reference %}} +[oncall-api-reference]: "/docs/oncall/ -> /docs/oncall//oncall-api-reference" +[oncall-api-reference]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/oncall-api-reference" +{{% /docs/reference %}} diff --git a/docs/sources/on-call-schedules/ical-schedules/index.md b/docs/sources/on-call-schedules/ical-schedules/index.md index d802f888..d8a79c69 100644 --- a/docs/sources/on-call-schedules/ical-schedules/index.md +++ b/docs/sources/on-call-schedules/ical-schedules/index.md @@ -20,7 +20,7 @@ OnCall as read-only schedules but can be leveraged similarly to a web-based sche > Unfortunately there is a known limitation with Google Calendar import and export. > Google may take up to 24h to import OnCall's calendar (OnCall -> Google) and sometimes our customers report delay in > exporting (Google Calendar -> OnCall). If actual calendar is critical for you, we suggest checking -> [web-based scheduling]({{< relref "web-schedule" >}}). +> [web-based scheduling] ## Before you begin @@ -99,3 +99,8 @@ contain a level marker result in all overlapping users receiving notifications. For example, users AliceGrafana and BobGrafana have overlapping schedules but BobGrafana is the intended primary contact. The calendar events titles would be `[L1] BobGrafana` and `[L0] AliceGrafana` - In this case AliceGrafana maintains the default [L0] status, and would not receive notifications during the overlapping time with BobGrafana. + +{{% docs/reference %}} +[web-schedule]: "/docs/oncall/ -> /docs/oncall//on-call-schedules/web-schedule" +[web-schedule]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/on-call-schedules/web-schedule" +{{% /docs/reference %}} diff --git a/docs/sources/oncall-api-reference/integrations.md b/docs/sources/oncall-api-reference/integrations.md index e67ddf13..e9365c85 100644 --- a/docs/sources/oncall-api-reference/integrations.md +++ b/docs/sources/oncall-api-reference/integrations.md @@ -74,7 +74,7 @@ The above command returns JSON structured in the following way: 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/alertmanager" >}}). +[Alertmanager][alertmanager]. **HTTP request** @@ -314,3 +314,8 @@ curl "{{API_URL}}/api/v1/integrations/CFRPV98RPR1U8/" \ **HTTP request** `DELETE {{API_URL}}/api/v1/integrations//` + +{{% docs/reference %}} +[alertmanager]: "/docs/oncall/ -> /docs/oncall//integrations/alertmanager" +[alertmanager]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/oncall/integrations/alertmanager" +{{% /docs/reference %}} diff --git a/docs/sources/outgoing-webhooks/_index.md b/docs/sources/outgoing-webhooks/_index.md index a871b624..c58f6244 100644 --- a/docs/sources/outgoing-webhooks/_index.md +++ b/docs/sources/outgoing-webhooks/_index.md @@ -1,6 +1,7 @@ --- aliases: - - ../outgoing-webhooks/ + - ../integrations/configure-outgoing-webhooks/ + - /docs/oncall/latest/outgoing-webhooks/ canonical: https://grafana.com/docs/oncall/latest/outgoing-webhooks/ keywords: - Grafana Cloud @@ -9,54 +10,478 @@ keywords: - on-call - amixr - webhooks -title: Outgoing Webhooks -weight: 900 +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 are used by Grafana OnCall to send data to a URL in a flexible way. These webhooks can be +triggered from a variety of event types and make use of Jinja2 to transform data into the format required at +the destination URL. Each outgoing webhook receives contextual data when executed which can be processed by +Jinja2 templates to customize the request being sent. -To automatically send alert data to a destination URL via outgoing webhook: +## Creating an 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. -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. -4. Configure the webhook payload in the **Data** field. -5. Click **Create Webhook**. +To create an outgoing webhook navigate to **Outgoing Webhooks** and click **+ Create**. On this screen outgoing +webhooks can be viewed, edited and deleted. To create the outgoing webhook populate the required fields and +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: +### Outgoing webhook fields -- `{{ alert_payload }}` -- `{{ alert_group_id }}` +The outgoing webhook is defined by the following fields. For more information about template usage +see [Outgoing webhook templates)](#outgoing-webhook-templates) section. -`alert_payload` is always the first level of any variable you want to call. +#### ID -The following is an example of an entry in the **Data** field that would return an alert name and description. +This field is generated after an outgoing webhook has been created. It is used to reference the responses of +other webhooks, see [Advanced Usage - Using response data](#using-response-data) for more details. - { - "name": "{{ alert_payload.labels.alertname }}", - "message": "{{ alert_payload.annotations.description }}" +#### Name + +Display name of the outgoing webhook. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ✔️ | ❌ | _Empty_ | + +#### Enabled + +Controls whether the outgoing webhook will trigger or is ignored. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ✔️ | ❌ | _True_ | + +#### Assign to Team + +Sets which team owns the outgoing webhook for filtering and visibility. +This setting does not restrict outgoing webhook execution to events from the selected team. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _Empty_ | + +#### Trigger Type + +The type of event that will cause this outgoing webhook to execute. The types of triggers are: + +- [Escalation Step](#escalation-step) +- [Alert Group Created](#alert-group-created) +- [Acknowledged](#acknowledged) +- [Resolved](#resolved) +- [Silenced](#silenced) +- [Unsilenced](#unsilenced) +- [Unresolved](#unresolved) +- [Unacknowledged](#acknowledged) + +For more details about types of triggers see [Event types](#event-types) + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ✔️ | ❌ | _None_ | + +#### HTTP Method + +The HTTP method used in the request made by the outgoing webhook. This should match what is required by the URL +you are sending to. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ✔️ | ❌ | _POST_ | + +#### Integrations + +Restricts the outgoing webhook to only trigger if the event came from a selected integration. +If no integrations are selected the outgoing webhook will trigger for any integration. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _None_ | + +#### Webhook URL + +The destination URL the outgoing webhook will make a request to. This must be a FQDN. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ✔️ | ✔️ | _Empty_ | + +#### Webhook Headers + +Headers to add to the outgoing webhook request. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ✔️ | _Empty_ | + +#### Username + +Username to use when making the outgoing webhook request. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _Empty_ | + +#### Password + +Password to use when making the outgoing webhook request. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _Empty_ | + +#### Authorization Header + +Authorization header to use when making the outgoing webhook request. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _None_ | + +#### Trigger Template + +A template used to dynamically determine whether the webhook should execute based on the content of the payload. +If the template evaluates to Empty, True or 1 the webhook will execute. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ✔️ | _Empty_ | + +#### Data + +The main body of the request to be sent by the outgoing webhook. + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ✔️ | _Empty_ | + +#### Forward All + +Toggle to send the entire webhook payload instead of using the values in the **Data** field + +| Required | [Template Accepted](#outgoing-webhook-templates) | Default Value | +|:--------:|:------------------------------------------------:|:-------------:| +| ❌ | ❌ | _False_ | + +## Outgoing webhook templates + +The fields that accept a Jinja2 template in outgoing webhooks are able to process data to customize the output. +The following is an example of the data available to access from a template. Some data depending on the timing +of the webhook and the triggering event may not always be available, +see [field descriptions](#outgoing-webhook-data-fields) specific details. The format you use to call the variables +must match the structure of how the fields are nested in the data. + +```json +{ + "event": { + "type": "resolve", + "time": "2023-04-19T21:59:21.714058+00:00" + }, + "user": { + "id": "UVMX6YI9VY9PV", + "username": "admin", + "email": "admin@localhost" + }, + "alert_group": { + "id": "I6HNZGUFG4K11", + "integration_id": "CZ7URAT4V3QF2", + "route_id": "RKHXJKVZYYVST", + "alerts_count": 1, + "state": "resolved", + "created_at": "2023-04-19T21:53:48.231148Z", + "resolved_at": "2023-04-19T21:59:21.714058Z", + "acknowledged_at": "2023-04-19T21:54:39.029347Z", + "title": "Incident", + "permalinks": { + "slack": null, + "telegram": null, + "web": "https://**********.grafana.net/a/grafana-oncall-app/alert-groups/I6HNZGUFG4K11" } - -The following is an example would return an alert name and the alert's labels. - - { - "alertname" : "{{ alert_payload.labels.alertname }}", - "labels" : "{{ alert_payload.labels }}" + }, + "alert_group_id": "I6HNZGUFG4K11", + "alert_payload": { + "endsAt": "0001-01-01T00:00:00Z", + "labels": { + "region": "eu-1", + "alertname": "TestAlert" + }, + "status": "firing", + "startsAt": "2018-12-25T15:47:47.377363608Z", + "amixr_demo": true, + "annotations": { + "description": "This alert was sent by user for the demonstration purposes" + }, + "generatorURL": "" + }, + "integration": { + "id": "CZ7URAT4V3QF2", + "type": "webhook", + "name": "Main Integration - Webhook", + "team": "Webhooks Demo" + }, + "notified_users": [], + "users_to_be_notified": [], + "responses": { + "WHP936BM1GPVHQ": { + "id": "7Qw7TbPmzppRnhLvK3AdkQ", + "created_at": "15:53:50", + "status": "new", + "content": { + "message": "Ticket created!", + "region": "eu" + } } + } +} +``` -By default, this will return labels in a list format. If you'd like your labels to be sent in formatted JSON, please use `| tojson()` in your data. For example: +### Outgoing webhook data fields - { - "alertname" : "{{ alert_payload.labels.alertname }}", - "labels" : "{{ alert_payload.labels | tojson() }}" - } +#### `event` -> **NOTE:** If you receive an error message and cannot create an outgoing webhook, verify that your JSON is -> formatted correctly. +Context information about the event that triggered the outgoing webhook. + +- `{{ event.type }}` - Lower case string matching [type of event](#event-types) +- `{{ event.time }}` - Time event was triggered + +#### `user` + +Information about the user if the source of the event was a user. If a user acknowledges an alert group after +receiving a notification this field will have that user's information. If an alert group was auto-resolved based +on criteria in the integration this field will be empty. + +- `{{ user.id }}` - [UID](#uid) of the user within Grafana OnCall +- `{{ user.username }}` - Username in Grafana +- `{{ user.email }}` - Email associated with user's Grafana account + +#### `alert_group` + +Details about the alert group associated with this event. + +- `{{ alert_group.id }}` - [UID](#uid) of alert group +- `{{ alert_group.integration_id }}` - [UID](#uid) of integration that alert came through +- `{{ alert_group.route_id }}` - [UID](#uid) of route of integration that alert came through +- `{{ alert_group.alerts_count }}` - Count of alerts in alert group +- `{{ alert_group.state }}` - Current state of alert group +- `{{ alert_group.created_at }}` - Timestamp alert group was created +- `{{ alert_group.resolved_at }}` - Timestamp alert group was resolved (None if not resolved yet) +- `{{ alert_group.acknowledged_at }}` - Timestamp alert group was acknowledged (None if not acknowledged yet) +- `{{ alert_group.title }}` - Title of alert group +- `{{ alert_group.permalinks }}` - Links to alert group in web and chat ops if available + +#### `{{ alert_group_id }}` + +UID of alert group, same as `{{ alert_group.id }}` (For convenience and compatibility with earler versions of Grafana OnCall) + +#### `alert_payload` + +Content of the first alert in the alert group. Content will depend on what the alert source has sent. +Some commonly used fields are: + +- `{{ alert_payload.labels.alertname }}` +- `{{ alert_payload.annotations.description }}` + +#### `integration` + +Details about the integration that received this alert + +- `{{ integration.id }}` - [UID](#uid) of integration +- `{{ integration.type }}` - Type of integration (grafana, alertmanager, webhook, etc.) +- `{{ integration.name }}` - Name of integration +- `{{ integration.team }}` - Team integration belongs to if integration is assigned to a team + +#### `notified_users` + +Array of users that have received notifications about the associated alert group. Each user element in the array +consists of `id`,`username`,`email`. Depending on timing of events and notifications this might not be populated yet +if notifications are still in progress. Access as `{{ notified_users[0].username }}` for example. + +#### `users_to_notify` + +Array of users that could potentially be notified based on the configured escalation chain. Each user element in the array +consists of `id`,`username`,`email`. Array elements are ordered based on the order users will be notified with the +first element being the user that will be notified next. Like `notified_users` depending on timing of notifications +a user in this array may have already been notified by the time this data is being processed. Access as +`{{ users_to_notify[0].username }}` for example. + +#### `responses` + +The responses field is used to access the response data of other webhooks that are associated with this alert group. +The keys inside responses are the [UID](#uid) of other outgoing webhooks. The values inside each response is the latest +response of the referenced webhook when it was executed on behalf of the current alert group. +See [Advanced Usage - Using response data](#using-response-data) for more details. Access as +`{{ responses["WHP936BM1GPVHQ"].content.message }}` for example + +### UID + +Templates often use UIDs to make decisions about what actions to take if you need to find the UID of an object +in the user interface to reference they can be found in the following places: + +- Outgoing Webhook - In the table there is an info icon, UID displayed on hover, click to copy to clipboard +- Integration - In integrations beside the name is an info icon, UID displayed on hover, click to copy to clipboard +- Routes - With an integration selected beside Send Demo Alert is an infor icon, UID displayed on hover, +click to copy to clipboard +- Alert group - When viewing an alert group UID is visible in the browser URL +- User - When viewing a user's profile UID is visible in the browser URL + +UIDs are also visible in the browser URL when a specific object is selected for view or edit. + +### Template examples + +The following is an example of an entry in the Data field that would return an alert name and description. + +```json +{ + "name": "{{ alert_payload.labels.alertname }}", + "message": "{{ alert_payload.annotations.description }}" +} +``` + +Here is an example using the user's email address as part of a URL: + +```bash +https://someticketsystem.com/new-ticket?assign-user={{ user.email }} +``` + +#### Note about JSON + +Take this template for example: + +```json +{ + "labels" : "{{ alert_payload.labels }}" +} +``` + +It will result in the following (Invalid JSON due to single quotes): + +```json +{ + "labels": {'region': 'eu-1', 'alertname': 'TestAlert'} +} +``` + +To fix change the template to: + +```json +{ + "labels" : "{{ alert_payload.labels | tojson()}}" +} +``` + +Now the result is correct: + +```json +{ + "labels": { + "alertname": "TestAlert", + "region": "eu-1" + } +} +``` + +## Event types + +### Escalation Step + +`event.type` `escalation` + +This event will trigger when the outgoing webhook is included as a step in an escalation chain. + +### Alert Group Created + +`event.type` `alert group created` + +This event will trigger when a new alert group is created. + +### Acknowledged + +`event.type` `acknowledge` + +This event will trigger when a user acknowledges an alert group or an alert group is auto-acknowledged +by the integration. + +### Resolved + +`event.type` `resolve` + +This event will trigger when a user resolves an alert group or an alert group is auto-resolved +by the integration. + +### Silenced + +`event.type` `silence` + +This event will trigger when a user silences an alert group. + +### Unsilenced + +`event.type` `unsilence` + +This event will trigger when a user unsilences an alert group or a silence expires. + +### Unresolved + +`event.type` `unresolve` + +This event will trigger when a user unresolves an alert group. + +### Unacknowledged + +`event.type` `unacknowledge` + +This event will trigger when a user unacknowledges an alert group. + +## Viewing status of outgoing webhooks + +In the outgoing webhooks table if a webhook is enabled **Last Run** will have the following information: + +- Timestamp outgoing webhook was triggered +- HTTP response code + +If more information is required you can click **Status** in the table. The status drawer shows the following: + +- Webhook Name +- Webhook UID +- Trigger Type +- Last Run Time +- URL +- Response Code +- Response Body +- Trigger Template +- Request Headers +- Request Data + +In the status drawer if a field makes use of a template it will display both the template and the result +otherwise it will only display the value. Fields which are not used are not shown. + +## Advanced usage + +### Using trigger template field + +The [trigger template field](#trigger-type) can be used to provide control over whether a webhook will execute. +This is useful in situations where many different kinds of alerts are going to the same integration but only some of +them should call the webhook. To accomplish this the trigger template field can contain a template that will process +data from the alert group and evaluate to empty, True or 1 if the webhook should execute, any other values will result +in the webhook not executing. + +### Using response data + +The `responses` section of the payload makes available the responses of other webhooks that have acted on the same +alert group. To access them `responses` uses the `id` of the webhook as a key. The `id` can be found by hovering +over the info icon, clicking will copy the `id` to the clipboard. The response data of the most recent +execution of the webhook for this same alert group can be accessed this way. + +The typical application of this is where a webhook will create a ticket in another system and OnCall needs to use +the `id` of that ticket to keep its status synchronized with the state changes being made in OnCall. + +### Advanced examples + +Integrate with third-party services: + +- [JIRA]({{< relref "../integrations/jira" >}}) +- [ServiceNow]({{< relref "../integrations/servicenow" >}}) +- [Zendesk]({{< relref "../integrations/zendesk" >}}) + +{{< section >}} diff --git a/docs/variables.mk b/docs/variables.mk index 2d567451..e08a176c 100644 --- a/docs/variables.mk +++ b/docs/variables.mk @@ -1,2 +1,5 @@ # List of projects to provide to the make-docs script. -PROJECTS = oncall +PROJECTS := oncall + +# Use alternative image until make-docs 3.0.0 is rolled out. +export DOCS_IMAGE := grafana/docs-base:dbd975af06 diff --git a/engine/apps/api/tests/test_escalation_policy.py b/engine/apps/api/tests/test_escalation_policy.py index 5f4464b2..4b8613f8 100644 --- a/engine/apps/api/tests/test_escalation_policy.py +++ b/engine/apps/api/tests/test_escalation_policy.py @@ -1,6 +1,7 @@ from unittest.mock import patch import pytest +from django.conf import settings from django.db.models import Max from django.urls import reverse from django.utils.timezone import timedelta @@ -913,8 +914,9 @@ def test_escalation_policy_escalation_options_webhooks( url = reverse("api-internal:escalation_policy-escalation-options") - with patch("apps.api.views.escalation_policy.is_webhooks_enabled_for_organization", return_value=enabled): - response = client.get(url, format="json", **make_user_auth_headers(user, token)) + settings.FEATURE_WEBHOOKS_2_ENABLED = enabled + + response = client.get(url, format="json", **make_user_auth_headers(user, token)) returned_options = [option["value"] for option in response.json()] if enabled: diff --git a/engine/apps/api/views/escalation_policy.py b/engine/apps/api/views/escalation_policy.py index fa330f23..611285c4 100644 --- a/engine/apps/api/views/escalation_policy.py +++ b/engine/apps/api/views/escalation_policy.py @@ -14,7 +14,6 @@ from apps.api.serializers.escalation_policy import ( EscalationPolicyUpdateSerializer, ) from apps.auth_token.auth import PluginAuthentication -from apps.webhooks.utils import is_webhooks_enabled_for_organization from common.api_helpers.mixins import ( CreateSerializerMixin, PublicPrimaryKeyMixin, @@ -132,10 +131,12 @@ class EscalationPolicyView( def escalation_options(self, request): choices = [] for step in EscalationPolicy.INTERNAL_API_STEPS: - if step == EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK and not is_webhooks_enabled_for_organization( - self.request.auth.organization.pk - ): + if step == EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK and not settings.FEATURE_WEBHOOKS_2_ENABLED: continue + + if step == EscalationPolicy.STEP_TRIGGER_CUSTOM_BUTTON and settings.FEATURE_WEBHOOKS_2_ENABLED: + continue + verbal = EscalationPolicy.INTERNAL_API_STEPS_TO_VERBAL_MAP[step] can_change_importance = ( step in EscalationPolicy.IMPORTANT_STEPS_SET or step in EscalationPolicy.DEFAULT_STEPS_SET diff --git a/engine/apps/api/views/features.py b/engine/apps/api/views/features.py index b0e41897..cd47a088 100644 --- a/engine/apps/api/views/features.py +++ b/engine/apps/api/views/features.py @@ -5,7 +5,6 @@ from rest_framework.views import APIView from apps.auth_token.auth import PluginAuthentication from apps.base.utils import live_settings -from apps.webhooks.utils import is_webhooks_enabled_for_organization FEATURE_SLACK = "slack" FEATURE_TELEGRAM = "telegram" @@ -60,7 +59,7 @@ class FeaturesAPIView(APIView): if request.auth.organization.pk in enabled_web_schedules_orgs.json_value["org_ids"]: enabled_features.append(FEATURE_WEB_SCHEDULES) - if is_webhooks_enabled_for_organization(request.auth.organization.pk): + if settings.FEATURE_WEBHOOKS_2_ENABLED: enabled_features.append(FEATURE_WEBHOOKS2) return enabled_features diff --git a/engine/apps/api/views/webhooks.py b/engine/apps/api/views/webhooks.py index 940ee683..3b186852 100644 --- a/engine/apps/api/views/webhooks.py +++ b/engine/apps/api/views/webhooks.py @@ -1,5 +1,6 @@ import json +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django_filters import rest_framework as filters from rest_framework import status @@ -14,7 +15,7 @@ from apps.api.permissions import RBACPermission from apps.api.serializers.webhook import WebhookResponseSerializer, WebhookSerializer from apps.auth_token.auth import PluginAuthentication from apps.webhooks.models import Webhook, WebhookResponse -from apps.webhooks.utils import apply_jinja_template_for_json, is_webhooks_enabled_for_organization +from apps.webhooks.utils import apply_jinja_template_for_json from common.api_helpers.exceptions import BadRequest from common.api_helpers.filters import ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, TeamModelMultipleChoiceFilter from common.api_helpers.mixins import PublicPrimaryKeyMixin, TeamFilteringMixin @@ -103,8 +104,8 @@ class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet): instance.delete() def check_webhooks_2_enabled(self): - if not is_webhooks_enabled_for_organization(self.request.auth.organization.pk): - raise PermissionDenied("Webhooks 2 not enabled for organization. Permission denied.") + if not settings.FEATURE_WEBHOOKS_2_ENABLED: + raise PermissionDenied("Webhooks 2 not enabled. Permission denied.") @action(methods=["get"], detail=False) def filters(self, request): diff --git a/engine/apps/webhooks/migrations/0008_auto_20230712_1613.py b/engine/apps/webhooks/migrations/0008_auto_20230712_1613.py new file mode 100644 index 00000000..f31a897b --- /dev/null +++ b/engine/apps/webhooks/migrations/0008_auto_20230712_1613.py @@ -0,0 +1,77 @@ +# Generated by Django 3.2.19 on 2023-07-12 16:13 +import logging + +from django.core.exceptions import ObjectDoesNotExist +from django.db import migrations + +from apps.alerts.models import EscalationPolicy +from apps.webhooks.models import Webhook + +LEGACY_SUFFIX = " (Legacy)" + +logger = logging.getLogger(__name__) + +def convert_custom_button_to_webhook(apps, schema_editor): + CustomButton = apps.get_model("alerts", "CustomButton") + Webhooks = apps.get_model("webhooks", "Webhook") + EscalationPolicies = apps.get_model("alerts", "EscalationPolicy") + + for cb in CustomButton.objects.all(): + webhook, _ = Webhooks.objects.get_or_create( + organization=cb.organization, + team=cb.team, + name=cb.name + LEGACY_SUFFIX, + is_legacy=True, + defaults=dict( + created_at=cb.created_at, + url=cb.webhook, + username=cb.user, + password=cb.password, + authorization_header=cb.authorization_header, + trigger_type=Webhook.TRIGGER_ESCALATION_STEP, + forward_all=cb.forward_whole_payload, + data=cb.data, + ) + ) + # migrate related escalation policies + EscalationPolicies.objects.filter( + step=EscalationPolicy.STEP_TRIGGER_CUSTOM_BUTTON, + custom_button_trigger=cb, + ).update( + step=EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK, + custom_webhook=webhook, + ) + + +def undo_custom_button_to_webhook(apps, schema_editor): + CustomButton = apps.get_model("alerts", "CustomButton") + Webhooks = apps.get_model("webhooks", "Webhook") + EscalationPolicies = apps.get_model("alerts", "EscalationPolicy") + + for webhook in Webhooks.objects.filter(is_legacy=True): + try: + cb = CustomButton.objects.get(name=webhook.name.removesuffix(LEGACY_SUFFIX), team=webhook.team, organization=webhook.organization) + except ObjectDoesNotExist: + logger.warning(f"Did not find matching custom button to revert {webhook.name} {webhook.organization.stack_slug}, skipping") + continue + + EscalationPolicies.objects.filter( + step=EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK, + custom_webhook=webhook, + ).update( + step=EscalationPolicy.STEP_TRIGGER_CUSTOM_BUTTON, + custom_button_trigger=cb, + custom_webhook=None, + ) + webhook.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('webhooks', '0007_webhookresponse_event_data'), + ] + + operations = [ + migrations.RunPython(convert_custom_button_to_webhook, undo_custom_button_to_webhook) + ] diff --git a/engine/apps/webhooks/utils.py b/engine/apps/webhooks/utils.py index d9d709ef..d46c0f8c 100644 --- a/engine/apps/webhooks/utils.py +++ b/engine/apps/webhooks/utils.py @@ -4,7 +4,6 @@ import re import socket from urllib.parse import urlparse -from django.apps import apps from django.conf import settings from apps.base.utils import live_settings @@ -179,16 +178,3 @@ def serialize_event(event, alert_group, user, responses=None): data["responses"] = responses return data - - -def is_webhooks_enabled_for_organization(organization_id): - DynamicSetting = apps.get_model("base", "DynamicSetting") - enabled_webhooks_orgs = DynamicSetting.objects.get_or_create( - name="enabled_webhooks_2_orgs", - defaults={ - "json_value": { - "org_ids": [], - } - }, - )[0] - return organization_id in enabled_webhooks_orgs.json_value["org_ids"] diff --git a/engine/settings/base.py b/engine/settings/base.py index 0664ce0f..e9a58b1f 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -64,6 +64,7 @@ FEATURE_WEB_SCHEDULES_ENABLED = getenv_boolean("FEATURE_WEB_SCHEDULES_ENABLED", FEATURE_MULTIREGION_ENABLED = getenv_boolean("FEATURE_MULTIREGION_ENABLED", default=False) FEATURE_INBOUND_EMAIL_ENABLED = getenv_boolean("FEATURE_INBOUND_EMAIL_ENABLED", default=False) FEATURE_PROMETHEUS_EXPORTER_ENABLED = getenv_boolean("FEATURE_PROMETHEUS_EXPORTER_ENABLED", default=False) +FEATURE_WEBHOOKS_2_ENABLED = getenv_boolean("FEATURE_WEBHOOKS_2_ENABLED", default=True) GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED = getenv_boolean("GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED", default=True) GRAFANA_CLOUD_NOTIFICATIONS_ENABLED = getenv_boolean("GRAFANA_CLOUD_NOTIFICATIONS_ENABLED", default=True) diff --git a/grafana-plugin/src/components/GForm/GForm.tsx b/grafana-plugin/src/components/GForm/GForm.tsx index d60de4e8..8574bce1 100644 --- a/grafana-plugin/src/components/GForm/GForm.tsx +++ b/grafana-plugin/src/components/GForm/GForm.tsx @@ -21,6 +21,7 @@ interface GFormProps { onSubmit: (data: any) => void; onFieldRender?: ( formItem: FormItem, + disabled: boolean, renderedControl: React.ReactElement, values: any, setValue: (value: string) => void @@ -31,9 +32,16 @@ const nullNormalizer = (value: string) => { return value || null; }; -function renderFormControl(formItem: FormItem, register: any, control: any, onChangeFn: (field, value) => void) { +function renderFormControl( + formItem: FormItem, + register: any, + control: any, + disabled, + onChangeFn: (field, value) => void +) { switch (formItem.type) { case FormItemType.Input: + console.log({ ...register(formItem.name, formItem.validation) }); return ( onChangeFn(undefined, value)} /> ); @@ -131,7 +139,7 @@ function renderFormControl(formItem: FormItem, register: any, control: any, onCh showLineNumbers={false} monacoOptions={{ ...MONACO_READONLY_CONFIG, - readOnly: formItem.isReadOnly, + readOnly: disabled, }} onChange={(value) => onChangeFn(field, value)} /> @@ -160,8 +168,13 @@ class GForm extends React.Component { setValue(formItem.name, undefined); // clear input value on hide return null; } + const disabled = formItem.disabled + ? true + : formItem.getDisabled + ? formItem.getDisabled(getValues()) + : false; - const formControl = renderFormControl(formItem, register, control, (field, value) => { + const formControl = renderFormControl(formItem, register, control, disabled, (field, value) => { field?.onChange(value); this.forceUpdate(); }); @@ -169,14 +182,16 @@ class GForm extends React.Component { return ( {onFieldRender - ? onFieldRender(formItem, formControl, getValues(), (value) => setValue(formItem.name, value)) + ? onFieldRender(formItem, disabled, formControl, getValues(), (value) => + setValue(formItem.name, value) + ) : formControl} ); diff --git a/grafana-plugin/src/components/GForm/GForm.types.ts b/grafana-plugin/src/components/GForm/GForm.types.ts index 4795a3a1..52253b93 100644 --- a/grafana-plugin/src/components/GForm/GForm.types.ts +++ b/grafana-plugin/src/components/GForm/GForm.types.ts @@ -14,7 +14,7 @@ export interface FormItem { name: string; label?: string; type: FormItemType; - isReadOnly?: boolean; + disabled?: boolean; description?: string; normalize?: (value: any) => any; isVisible?: (data: any) => any; diff --git a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx index 3560932d..89969613 100644 --- a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx @@ -442,6 +442,10 @@ export class EscalationPolicy extends React.Component { + const webhook = outgoingWebhook2Store.items[id]; + return webhook.trigger_type_name === 'Escalation step'; + }} /> ); diff --git a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.tsx b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.tsx index f7b6eb68..d96588e1 100644 --- a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.tsx +++ b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.tsx @@ -152,7 +152,6 @@ export const form: { name: string; fields: FormItem[] } = { label: 'Webhook Headers', description: 'Request headers should be in JSON format.', type: FormItemType.Monaco, - isReadOnly: true, extra: { rows: 3, }, @@ -174,7 +173,6 @@ export const form: { name: string; fields: FormItem[] } = { { name: 'trigger_template', type: FormItemType.Monaco, - isReadOnly: true, description: 'Trigger template is used to conditionally execute the webhook based on incoming data. The trigger template must be empty or evaluate to true or 1 for the webhook to be sent', extra: { @@ -191,7 +189,6 @@ export const form: { name: string; fields: FormItem[] } = { name: 'data', getDisabled: (data) => Boolean(data?.forward_all), type: FormItemType.Monaco, - isReadOnly: true, description: 'Available variables: {{ event }}, {{ user }}, {{ alert_group }}, {{ alert_group_id }}, {{ alert_payload }}, {{ integration }}, {{ notified_users }}, {{ users_to_be_notified }}, {{ responses }}', extra: {}, diff --git a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.tsx b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.tsx index 2f915fc9..42e3bac6 100644 --- a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.tsx +++ b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.tsx @@ -68,12 +68,19 @@ const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => { }; }; - const enrchField = (formItem: FormItem, renderedControl: React.ReactElement, values, setFormFieldValue) => { + const enrchField = ( + formItem: FormItem, + disabled: boolean, + renderedControl: React.ReactElement, + values, + setFormFieldValue + ) => { if (formItem.type === FormItemType.Monaco) { return (
{renderedControl}
{data.is_legacy ? (
- Legacy migrated webhooks are not editable. + Legacy migrated webhooks are not editable. Make a copy to make changes.
) : ( '' diff --git a/grafana-plugin/src/containers/WebhooksTemplateEditor/WebhooksTemplateEditor.tsx b/grafana-plugin/src/containers/WebhooksTemplateEditor/WebhooksTemplateEditor.tsx index e9a84ed1..63dd8e5c 100644 --- a/grafana-plugin/src/containers/WebhooksTemplateEditor/WebhooksTemplateEditor.tsx +++ b/grafana-plugin/src/containers/WebhooksTemplateEditor/WebhooksTemplateEditor.tsx @@ -1,10 +1,11 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Button, Drawer, HorizontalGroup, VerticalGroup } from '@grafana/ui'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import CheatSheet from 'components/CheatSheet/CheatSheet'; +import { genericTemplateCheatSheet } from 'components/CheatSheet/CheatSheet.config'; import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; import Text from 'components/Text/Text'; import styles from 'containers/IntegrationTemplate/IntegrationTemplate.module.scss'; @@ -32,7 +33,7 @@ interface WebhooksTemplateEditorProps { } const WebhooksTemplateEditor: React.FC = ({ template, id, onHide, handleSubmit }) => { - const [isCheatSheetVisible] = useState(false); + const [isCheatSheetVisible, setIsCheatSheetVisible] = useState(false); const [changedTemplateBody, setChangedTemplateBody] = useState(template.value); const [editorHeight, setEditorHeight] = useState(undefined); const [selectedPayload, setSelectedPayload] = useState(undefined); @@ -71,6 +72,14 @@ const WebhooksTemplateEditor: React.FC = ({ templat } }; + const onShowCheatSheet = useCallback(() => { + setIsCheatSheetVisible(true); + }, []); + + const onCloseCheatSheet = useCallback(() => { + setIsCheatSheetVisible(false); + }, []); + return ( = ({ templat {isCheatSheetVisible ? ( ) : ( @@ -129,10 +138,9 @@ const WebhooksTemplateEditor: React.FC = ({ templat
Template editor - - {/* */} +
@@ -163,14 +171,6 @@ const WebhooksTemplateEditor: React.FC = ({ templat
); - - // function onShowCheatSheet() {} - - function onCloseCheatSheet() {} - - function getCheatSheet(_templateName: string) { - return undefined; - } }; export default WebhooksTemplateEditor; diff --git a/grafana-plugin/src/models/filters/filters.helpers.ts b/grafana-plugin/src/models/filters/filters.helpers.ts index b3ca0149..9265c276 100644 --- a/grafana-plugin/src/models/filters/filters.helpers.ts +++ b/grafana-plugin/src/models/filters/filters.helpers.ts @@ -2,7 +2,6 @@ export const getApiPathByPage = (page: string) => { return ( { outgoing_webhooks: 'custom_buttons', - outgoing_webhooks_2: 'webhooks', incidents: 'alertgroups', integrations: 'alert_receive_channels', }[page] || page diff --git a/grafana-plugin/src/pages/index.tsx b/grafana-plugin/src/pages/index.tsx index 48e9a989..3271b2a0 100644 --- a/grafana-plugin/src/pages/index.tsx +++ b/grafana-plugin/src/pages/index.tsx @@ -97,15 +97,6 @@ export const pages: { [id: string]: PageDefinition } = [ hideFromTabs: isTopNavbar(), action: UserActions.ChatOpsRead, }, - { - icon: 'link', - id: 'outgoing_webhooks_2', - text: 'Outgoing webhooks 2', - path: getPath('outgoing_webhooks_2'), - hideFromBreadcrumbs: true, - hideFromTabs: true, - action: UserActions.OutgoingWebhooksRead, - }, { icon: 'wrench', id: 'maintenance', @@ -177,8 +168,7 @@ export const ROUTES = { escalations: ['escalations', 'escalations/:id'], schedules: ['schedules'], schedule: ['schedules/:id'], - outgoing_webhooks: ['outgoing_webhooks', 'outgoing_webhooks/:id'], - outgoing_webhooks_2: ['outgoing_webhooks_2', 'outgoing_webhooks_2/:id', 'outgoing_webhooks_2/:action/:id'], + outgoing_webhooks: ['outgoing_webhooks', 'outgoing_webhooks/:id', 'outgoing_webhooks/:action/:id'], maintenance: ['maintenance'], settings: ['settings'], 'chat-ops': ['chat-ops'], diff --git a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.module.scss b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.module.scss index 6b9ed498..63b4cabc 100644 --- a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.module.scss +++ b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.module.scss @@ -10,10 +10,6 @@ align-items: baseline; } -.header__desc { - margin-bottom: 12px; -} - .hamburgerMenu { display: flex; flex-direction: column; @@ -38,4 +34,4 @@ &:hover { background: var(--cards-background); } -} \ No newline at end of file +} diff --git a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx index 0ba16373..98cf2c95 100644 --- a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx +++ b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx @@ -177,20 +177,13 @@ class OutgoingWebhooks2 extends React.Component (
- - - Outgoing Webhooks 2 - - - Preview Functionality! Things will change and things - will break! Do not use for critical production processes! - - + + Outgoing Webhooks +
-
@@ -217,7 +210,7 @@ class OutgoingWebhooks2 extends React.Component { this.onDeleteClick(outgoingWebhook2Id).then(() => { this.setState({ outgoingWebhook2Id: undefined, outgoingWebhook2Action: undefined }); - history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2`); + history.push(`${PLUGIN_ROOT}/outgoing_webhooks`); }); }} /> @@ -393,7 +386,7 @@ class OutgoingWebhooks2 extends React.Component - history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2/edit/${id}`) + history.push(`${PLUGIN_ROOT}/outgoing_webhooks/edit/${id}`) ); }; @@ -401,7 +394,7 @@ class OutgoingWebhooks2 extends React.Component - history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2/copy/${id}`) + history.push(`${PLUGIN_ROOT}/outgoing_webhooks/copy/${id}`) ); }; @@ -427,7 +420,7 @@ class OutgoingWebhooks2 extends React.Component - history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2/last_run/${id}`) + history.push(`${PLUGIN_ROOT}/outgoing_webhooks/last_run/${id}`) ); }; @@ -436,7 +429,7 @@ class OutgoingWebhooks2 extends React.Component { - - - - + {rootStore.hasFeature(AppFeature.Webhooks2) ? ( + + ) : ( + + )}