Merge branch 'dev' into grafana_cloud_notifications
This commit is contained in:
commit
94cedd28d9
23 changed files with 327 additions and 444 deletions
12
.drone.yml
12
.drone.yml
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: Build and Release
|
||||
|
|
@ -30,8 +31,8 @@ steps:
|
|||
- yarn ci-build:finish
|
||||
- yarn ci-package
|
||||
- cd ci/dist
|
||||
- zip -r grafana-oncall-app-${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}.zip ./grafana-oncall-app
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app-${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
|
||||
- zip -r grafana-oncall-app.zip ./grafana-oncall-app
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
|
||||
|
||||
- name: Publish Plugin to GCS (release)
|
||||
image: plugins/gcs
|
||||
|
|
@ -227,4 +228,9 @@ get:
|
|||
name: machine-user-token
|
||||
path: infra/data/ci/drone
|
||||
kind: secret
|
||||
name: drone_token
|
||||
name: drone_token
|
||||
---
|
||||
kind: signature
|
||||
hmac: 81b9b7cda5a8f8525f40f39821be50881e0c4cb0c40a45b3e63bc0cc47274649
|
||||
|
||||
...
|
||||
|
|
|
|||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@grafana.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
19
DEVELOPER.md
19
DEVELOPER.md
|
|
@ -59,7 +59,7 @@ mkdir sqlite_data
|
|||
# Migrate the DB:
|
||||
python manage.py migrate
|
||||
|
||||
# Create user for django admin panel:
|
||||
# Create user for django admin panel (if you need it):
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ python manage.py createsuperuser
|
|||
# Http server:
|
||||
python manage.py runserver
|
||||
|
||||
# Worker for background tasks(run it in the parallel terminal, don't forget to export .env there)
|
||||
# Worker for background tasks (run it in the parallel terminal, don't forget to export .env there)
|
||||
python manage.py start_celery
|
||||
|
||||
# Additionally you could launch the worker with periodic tasks launcher (99% you don't need this)
|
||||
|
|
@ -383,3 +383,18 @@ pytest --ds=settings.dev
|
|||
- Set Settings to settings/dev.py
|
||||
5. Create a new Django Server run configuration to Run/Debug the engine
|
||||
- Use a plugin such as EnvFile to load the .env file
|
||||
|
||||
## Update drone build
|
||||
The .drone.yml build file must be signed when changes are made to it. Follow these steps:
|
||||
|
||||
If you have not installed drone CLI follow [these instructions](https://docs.drone.io/cli/install/)
|
||||
|
||||
To sign the .drone.yml file:
|
||||
```bash
|
||||
export DRONE_SERVER=https://drone.grafana.net
|
||||
|
||||
# Get your drone token from https://drone.grafana.net/account
|
||||
export DRONE_TOKEN=<Your DRONE_TOKEN>
|
||||
|
||||
drone sign --save grafana/oncall .drone.yml
|
||||
```
|
||||
|
|
|
|||
159
GOVERNANCE.md
Normal file
159
GOVERNANCE.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
title: Governance
|
||||
---
|
||||
|
||||
# Governance
|
||||
|
||||
This document describes the rules and governance of the project. It is meant to be followed by all the developers of the project and the OnCall community. Common terminology used in this governance document are listed below:
|
||||
|
||||
- **Team members**: Any members of the private [team mailing list][team].
|
||||
|
||||
- **Maintainers**: Maintainers lead an individual project or parts thereof ([`MAINTAINERS.md`][maintainers]).
|
||||
|
||||
- **Projects**: A single repository in the Grafana GitHub organization and listed below is referred to as a project:
|
||||
|
||||
- oncall
|
||||
|
||||
- **The OnCall project**: The sum of all activities performed under this governance, concerning one or more repositories or the community.
|
||||
|
||||
## Values
|
||||
|
||||
The OnCall developers and community are expected to follow the values defined in the [Code of Conduct][coc]. Furthermore, the OnCall community strives for kindness, giving feedback effectively, and building a welcoming environment. The OnCall developers generally decide by consensus and only resort to conflict resolution by a majority vote if consensus cannot be reached.
|
||||
|
||||
## Projects
|
||||
|
||||
Each project must have a [`MAINTAINERS.md`][maintainers] file with at least one maintainer. Where a project has a release process, access and documentation should be such that more than one person can perform a release. Releases should be announced on the [announcements][https://github.com/grafana/oncall/discussions/categories/announcements] category at the GitHub Discussions. Any new projects should be first proposed on the [team mailing list][team] following the voting procedures listed below.
|
||||
|
||||
## Decision making
|
||||
|
||||
### Team members
|
||||
|
||||
Team member status may be given to those who have made ongoing contributions to the OnCall project for at least 3 months. This is usually in the form of code improvements and/or notable work on documentation, but organizing events or user support could also be taken into account.
|
||||
|
||||
New members may be proposed by any existing member by email to the [team mailing list][team]. It is highly desirable to reach consensus about acceptance of a new member. However, the proposal is ultimately voted on by a formal [supermajority vote](#supermajority-vote).
|
||||
|
||||
If the new member proposal is accepted, the proposed team member should be contacted privately via email to confirm or deny their acceptance of team membership. This email will also be CC'd to the [team mailing list][team] for record-keeping purposes.
|
||||
|
||||
If they choose to accept, the [onboarding](#onboarding) procedure is followed.
|
||||
|
||||
Team members may retire at any time by emailing [the team][team].
|
||||
|
||||
Team members can be removed by [supermajority vote](#supermajority-vote) on [the team mailing list][team].
|
||||
For this vote, the member in question is not eligible to vote and does not count towards the quorum.
|
||||
Any removal vote can cover only one single person.
|
||||
|
||||
Upon death of a member, they leave the team automatically.
|
||||
|
||||
In case a member leaves, the [offboarding](#offboarding) procedure is applied.
|
||||
|
||||
The current team members are:
|
||||
|
||||
- Ildar Iskhakov — [@iskhakov](https://github.com/iskhakov) ([Grafana Labs](https://grafana.com/))
|
||||
- Innokentii Konstantinov — [@Konstantinov-Innokentii](https://github.com/Konstantinov-Innokentii) ([Grafana Labs](https://grafana.com/))
|
||||
- Matías Bordese — [@matiasb](https://github.com/matiasb) ([Grafana Labs](https://grafana.com/))
|
||||
- Matvey Kukuy — [@Matvey-Kuk](https://github.com/Matvey-Kuk) ([Grafana Labs](https://grafana.com/))
|
||||
- Michael Derynck — [@mderynck](https://github.com/mderynck) ([Grafana Labs](https://grafana.com/))
|
||||
- Vadim Stepanov — [@vadimkerr](https://github.com/vadimkerr) ([Grafana Labs](https://grafana.com/))
|
||||
- Yulia Shanyrova — [@Ukochka](https://github.com/Ukochka) ([Grafana Labs](https://grafana.com/))
|
||||
- Maxim Mordasov — [@maskin25](https://github.com/maskin25) ([Grafana Labs](https://grafana.com/))
|
||||
- Julia Artyukhina — [@Ferril](https://github.com/Ferril) ([Grafana Labs](https://grafana.com/))
|
||||
- Julia Artyukhina — [@Ferril](https://github.com/Ferril) ([Grafana Labs](https://grafana.com/))
|
||||
|
||||
Previous team members:
|
||||
|
||||
- n/a
|
||||
|
||||
### Maintainers
|
||||
|
||||
Maintainers lead one or more project(s) or parts thereof and serve as a point of conflict resolution amongst the contributors to this project. Ideally, maintainers are also team members, but exceptions are possible for suitable maintainers that, for whatever reason, are not yet team members.
|
||||
|
||||
Changes in maintainership have to be announced on the [announcemount][https://github.com/grafana/oncall/discussions/categories/announcements] category at the GitHub Discussions. They are decided by [rough consensus](#consensus) and formalized by changing the [`MAINTAINERS.md`][maintainers] file of the respective repository.
|
||||
|
||||
Maintainers are granted commit rights to all projects covered by this governance.
|
||||
|
||||
A maintainer or committer may resign by notifying the [team mailing list][team]. A maintainer with no project activity for a year is considered to have resigned. Maintainers that wish to resign are encouraged to propose another team member to take over the project.
|
||||
|
||||
A project may have multiple maintainers, as long as the responsibilities are clearly agreed upon between them. This includes coordinating who handles which issues and pull requests.
|
||||
|
||||
### Technical decisions
|
||||
|
||||
Technical decisions that only affect a single project are made informally by the maintainer of this project, and [rough consensus](#consensus) is assumed. Technical decisions that span multiple parts of the project should be discussed and made on the the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
Decisions are usually made by [rough consensus](#consensus). If no consensus can be reached, the matter may be resolved by [majority vote](#majority-vote).
|
||||
|
||||
### Governance changes
|
||||
|
||||
Changes to this document are made by Grafana Labs.
|
||||
|
||||
### Other matters
|
||||
|
||||
Any matter that needs a decision may be called to a vote by any member if they deem it necessary. For private or personnel matters, discussion and voting takes place on the [team mailing list][team], otherwise on the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
## Voting
|
||||
|
||||
The OnCall project usually runs by informal consensus, however sometimes a formal decision must be made.
|
||||
|
||||
Depending on the subject matter, as laid out [above](#decision-making), different methods of voting are used.
|
||||
|
||||
For all votes, voting must be open for at least one week. The end date should be clearly stated in the call to vote. A vote may be called and closed early if enough votes have come in one way so that further votes cannot change the final decision.
|
||||
|
||||
In all cases, all and only [team members](#team-members) are eligible to vote, with the sole exception of the forced removal of a team member, in which said member is not eligible to vote.
|
||||
|
||||
Discussion and votes on personnel matters (including but not limited to team membership and maintainership) are held in private on the [team mailing list][team]. All other discussion and votes are held in public on the [GitHub Discussions][https://github.com/grafana/oncall/discussions].
|
||||
|
||||
For public discussions, anyone interested is encouraged to participate. Formal power to object or vote is limited to [team members](#team-members).
|
||||
|
||||
### Consensus
|
||||
|
||||
The default decision making mechanism for the OnCall project is [rough][rough] consensus. This means that any decision on technical issues is considered supported by the [team][team] as long as nobody objects or the objection has been considered but not necessarily accommodated.
|
||||
|
||||
Silence on any consensus decision is implicit agreement and equivalent to explicit agreement. Explicit agreement may be stated at will. Decisions may, but do not need to be called out and put up for decision on the [GitHub Discussions][https://github.com/grafana/oncall/discussions] at any time and by anyone.
|
||||
|
||||
Consensus decisions can never override or go against the spirit of an earlier explicit vote.
|
||||
|
||||
If any [team member](#team-members) raises objections, the team members work together towards a solution that all involved can accept. This solution is again subject to rough consensus.
|
||||
|
||||
In case no consensus can be found, but a decision one way or the other must be made, any [team member](#team-members) may call a formal [majority vote](#majority-vote).
|
||||
|
||||
### Majority vote
|
||||
|
||||
Majority votes must be called explicitly in a separate thread on the appropriate mailing list. The subject must be prefixed with `[VOTE]`. In the body, the call to vote must state the proposal being voted on. It should reference any discussion leading up to this point.
|
||||
|
||||
Votes may take the form of a single proposal, with the option to vote yes or no, or the form of multiple alternatives.
|
||||
|
||||
A vote on a single proposal is considered successful if more vote in favor than against.
|
||||
|
||||
If there are multiple alternatives, members may vote for one or more alternatives, or vote “no” to object to all alternatives. It is not possible to cast an “abstain” vote. A vote on multiple alternatives is considered decided in favor of one alternative if it has received the most votes in favor, and a vote from more than half of those voting. Should no alternative reach this quorum, another vote on a reduced number of options may be called separately.
|
||||
|
||||
### Supermajority vote
|
||||
|
||||
Supermajority votes must be called explicitly in a separate thread on the appropriate mailing list. The subject must be prefixed with `[VOTE]`. In the body, the call to vote must state the proposal being voted on. It should reference any discussion leading up to this point.
|
||||
|
||||
Votes may take the form of a single proposal, with the option to vote yes or no, or the form of multiple alternatives.
|
||||
|
||||
A vote on a single proposal is considered successful if at least two thirds of those eligible to vote vote in favor.
|
||||
|
||||
If there are multiple alternatives, members may vote for one or more alternatives, or vote “no” to object to all alternatives. A vote on multiple alternatives is considered decided in favor of one alternative if it has received the most votes in favor, and a vote from at least two thirds of those eligible to vote. Should no alternative reach this quorum, another vote on a reduced number of options may be called separately.
|
||||
|
||||
## On- / Offboarding
|
||||
|
||||
### Onboarding
|
||||
|
||||
The new member is
|
||||
|
||||
- added to the list of [team members](#team-members). Ideally by sending a PR of their own, at least approving said PR.
|
||||
- announced on the [GitHub Discussions][https://github.com/grafana/oncall/discussions] by an existing team member. Ideally, the new member replies in this thread, acknowledging team membership.
|
||||
- added to the projects with commit rights.
|
||||
- added to the [team mailing list][team].
|
||||
|
||||
### Offboarding
|
||||
|
||||
The ex-member is
|
||||
|
||||
- removed from the list of [team members](#team-members). Ideally by sending a PR of their own, at least approving said PR. In case of forced removal, no approval is needed.
|
||||
- removed from the projects. Optionally, they can retain maintainership of one or more repositories if the [team](#team-members) agrees.
|
||||
- removed from the team mailing list and demoted to a normal member of the other mailing lists.
|
||||
- not allowed to call themselves an active team member any more, nor allowed to imply this to be the case.
|
||||
- added to a list of previous members if they so choose.
|
||||
|
||||
If needed, we reserve the right to publicly announce removal.
|
||||
|
|
@ -9,9 +9,11 @@ The default license for this project is [AGPL-3.0-only](LICENSE).
|
|||
The following directories and their subdirectories are licensed under Apache-2.0:
|
||||
|
||||
```
|
||||
n/a
|
||||
```
|
||||
|
||||
The following directories and their subdirectories are licensed under their original upstream licenses:
|
||||
|
||||
```
|
||||
n/a
|
||||
```
|
||||
|
|
|
|||
14
MAINTAINERS.md
Normal file
14
MAINTAINERS.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
The following are the main/default maintainers:
|
||||
|
||||
- Ildar Iskhakov — [@iskhakov](https://github.com/iskhakov) ([Grafana Labs](https://grafana.com/))
|
||||
- Matvey Kukuy — [@Matvey-Kuk](https://github.com/Matvey-Kuk) ([Grafana Labs](https://grafana.com/))
|
||||
|
||||
Some parts of the codebase have other maintainers, the package paths also include all sub-packages:
|
||||
|
||||
n/a
|
||||
|
||||
For the sake of brevity, not all subtrees are explicitly listed. Due to the
|
||||
size of this repository, the natural changes in focus of maintainers over time,
|
||||
and nuances of where particular features live, this list will always be
|
||||
incomplete and out of date. However the listed maintainer(s) should be able to
|
||||
direct a PR/question to the right person.
|
||||
|
|
@ -48,7 +48,7 @@ services:
|
|||
condition: service_healthy
|
||||
|
||||
grafana:
|
||||
image: "grafana/grafana:8.3.2"
|
||||
image: "grafana/grafana:8.5.5"
|
||||
restart: always
|
||||
mem_limit: 500m
|
||||
cpus: 0.5
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ pull:
|
|||
|
||||
.PHONY: docs
|
||||
docs: pull
|
||||
docker run -v $(shell pwd)/sources:$(CONTENT_PATH):Z -p $(PORT) --rm -it $(IMAGE)
|
||||
docker run -v '$(shell pwd)/sources:$(CONTENT_PATH):Z' -p $(PORT) --rm -it $(IMAGE)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import Optional
|
|||
from django.apps import apps
|
||||
from rest_framework import status
|
||||
|
||||
from apps.alerts.tasks import create_contact_points_for_datasource
|
||||
from apps.alerts.tasks import schedule_create_contact_points_for_datasource
|
||||
from apps.grafana_plugin.helpers import GrafanaAPIClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -77,16 +77,15 @@ class GrafanaAlertingSyncManager:
|
|||
# sync other datasource
|
||||
for datasource in datasources:
|
||||
if datasource["type"] == GrafanaAlertingSyncManager.ALERTING_DATASOURCE:
|
||||
if self.create_contact_point(datasource) is None:
|
||||
contact_point = self.create_contact_point(datasource)
|
||||
if contact_point is None:
|
||||
# Failed to create contact point duo to getting wrong alerting config. It is expected behaviour.
|
||||
# Add datasource to list and retry to create contact point for it async
|
||||
datasources_to_create.append(datasource)
|
||||
|
||||
if datasources_to_create:
|
||||
# create other contact points async
|
||||
create_contact_points_for_datasource.apply_async(
|
||||
(self.alert_receive_channel.pk, datasources_to_create),
|
||||
)
|
||||
schedule_create_contact_points_for_datasource(self.alert_receive_channel.pk, datasources_to_create)
|
||||
else:
|
||||
self.alert_receive_channel.is_finished_alerting_setup = True
|
||||
self.alert_receive_channel.save(update_fields=["is_finished_alerting_setup"])
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:42
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from django.utils import timezone, dateparse
|
||||
from apps.alerts.models.alert_receive_channel import number_to_smiles_translator
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
TYPE_SINGLE_EVENT = 0
|
||||
TYPE_RECURRENT_EVENT = 1
|
||||
FREQUENCY_WEEKLY = 1
|
||||
SOURCE_TERRAFORM = 3
|
||||
STEP_WAIT = 0
|
||||
STEP_NOTIFY_USERS_QUEUE = 12
|
||||
SOURCE_WEB = 1
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
Organization = apps.get_model('user_management', 'Organization')
|
||||
AlertReceiveChannel = apps.get_model('alerts', 'AlertReceiveChannel')
|
||||
EscalationChain = apps.get_model('alerts', 'EscalationChain')
|
||||
ChannelFilter = apps.get_model('alerts', 'ChannelFilter')
|
||||
EscalationPolicy = apps.get_model('alerts', 'EscalationPolicy')
|
||||
OnCallScheduleICal = apps.get_model('schedules', 'OnCallScheduleICal')
|
||||
AlertGroup = apps.get_model('alerts', 'AlertGroup')
|
||||
Alert = apps.get_model('alerts', 'Alert')
|
||||
CustomButton = apps.get_model("alerts", "CustomButton")
|
||||
CustomOnCallShift = apps.get_model('schedules', 'CustomOnCallShift')
|
||||
|
||||
organization = Organization.objects.get(public_primary_key=public_api_constants.DEMO_ORGANIZATION_ID)
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
alert_receive_channel, _ = AlertReceiveChannel.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_INTEGRATION_ID,
|
||||
defaults=dict(
|
||||
integration=0,
|
||||
author=user,
|
||||
organization=organization,
|
||||
smile_code=number_to_smiles_translator(0)
|
||||
)
|
||||
)
|
||||
escalation_chain, _ = EscalationChain.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_CHAIN_ID,
|
||||
defaults=dict(
|
||||
name="default",
|
||||
organization=organization,
|
||||
)
|
||||
)
|
||||
|
||||
channel_filter_1, _ = ChannelFilter.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ROUTE_ID_1,
|
||||
defaults=dict(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
slack_channel_id=public_api_constants.DEMO_SLACK_CHANNEL_FOR_ROUTE_ID,
|
||||
filtering_term='us-(east|west)',
|
||||
order=0,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
ChannelFilter.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ROUTE_ID_2,
|
||||
defaults=dict(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
slack_channel_id=public_api_constants.DEMO_SLACK_CHANNEL_FOR_ROUTE_ID,
|
||||
filtering_term='.*',
|
||||
order=1,
|
||||
is_default=True,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
|
||||
EscalationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_POLICY_ID_1,
|
||||
defaults=dict(
|
||||
step=STEP_WAIT,
|
||||
wait_delay=timezone.timedelta(minutes=1),
|
||||
order=0,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
|
||||
escalation_policy_1, _ = EscalationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ESCALATION_POLICY_ID_2,
|
||||
defaults=dict(
|
||||
step=STEP_NOTIFY_USERS_QUEUE,
|
||||
order=1,
|
||||
escalation_chain=escalation_chain,
|
||||
)
|
||||
)
|
||||
escalation_policy_1.notify_to_users_queue.add(user)
|
||||
|
||||
schedule, _ = OnCallScheduleICal.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_SCHEDULE_ID_ICAL,
|
||||
defaults=dict(
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_SCHEDULE_NAME_ICAL,
|
||||
ical_url_overrides=public_api_constants.DEMO_SCHEDULE_ICAL_URL_OVERRIDES,
|
||||
channel=public_api_constants.DEMO_SLACK_CHANNEL_SLACK_ID,
|
||||
)
|
||||
)
|
||||
|
||||
alert_group, _ = AlertGroup.all_objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_INCIDENT_ID,
|
||||
defaults=dict(
|
||||
channel=alert_receive_channel,
|
||||
channel_filter=channel_filter_1,
|
||||
resolved=True,
|
||||
resolved_at=dateparse.parse_datetime(public_api_constants.DEMO_INCIDENT_RESOLVED_AT),
|
||||
)
|
||||
)
|
||||
alert_group.started_at = dateparse.parse_datetime(public_api_constants.DEMO_INCIDENT_CREATED_AT)
|
||||
alert_group.save(update_fields=['started_at'])
|
||||
|
||||
for id, created_at in public_api_constants.DEMO_ALERT_IDS:
|
||||
alert, _ = Alert.objects.get_or_create(
|
||||
public_primary_key=id,
|
||||
defaults=dict(
|
||||
group=alert_group,
|
||||
raw_request_data=public_api_constants.DEMO_ALERT_PAYLOAD,
|
||||
title='Memory above 90% threshold',
|
||||
)
|
||||
)
|
||||
alert.created_at = dateparse.parse_datetime(created_at)
|
||||
alert.save(update_fields=['created_at'])
|
||||
|
||||
CustomButton.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_CUSTOM_ACTION_ID,
|
||||
defaults=dict(
|
||||
name=public_api_constants.DEMO_CUSTOM_ACTION_NAME,
|
||||
organization=organization,
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_1, _ = CustomOnCallShift.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ON_CALL_SHIFT_ID_1,
|
||||
defaults=dict(
|
||||
type=TYPE_SINGLE_EVENT,
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_ON_CALL_SHIFT_NAME_1,
|
||||
start=dateparse.parse_datetime(public_api_constants.DEMO_ON_CALL_SHIFT_START_1),
|
||||
duration=timezone.timedelta(seconds=public_api_constants.DEMO_ON_CALL_SHIFT_DURATION),
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_1.users.add(user)
|
||||
|
||||
on_call_shift_2, _ = CustomOnCallShift.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ON_CALL_SHIFT_ID_2,
|
||||
defaults=dict(
|
||||
type=TYPE_RECURRENT_EVENT,
|
||||
organization=organization,
|
||||
name=public_api_constants.DEMO_ON_CALL_SHIFT_NAME_2,
|
||||
start=dateparse.parse_datetime(public_api_constants.DEMO_ON_CALL_SHIFT_START_2),
|
||||
duration=timezone.timedelta(seconds=public_api_constants.DEMO_ON_CALL_SHIFT_DURATION),
|
||||
frequency=FREQUENCY_WEEKLY,
|
||||
interval=2,
|
||||
by_day=public_api_constants.DEMO_ON_CALL_SHIFT_BY_DAY,
|
||||
source=SOURCE_TERRAFORM,
|
||||
)
|
||||
)
|
||||
|
||||
on_call_shift_2.users.add(user)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances'),
|
||||
('schedules', '0002_squashed_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -4,6 +4,7 @@ from .calculcate_escalation_finish_time import calculate_escalation_finish_time
|
|||
from .call_ack_url import call_ack_url # noqa: F401
|
||||
from .check_escalation_finished import check_escalation_finished_task # noqa: F401
|
||||
from .create_contact_points_for_datasource import create_contact_points_for_datasource # noqa: F401
|
||||
from .create_contact_points_for_datasource import schedule_create_contact_points_for_datasource # noqa: F401
|
||||
from .custom_button_result import custom_button_result # noqa: F401
|
||||
from .delete_alert_group import delete_alert_group # noqa: F401
|
||||
from .distribute_alert import distribute_alert # noqa: F401
|
||||
|
|
|
|||
|
|
@ -1,9 +1,32 @@
|
|||
import logging
|
||||
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.apps import apps
|
||||
from django.core.cache import cache
|
||||
from rest_framework import status
|
||||
|
||||
from apps.grafana_plugin.helpers import GrafanaAPIClient
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id):
|
||||
CACHE_KEY_PREFIX = "create_contact_points_for_datasource"
|
||||
return f"{CACHE_KEY_PREFIX}_{alert_receive_channel_id}"
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task
|
||||
def schedule_create_contact_points_for_datasource(alert_receive_channel_id, datasource_list):
|
||||
CACHE_LIFETIME = 600
|
||||
START_TASK_DELAY = 3
|
||||
task = create_contact_points_for_datasource.apply_async(
|
||||
args=[alert_receive_channel_id, datasource_list], countdown=START_TASK_DELAY
|
||||
)
|
||||
cache_key = get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id)
|
||||
cache.set(cache_key, task.id, timeout=CACHE_LIFETIME)
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=10)
|
||||
def create_contact_points_for_datasource(alert_receive_channel_id, datasource_list):
|
||||
|
|
@ -11,6 +34,11 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
Try to create contact points for other datasource.
|
||||
Restart task for datasource, for which contact point was not created.
|
||||
"""
|
||||
cache_key = get_cache_key_create_contact_points_for_datasource(alert_receive_channel_id)
|
||||
cached_task_id = cache.get(cache_key)
|
||||
current_task_id = create_contact_points_for_datasource.request.id
|
||||
if cached_task_id is not None and current_task_id != cached_task_id:
|
||||
return
|
||||
|
||||
AlertReceiveChannel = apps.get_model("alerts", "AlertReceiveChannel")
|
||||
|
||||
|
|
@ -21,7 +49,7 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
api_token=alert_receive_channel.organization.api_token,
|
||||
)
|
||||
# list of datasource for which contact point creation was failed
|
||||
datasource_to_create = []
|
||||
datasources_to_create = []
|
||||
for datasource in datasource_list:
|
||||
contact_point = None
|
||||
config, response_info = client.get_alerting_config(datasource["id"])
|
||||
|
|
@ -29,16 +57,22 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
|
|||
if response_info.get("status_code") == status.HTTP_404_NOT_FOUND:
|
||||
client.get_alertmanager_status_with_config(datasource["id"])
|
||||
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
|
||||
elif response_info.get("status_code") == status.HTTP_400_BAD_REQUEST:
|
||||
logger.warning(
|
||||
f"Failed to create contact point for integration {alert_receive_channel_id}, "
|
||||
f"datasource info: {datasource}; response: {response_info}"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
|
||||
if contact_point is None:
|
||||
# Failed to create contact point duo to getting wrong alerting config.
|
||||
# Add datasource to list and retry to create contact point for it again
|
||||
datasource_to_create.append(datasource)
|
||||
datasources_to_create.append(datasource)
|
||||
|
||||
# if some contact points were not created, restart task for them
|
||||
if datasource_to_create:
|
||||
create_contact_points_for_datasource.apply_async((alert_receive_channel_id, datasource_to_create), countdown=5)
|
||||
if datasources_to_create:
|
||||
schedule_create_contact_points_for_datasource(alert_receive_channel_id, datasources_to_create)
|
||||
else:
|
||||
alert_receive_channel.is_finished_alerting_setup = True
|
||||
alert_receive_channel.save(update_fields=["is_finished_alerting_setup"])
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 13:02
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
|
||||
from apps.auth_token import constants
|
||||
from apps.auth_token import crypto
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
Organization = apps.get_model('user_management', 'Organization')
|
||||
ApiAuthToken = apps.get_model('auth_token', 'ApiAuthToken')
|
||||
|
||||
organization = Organization.objects.get(public_primary_key=public_api_constants.DEMO_ORGANIZATION_ID)
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
token_string = crypto.generate_token_string()
|
||||
digest = crypto.hash_token_string(token_string)
|
||||
|
||||
ApiAuthToken.objects.get_or_create(
|
||||
name=public_api_constants.DEMO_AUTH_TOKEN,
|
||||
user=user,
|
||||
organization=organization,
|
||||
defaults=dict(token_key=token_string[:constants.TOKEN_KEY_LENGTH], digest=digest)
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth_token', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:45
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from django.utils import timezone
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
STEP_WAIT = 0
|
||||
STEP_NOTIFY = 1
|
||||
NOTIFY_BY_SMS = 1
|
||||
NOTIFY_BY_PHONE = 2
|
||||
FIVE_MINUTES = timezone.timedelta(minutes=5)
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
User = apps.get_model('user_management', 'User')
|
||||
UserNotificationPolicy = apps.get_model("base", "UserNotificationPolicy")
|
||||
|
||||
user = User.objects.get(public_primary_key=public_api_constants.DEMO_USER_ID)
|
||||
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_1,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
notify_by=NOTIFY_BY_SMS,
|
||||
step=STEP_NOTIFY,
|
||||
order=0,
|
||||
)
|
||||
)
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_2,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
step=STEP_WAIT,
|
||||
wait_delay=FIVE_MINUTES,
|
||||
order=1,
|
||||
)
|
||||
)
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_3,
|
||||
defaults=dict(
|
||||
important=False,
|
||||
user=user,
|
||||
step=STEP_NOTIFY,
|
||||
notify_by=NOTIFY_BY_PHONE,
|
||||
order=2,
|
||||
)
|
||||
)
|
||||
|
||||
UserNotificationPolicy.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_PERSONAL_NOTIFICATION_ID_4,
|
||||
defaults=dict(
|
||||
important=True,
|
||||
user=user,
|
||||
notify_by=NOTIFY_BY_PHONE,
|
||||
order=0,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0002_squashed_initial'),
|
||||
('user_management', '0002_squashed_create_demo_token_instances')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:51
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from apps.public_api import constants as public_api_constants
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
SlackUserIdentity = apps.get_model('slack', 'SlackUserIdentity')
|
||||
SlackTeamIdentity = apps.get_model('slack', 'SlackTeamIdentity')
|
||||
SlackChannel = apps.get_model('slack', 'SlackChannel')
|
||||
SlackUserGroup = apps.get_model("slack", "SlackUserGroup")
|
||||
|
||||
slack_team_identity, _ = SlackTeamIdentity.objects.get_or_create(
|
||||
slack_id=public_api_constants.DEMO_SLACK_TEAM_ID,
|
||||
)
|
||||
SlackUserIdentity.objects.get_or_create(
|
||||
slack_id=public_api_constants.DEMO_SLACK_USER_ID,
|
||||
slack_team_identity=slack_team_identity,
|
||||
)
|
||||
|
||||
SlackChannel.objects.get_or_create(
|
||||
name=public_api_constants.DEMO_SLACK_CHANNEL_NAME,
|
||||
slack_id=public_api_constants.DEMO_SLACK_CHANNEL_SLACK_ID,
|
||||
slack_team_identity=slack_team_identity,
|
||||
)
|
||||
|
||||
SlackUserGroup.objects.get_or_create(
|
||||
slack_team_identity=slack_team_identity,
|
||||
slack_id=public_api_constants.DEMO_SLACK_USER_GROUP_SLACK_ID,
|
||||
public_primary_key=public_api_constants.DEMO_SLACK_USER_GROUP_ID,
|
||||
name=public_api_constants.DEMO_SLACK_USER_GROUP_NAME,
|
||||
handle=public_api_constants.DEMO_SLACK_USER_GROUP_HANDLE,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('slack', '0002_squashed_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-04 10:46
|
||||
|
||||
import sys
|
||||
from django.db import migrations
|
||||
from apps.public_api import constants as public_api_constants
|
||||
from common.constants.role import Role
|
||||
|
||||
|
||||
def create_demo_token_instances(apps, schema_editor):
|
||||
if not (len(sys.argv) > 1 and sys.argv[1] == 'test'):
|
||||
SlackUserIdentity = apps.get_model('slack', 'SlackUserIdentity')
|
||||
SlackTeamIdentity = apps.get_model('slack', 'SlackTeamIdentity')
|
||||
User = apps.get_model('user_management', 'User')
|
||||
Organization = apps.get_model('user_management', 'Organization')
|
||||
|
||||
slack_team_identity = SlackTeamIdentity.objects.get(slack_id=public_api_constants.DEMO_SLACK_TEAM_ID)
|
||||
slack_user_identity = SlackUserIdentity.objects.get(
|
||||
slack_id=public_api_constants.DEMO_SLACK_USER_ID,
|
||||
slack_team_identity=slack_team_identity,
|
||||
)
|
||||
|
||||
organization, _ = Organization.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_ORGANIZATION_ID,
|
||||
defaults=dict(
|
||||
slack_team_identity=slack_team_identity,
|
||||
org_id=0, stack_id=0,
|
||||
)
|
||||
)
|
||||
User.objects.get_or_create(
|
||||
public_primary_key=public_api_constants.DEMO_USER_ID,
|
||||
defaults=dict(
|
||||
username=public_api_constants.DEMO_USER_USERNAME,
|
||||
email=public_api_constants.DEMO_USER_EMAIL,
|
||||
organization=organization,
|
||||
role=Role.ADMIN,
|
||||
slack_user_identity=slack_user_identity,
|
||||
user_id=0,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0001_squashed_initial'),
|
||||
('slack', '0003_squashed_create_demo_token_instances'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_demo_token_instances, migrations.RunPython.noop)
|
||||
]
|
||||
|
|
@ -9,7 +9,6 @@ celery==4.3.0
|
|||
redis==3.2.0
|
||||
django-celery-results==1.0.4
|
||||
humanize==0.5.1
|
||||
django-mysql==2.4.1
|
||||
uwsgi==2.0.20
|
||||
django-cors-headers==3.7.0
|
||||
django-debug-toolbar==3.2.1
|
||||
|
|
@ -39,3 +38,5 @@ django-rest-polymorphic==0.1.9
|
|||
pre-commit==2.15.0
|
||||
https://github.com/iskhakov/django-push-notifications/archive/refs/tags/2.0.0-hotfix-4.tar.gz
|
||||
django-mirage-field==1.3.0
|
||||
django-mysql==4.6.0
|
||||
PyMySQL==1.0.2
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const RootWithLoader = observer((props: AppRootProps) => {
|
|||
} else if (store.isUserAnonymous) {
|
||||
text = '😞 Unfortunately Grafana OnCall is available for authorized users only, please sign in to proceed.';
|
||||
} else if (store.retrySync) {
|
||||
text = `🚫 OnCall took too many tries to synchronize`;
|
||||
text = `🚫 OnCall took too many tries to synchronize... Are background workers up and running?`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -189,7 +189,9 @@ export const PluginConfigPage = (props: Props) => {
|
|||
|
||||
if (counter >= 5) {
|
||||
clearInterval(interval);
|
||||
setPluginStatusMessage(`OnCall took too many tries to synchronize.`);
|
||||
setPluginStatusMessage(
|
||||
`OnCall took too many tries to synchronize. Did you launch Celery workers? Background workers should perform synchronization, not web server.`
|
||||
);
|
||||
setRetrySync(true);
|
||||
setPluginStatusOk(false);
|
||||
setPluginConfigLoading(false);
|
||||
|
|
@ -212,14 +214,14 @@ export const PluginConfigPage = (props: Props) => {
|
|||
<Legend>Configure Grafana OnCall</Legend>
|
||||
{pluginStatusOk && (
|
||||
<p>
|
||||
Configuration was sucessfully created. Now you can find Grafana OnCall on right toolbar.{' '}
|
||||
Plugin and the backend are connected! Check Grafana OnCall 👈👈👈{' '}
|
||||
<img alt="Grafana OnCall Logo" src={logo} width={18} />
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSelfHostedInstall ? (
|
||||
<div>
|
||||
<p>{'Plugin <-> backend connection status'}</p>
|
||||
<p>{'Plugin <-> backend connection status:'}</p>
|
||||
<pre>
|
||||
<Text type="link">{pluginStatusMessage}</Text>
|
||||
</pre>
|
||||
|
|
@ -242,12 +244,28 @@ export const PluginConfigPage = (props: Props) => {
|
|||
<React.Fragment>
|
||||
<Legend>Configure Grafana OnCall</Legend>
|
||||
<p>This page will help you to connect OnCall backend and OnCall Grafana plugin 👋</p>
|
||||
<p>1. Grafana OnCall is a Grafana plugin and backend. Run backend</p>
|
||||
<p>
|
||||
<Text type="secondary">
|
||||
- Talk to the OnCall team in the #grafana-oncall channel at{' '}
|
||||
<a href="https://slack.grafana.com/">
|
||||
<Text type="link">Slack</Text>
|
||||
</a>
|
||||
<br />- Ask questions at{' '}
|
||||
<a href="https://github.com/grafana/oncall/discussions/categories/q-a">
|
||||
<Text type="link">GitHub Discussions</Text>
|
||||
</a>{' '}
|
||||
or file bugs at{' '}
|
||||
<a href="https://github.com/grafana/oncall/issues">
|
||||
<Text type="link">GitHub Issues</Text>
|
||||
</a>
|
||||
</Text>
|
||||
</p>
|
||||
<p>1. Launch backend</p>
|
||||
<VerticalGroup>
|
||||
<Text type="secondary">
|
||||
Run production backend using{' '}
|
||||
<a href="http://oncall-stub.com">
|
||||
<Text type="link">this instructions at our GitHub </Text>
|
||||
<a href="https://github.com/grafana/oncall#production-setup">
|
||||
<Text type="link">this instructions at our GitHub</Text>
|
||||
</a>
|
||||
,
|
||||
</Text>
|
||||
|
|
@ -267,30 +285,9 @@ export const PluginConfigPage = (props: Props) => {
|
|||
</Text>
|
||||
</pre>
|
||||
</VerticalGroup>
|
||||
<Alert
|
||||
severity="info"
|
||||
/* @ts-ignore */
|
||||
title={
|
||||
<>
|
||||
<Text type="secondary">
|
||||
Need help?
|
||||
<br />
|
||||
1. Talk to the developers in the #grafana-oncall channel at{' '}
|
||||
<a href="http://oncall-stub.com">
|
||||
<Text type="link">Slack</Text>
|
||||
</a>
|
||||
<br />
|
||||
2. Search for issues or create a new one in the{' '}
|
||||
<a href="http://oncall-stub.com">
|
||||
<Text type="link">GitHub</Text>
|
||||
</a>
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<p>2. Conect the backend and the plugin </p>
|
||||
<p>{'Plugin <-> backend connection status'}</p>
|
||||
<p>{'Plugin <-> backend connection status:'}</p>
|
||||
<pre>
|
||||
<Text type="link">{pluginStatusMessage}</Text>
|
||||
</pre>
|
||||
|
|
@ -301,7 +298,7 @@ Seek for such a line: “Your invite token: <<LONG TOKEN>> , use it in the Graf
|
|||
>
|
||||
<>
|
||||
<Input id="onCallInvitationToken" onChange={handleInvitationTokenChange} />
|
||||
<a href="http://oncall-stub.com">
|
||||
<a href="https://github.com/grafana/oncall/blob/dev/DEVELOPER.md#frontend-setup">
|
||||
<Text size="small" type="link">
|
||||
How to re-issue the invite token?
|
||||
</Text>
|
||||
|
|
@ -311,7 +308,7 @@ Seek for such a line: “Your invite token: <<LONG TOKEN>> , use it in the Graf
|
|||
|
||||
<Field
|
||||
label="OnCall backend URL"
|
||||
description="It should be rechable from Grafana. Possible options:
|
||||
description="It should be reachable from Grafana. Possible options:
|
||||
http://host.docker.internal:8000 (if you run backend in the docker locally)
|
||||
http://localhost:8000
|
||||
..."
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 2.4 KiB |
|
|
@ -68,7 +68,7 @@ export class AlertGroupStore extends BaseStore {
|
|||
constructor(rootStore: RootStore) {
|
||||
super(rootStore);
|
||||
|
||||
this.path = '/alertgroups1/';
|
||||
this.path = '/alertgroups/';
|
||||
}
|
||||
|
||||
async attachAlert(pk: Alert['pk'], rootPk: Alert['pk']) {
|
||||
|
|
|
|||
|
|
@ -302,12 +302,11 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
|
|||
(results && results.some((alert: AlertType) => alert.undoAction)) || Object.keys(affectedRows).length
|
||||
);
|
||||
|
||||
console.log('results', results);
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
{this.renderBulkActions()}
|
||||
<GTable
|
||||
emptyText={results ? 'No alert groups found' : 'Something went wrong'}
|
||||
emptyText={alertGroupsLoading ? 'Loading...' : 'No alert groups found'}
|
||||
loading={alertGroupsLoading}
|
||||
className={cx('incidents-table')}
|
||||
rowSelection={{ selectedRowKeys: selectedIncidentIds, onChange: this.handleSelectedIncidentIdsChange }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import plugin from '../../package.json'; // eslint-disable-line
|
||||
|
||||
export const APP_TITLE = 'Grafana OnCall';
|
||||
export const APP_SUBTITLE = `Incident Response powered by Amixr (${plugin?.version})`;
|
||||
export const APP_SUBTITLE = `Incident Response (${plugin?.version})`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue