mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Adding Evaluate button for alerts to test them (#7032)
This commit is contained in:
@@ -16,6 +16,7 @@ import MenuButton from "./components/MenuButton";
|
||||
import AlertView from "./AlertView";
|
||||
import AlertEdit from "./AlertEdit";
|
||||
import AlertNew from "./AlertNew";
|
||||
import notifications from "@/services/notifications";
|
||||
|
||||
const MODES = {
|
||||
NEW: 0,
|
||||
@@ -178,6 +179,17 @@ class Alert extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
evaluate = () => {
|
||||
const { alert } = this.state;
|
||||
return AlertService.evaluate(alert)
|
||||
.then(() => {
|
||||
notification.success("Alert evaluated. Refresh page for updated status.");
|
||||
})
|
||||
.catch(() => {
|
||||
notifications.error("Failed to evaluate alert.");
|
||||
});
|
||||
};
|
||||
|
||||
mute = () => {
|
||||
const { alert } = this.state;
|
||||
return AlertService.mute(alert)
|
||||
@@ -224,7 +236,14 @@ class Alert extends React.Component {
|
||||
const { queryResult, mode, canEdit, pendingRearm } = this.state;
|
||||
|
||||
const menuButton = (
|
||||
<MenuButton doDelete={this.delete} muted={muted} mute={this.mute} unmute={this.unmute} canEdit={canEdit} />
|
||||
<MenuButton
|
||||
doDelete={this.delete}
|
||||
muted={muted}
|
||||
mute={this.mute}
|
||||
unmute={this.unmute}
|
||||
canEdit={canEdit}
|
||||
evaluate={this.evaluate}
|
||||
/>
|
||||
);
|
||||
|
||||
const commonProps = {
|
||||
|
||||
@@ -11,7 +11,7 @@ import LoadingOutlinedIcon from "@ant-design/icons/LoadingOutlined";
|
||||
import EllipsisOutlinedIcon from "@ant-design/icons/EllipsisOutlined";
|
||||
import PlainButton from "@/components/PlainButton";
|
||||
|
||||
export default function MenuButton({ doDelete, canEdit, mute, unmute, muted }) {
|
||||
export default function MenuButton({ doDelete, canEdit, mute, unmute, evaluate, muted }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const execute = useCallback(action => {
|
||||
@@ -55,6 +55,9 @@ export default function MenuButton({ doDelete, canEdit, mute, unmute, muted }) {
|
||||
<Menu.Item>
|
||||
<PlainButton onClick={confirmDelete}>Delete</PlainButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<PlainButton onClick={() => execute(evaluate)}>Evaluate</PlainButton>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}>
|
||||
<Button aria-label="More actions">
|
||||
@@ -69,6 +72,7 @@ MenuButton.propTypes = {
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
mute: PropTypes.func.isRequired,
|
||||
unmute: PropTypes.func.isRequired,
|
||||
evaluate: PropTypes.func.isRequired,
|
||||
muted: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const Alert = {
|
||||
delete: data => axios.delete(`api/alerts/${data.id}`),
|
||||
mute: data => axios.post(`api/alerts/${data.id}/mute`),
|
||||
unmute: data => axios.delete(`api/alerts/${data.id}/mute`),
|
||||
evaluate: data => axios.post(`api/alerts/${data.id}/eval`),
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from flask import request
|
||||
from funcy import project
|
||||
|
||||
from redash import models
|
||||
from redash import models, utils
|
||||
from redash.handlers.base import (
|
||||
BaseResource,
|
||||
get_object_or_404,
|
||||
@@ -14,6 +14,10 @@ from redash.permissions import (
|
||||
view_only,
|
||||
)
|
||||
from redash.serializers import serialize_alert
|
||||
from redash.tasks.alerts import (
|
||||
notify_subscriptions,
|
||||
should_notify,
|
||||
)
|
||||
|
||||
|
||||
class AlertResource(BaseResource):
|
||||
@@ -43,6 +47,21 @@ class AlertResource(BaseResource):
|
||||
models.db.session.commit()
|
||||
|
||||
|
||||
class AlertEvaluateResource(BaseResource):
|
||||
def post(self, alert_id):
|
||||
alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)
|
||||
require_admin_or_owner(alert.user.id)
|
||||
|
||||
new_state = alert.evaluate()
|
||||
if should_notify(alert, new_state):
|
||||
alert.state = new_state
|
||||
alert.last_triggered_at = utils.utcnow()
|
||||
models.db.session.commit()
|
||||
|
||||
notify_subscriptions(alert, new_state, {})
|
||||
self.record_event({"action": "evaluate", "object_id": alert.id, "object_type": "alert"})
|
||||
|
||||
|
||||
class AlertMuteResource(BaseResource):
|
||||
def post(self, alert_id):
|
||||
alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)
|
||||
|
||||
@@ -3,6 +3,7 @@ from flask_restful import Api
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from redash.handlers.alerts import (
|
||||
AlertEvaluateResource,
|
||||
AlertListResource,
|
||||
AlertMuteResource,
|
||||
AlertResource,
|
||||
@@ -117,6 +118,7 @@ def json_representation(data, code, headers=None):
|
||||
|
||||
api.add_org_resource(AlertResource, "/api/alerts/<alert_id>", endpoint="alert")
|
||||
api.add_org_resource(AlertMuteResource, "/api/alerts/<alert_id>/mute", endpoint="alert_mute")
|
||||
api.add_org_resource(AlertEvaluateResource, "/api/alerts/<alert_id>/eval", endpoint="alert_eval")
|
||||
api.add_org_resource(
|
||||
AlertSubscriptionListResource,
|
||||
"/api/alerts/<alert_id>/subscriptions",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import datetime
|
||||
|
||||
from mock import patch
|
||||
|
||||
from redash.models import Alert, AlertSubscription, db
|
||||
from redash.utils import utcnow
|
||||
from tests import BaseTestCase
|
||||
|
||||
|
||||
@@ -39,6 +44,26 @@ class TestAlertResourcePost(BaseTestCase):
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
|
||||
|
||||
class TestAlertEvaluateResource(BaseTestCase):
|
||||
@patch("redash.handlers.alerts.notify_subscriptions")
|
||||
def test_evaluates_alert_and_notifies(self, mock_notify_subscriptions):
|
||||
query = self.factory.create_query(
|
||||
data_source=self.factory.create_data_source(group=self.factory.create_group())
|
||||
)
|
||||
retrieved_at = utcnow() - datetime.timedelta(days=1)
|
||||
query_result = self.factory.create_query_result(
|
||||
retrieved_at=retrieved_at,
|
||||
query_text=query.query_text,
|
||||
query_hash=query.query_hash,
|
||||
)
|
||||
query.latest_query_data = query_result
|
||||
alert = self.factory.create_alert(query_rel=query)
|
||||
rv = self.make_request("post", "/api/alerts/{}/eval".format(alert.id))
|
||||
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
mock_notify_subscriptions.assert_called()
|
||||
|
||||
|
||||
class TestAlertResourceDelete(BaseTestCase):
|
||||
def test_removes_alert_and_subscriptions(self):
|
||||
subscription = self.factory.create_alert_subscription()
|
||||
|
||||
Reference in New Issue
Block a user