Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build custom alert message #3137

Merged
merged 25 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions client/app/pages/alert/alert.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,39 @@
</div>
</div>
</div>
<div class="form-group" ng-show="$ctrl.selectedQuery && $ctrl.showExtendedOptions">
<label>Custom subject</label>
<input type="string" class="form-control" ng-model="$ctrl.alert.options.subject" ng-disabled="!$ctrl.canEdit">
</div>
<div ng-show="$ctrl.selectedQuery && $ctrl.showExtendedOptions">
<div class="form-group" ng-show="$ctrl.selectedQuery">
<label>Description template</label>
<i class="fa fa-question-circle" uib-tooltip="{{$ctrl.alertTemplate.helpMessage}}"></i>
<div class="row bg-white p-b-5" ng-if="$ctrl.canEdit" resizable r-directions="['bottom']" r-height="300" style="min-height:100px;">
<div ui-ace="$ctrl.alertTemplate.editorOptions" ng-model="$ctrl.alert.options.template"></div>
</div>
</div>
<div class="form-group" ng-if="$ctrl.canEdit">
<button class="btn btn-default" ng-click="$ctrl.preview()">Preview</button>
<label for="show-as-html">Show As HTML</label>
<input type="checkbox" name="show-as-html" ng-model="$ctrl.showAsHTML">
</div>
<div class="panel panel-default" ng-if="$ctrl.alert.preview">
<div class="panel-heading">
<label for="hide-preview">Hide Preview</label>
<input type="checkbox" name="hide-preview" ng-model="$ctrl.hidePreview">
</div>
<div class="panel-body" ng-if="$ctrl.hidePreview == false">
<div ng-if="!$ctrl.showAsHTML">
<div ng-bind-html="$ctrl.alert.preview"></div>
</div>
<div ng-if="$ctrl.showAsHTML">
<div ng-bind-html="$ctrl.alert.previewHTML"></div>
</div>
</div>
<div class="panel-footer"></div>
</div>
</div>

<div class="form-group" ng-if="$ctrl.canEdit">
<button class="btn btn-primary" ng-disabled="!alertForm.$valid" ng-click="$ctrl.saveChanges()">Save</button>
Expand Down
26 changes: 25 additions & 1 deletion client/app/pages/alert/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { template as templateBuilder } from 'lodash';
import notification from '@/services/notification';
import Modal from 'antd/lib/modal';
import template from './alert.html';
import AlertTemplate from '@/services/alert-template';
import { clientConfig } from '@/services/auth';
import navigateTo from '@/services/navigateTo';

function AlertCtrl($scope, $routeParams, $location, $sce, currentUser, Query, Events, Alert) {
function AlertCtrl($scope, $routeParams, $location, $sce, $sanitize, currentUser, Query, Events, Alert) {
this.alertId = $routeParams.alertId;
this.hidePreview = false;
this.alertTemplate = new AlertTemplate();
this.showExtendedOptions = clientConfig.extendedAlertOptions;

if (this.alertId === 'new') {
Events.record('view', 'page', 'alerts/new');
Expand Down Expand Up @@ -62,6 +67,9 @@ function AlertCtrl($scope, $routeParams, $location, $sce, currentUser, Query, Ev
if (this.alert.rearm === '' || this.alert.rearm === 0) {
this.alert.rearm = null;
}
if (this.alert.template === undefined || this.alert.template === '') {
this.alert.template = null;
}
this.alert.$save(
(alert) => {
notification.success('Saved.');
Expand All @@ -75,6 +83,22 @@ function AlertCtrl($scope, $routeParams, $location, $sce, currentUser, Query, Ev
);
};

this.preview = () => {
const notifyError = () => notification.error('Unable to render description. please confirm your template.');
try {
const result = this.alertTemplate.render(this.alert, this.queryResult.query_result.data);
this.alert.preview = $sce.trustAsHtml(result.escaped);
this.alert.previewHTML = $sce.trustAsHtml($sanitize(result.raw));
if (!result.raw) {
notifyError();
}
} catch (e) {
notifyError();
this.alert.preview = e.message;
this.alert.previewHTML = e.message;
}
};

this.delete = () => {
const doDelete = () => {
this.alert.$delete(() => {
Expand Down
41 changes: 41 additions & 0 deletions client/app/services/alert-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// import { $http } from '@/services/ng';
import Mustache from 'mustache';

export default class AlertTemplate {
render(alert, queryResult) {
const view = {
state: alert.state,
rows: queryResult.rows,
cols: queryResult.columns,
};
const result = Mustache.render(alert.options.template, view);
const escaped = result
.replace(/"/g, '&quot;')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n|\r/g, '<br>');

return { escaped, raw: result };
}

constructor() {
this.helpMessage = `using template engine "mustache".
you can build message with latest query result.
variable name "rows" is assigned as result rows. "cols" as result columns, "state" as alert state.`;

this.editorOptions = {
useWrapMode: true,
showPrintMargin: false,
advanced: {
behavioursEnabled: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
autoScrollEditorIntoView: true,
},
onLoad(editor) {
editor.$blockScrolling = Infinity;
},
};
}
}
10 changes: 7 additions & 3 deletions redash/destinations/chatwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ def notify(self, alert, query, user, new_state, app, host, options):

alert_url = '{host}/alerts/{alert_id}'.format(host=host, alert_id=alert.id)
query_url = '{host}/queries/{query_id}'.format(host=host, query_id=query.id)

message_template = options.get('message_template', ChatWork.ALERTS_DEFAULT_MESSAGE_TEMPLATE)

message = message_template.replace('\\n', '\n').format(
message = ''
if alert.custom_subject:
message = alert.custom_subject + '\n'
message += message_template.replace('\\n', '\n').format(
alert_name=alert.name, new_state=new_state.upper(),
alert_url=alert_url,
query_url=query_url)

if alert.template:
description = alert.render_template()
message = message + "\n" + description
headers = {'X-ChatWorkToken': options.get('api_token')}
payload = {'body': message}

Expand Down
15 changes: 12 additions & 3 deletions redash/destinations/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,30 @@ def notify(self, alert, query, user, new_state, app, host, options):
logging.warning("No emails given. Skipping send.")

html = """
Check <a href="{host}/alerts/{alert_id}">alert</a> / check <a href="{host}/queries/{query_id}">query</a>.
Check <a href="{host}/alerts/{alert_id}">alert</a> / check <a href="{host}/queries/{query_id}">query</a> </br>.
""".format(host=host, alert_id=alert.id, query_id=query.id)
if alert.template:
description = alert.render_template()
html += "<br>" + description
logging.debug("Notifying: %s", recipients)

try:
alert_name = alert.name.encode('utf-8', 'ignore')
state = new_state.upper()
subject_template = options.get('subject_template', settings.ALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE)
if alert.custom_subject:
subject = alert.custom_subject
else:
subject_template = options.get('subject_template', settings.ALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE)
subject = subject_template.format(alert_name=alert_name, state=state)

message = Message(
recipients=recipients,
subject=subject_template.format(alert_name=alert_name, state=state),
subject=subject,
html=html
)
mail.send(message)
except Exception:
logging.exception("Mail send error.")


register(Email)
18 changes: 17 additions & 1 deletion redash/destinations/hangoutschat.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ def notify(self, alert, query, user, new_state, app, host, options):
else:
message = "Unable to determine status. Check Query and Alert configuration."

if alert.custom_subject:
title = alert.custom_subject
else:
title = alert.name

data = {
"cards": [
{
"header": {
"title": alert.name
"title": title
},
"sections": [
{
Expand All @@ -65,6 +70,17 @@ def notify(self, alert, query, user, new_state, app, host, options):
]
}

if alert.template:
data["cards"][0]["sections"].append({
"widgets": [
{
"textParagraph": {
"text": alert.render_template()
}
}
]
})

if options.get("icon_url"):
data["cards"][0]["header"]["imageUrl"] = options.get("icon_url")

Expand Down
9 changes: 9 additions & 0 deletions redash/destinations/mattermost.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,16 @@ def notify(self, alert, query, user, new_state, app, host, options):
else:
text = "####" + alert.name + " went back to normal"

if alert.custom_subject:
text += '\n' + alert.custom_subject
payload = {'text': text}

if alert.template:
payload['attachments'] = [{'fields': [{
"title": "Description",
"value": alert.render_template()
}]}]

if options.get('username'): payload['username'] = options.get('username')
if options.get('icon_url'): payload['icon_url'] = options.get('icon_url')
if options.get('channel'): payload['channel'] = options.get('channel')
Expand Down
7 changes: 6 additions & 1 deletion redash/destinations/pagerduty.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ def notify(self, alert, query, user, new_state, app, host, options):

default_desc = self.DESCRIPTION_STR.format(query_id=query.id, query_name=query.name)

if options.get('description'):
if alert.custom_subject:
default_desc = alert.custom_subject
elif options.get('description'):
default_desc = options.get('description')

incident_key = self.KEY_STRING.format(alert_id=alert.id, query_id=query.id)
Expand All @@ -58,6 +60,9 @@ def notify(self, alert, query, user, new_state, app, host, options):
}
}

if alert.template:
data['payload']['custom_details'] = alert.render_template()

if new_state == 'triggered':
data['event_action'] = 'trigger'
elif new_state == "unknown":
Expand Down
11 changes: 10 additions & 1 deletion redash/destinations/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,17 @@ def notify(self, alert, query, user, new_state, app, host, options):
"short": True
}
]
if alert.template:
description = alert.render_template()
fields.append({
"title": "Description",
"value": description
})
if new_state == "triggered":
text = alert.name + " just triggered"
if alert.custom_subject:
text = alert.custom_subject
else:
text = alert.name + " just triggered"
color = "#c0392b"
else:
text = alert.name + " went back to normal"
Expand Down
6 changes: 5 additions & 1 deletion redash/destinations/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ def notify(self, alert, query, user, new_state, app, host, options):
data = {
'event': 'alert_state_change',
'alert': serialize_alert(alert, full=False),
'url_base': host
'url_base': host,
}

data['alert']['description'] = alert.render_template()
data['alert']['title'] = alert.custom_subject

headers = {'Content-Type': 'application/json'}
auth = HTTPBasicAuth(options.get('username'), options.get('password')) if options.get('username') else None
resp = requests.post(options.get('url'), data=json_dumps(data), auth=auth, headers=headers, timeout=5.0)
Expand Down
3 changes: 2 additions & 1 deletion redash/handlers/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
require_fields)
from redash.permissions import (require_access, require_admin_or_owner,
require_permission, view_only)
from redash.utils import json_dumps


class AlertResource(BaseResource):
Expand Down Expand Up @@ -60,7 +61,7 @@ def post(self):
query_rel=query,
user=self.current_user,
rearm=req.get('rearm'),
options=req['options']
options=req['options'],
)

models.db.session.add(alert)
Expand Down
1 change: 1 addition & 0 deletions redash/handlers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def client_config():
'showPermissionsControl': current_org.get_setting("feature_show_permissions_control"),
'allowCustomJSVisualizations': settings.FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS,
'autoPublishNamedQueries': settings.FEATURE_AUTO_PUBLISH_NAMED_QUERIES,
'extendedAlertOptions': settings.FEATURE_EXTENDED_ALERT_OPTIONS,
'mailSettingsMissing': not settings.email_server_is_configured(),
'dashboardRefreshIntervals': settings.DASHBOARD_REFRESH_INTERVALS,
'queryRefreshIntervals': settings.QUERY_REFRESH_INTERVALS,
Expand Down
17 changes: 16 additions & 1 deletion redash/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from redash.metrics import database # noqa: F401
from redash.query_runner import (get_configuration_schema_for_query_runner_type,
get_query_runner, TYPE_BOOLEAN, TYPE_DATE, TYPE_DATETIME)
from redash.utils import generate_token, json_dumps, json_loads
from redash.utils import generate_token, json_dumps, json_loads, mustache_render
from redash.utils.configuration import ConfigurationContainer
from redash.models.parameterized_query import ParameterizedQuery

Expand Down Expand Up @@ -796,6 +796,21 @@ def evaluate(self):
def subscribers(self):
return User.query.join(AlertSubscription).filter(AlertSubscription.alert == self)

def render_template(self):
if not self.template:
return ''
data = json_loads(self.query_rel.latest_query_data.data)
context = {'rows': data['rows'], 'cols': data['columns'], 'state': self.state}
return mustache_render(self.template, context)

@property
def template(self):
return self.options.get('template', '')

@property
def custom_subject(self):
return self.options.get('subject', '')

@property
def groups(self):
return self.query_rel.groups
Expand Down
1 change: 1 addition & 0 deletions redash/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ def email_server_is_configured():
FEATURE_SHOW_QUERY_RESULTS_COUNT = parse_boolean(os.environ.get("REDASH_FEATURE_SHOW_QUERY_RESULTS_COUNT", "true"))
FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS = parse_boolean(os.environ.get("REDASH_FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS", "false"))
FEATURE_AUTO_PUBLISH_NAMED_QUERIES = parse_boolean(os.environ.get("REDASH_FEATURE_AUTO_PUBLISH_NAMED_QUERIES", "true"))
FEATURE_EXTENDED_ALERT_OPTIONS = parse_boolean(os.environ.get("REDASH_FEATURE_EXTENDED_ALERT_OPTIONS", "false"))

# BigQuery
BIGQUERY_HTTP_TIMEOUT = int(os.environ.get("REDASH_BIGQUERY_HTTP_TIMEOUT", "600"))
Expand Down
1 change: 1 addition & 0 deletions redash/tasks/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def check_alerts_for_query(query_id):
query = models.Query.query.get(query_id)

for alert in query.alerts:
logger.info("Checking alert (%d) of query %d.", alert.id, query_id)
new_state = alert.evaluate()

if should_notify(alert, new_state):
Expand Down