mirror of
https://github.com/getredash/redash.git
synced 2025-12-20 01:47:39 -05:00
Add min/max/first selector for alerts (#7076)
This commit is contained in:
@@ -65,6 +65,7 @@ export const Query = PropTypes.shape({
|
|||||||
|
|
||||||
export const AlertOptions = PropTypes.shape({
|
export const AlertOptions = PropTypes.shape({
|
||||||
column: PropTypes.string,
|
column: PropTypes.string,
|
||||||
|
selector: PropTypes.oneOf(["first", "min", "max"]),
|
||||||
op: PropTypes.oneOf([">", ">=", "<", "<=", "==", "!="]),
|
op: PropTypes.oneOf([">", ">=", "<", "<=", "==", "!="]),
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
custom_subject: PropTypes.string,
|
custom_subject: PropTypes.string,
|
||||||
@@ -83,6 +84,7 @@ export const Alert = PropTypes.shape({
|
|||||||
query: Query,
|
query: Query,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
column: PropTypes.string,
|
column: PropTypes.string,
|
||||||
|
selector: PropTypes.string,
|
||||||
op: PropTypes.string,
|
op: PropTypes.string,
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class Alert extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
alert: {
|
alert: {
|
||||||
options: {
|
options: {
|
||||||
|
selector: "first",
|
||||||
op: ">",
|
op: ">",
|
||||||
value: 1,
|
value: 1,
|
||||||
muted: false,
|
muted: false,
|
||||||
@@ -75,7 +76,7 @@ class Alert extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
const { alertId } = this.props;
|
const { alertId } = this.props;
|
||||||
AlertService.get({ id: alertId })
|
AlertService.get({ id: alertId })
|
||||||
.then(alert => {
|
.then((alert) => {
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
const canEdit = currentUser.canEdit(alert);
|
const canEdit = currentUser.canEdit(alert);
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ class Alert extends React.Component {
|
|||||||
this.onQuerySelected(alert.query);
|
this.onQuerySelected(alert.query);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.props.onError(error);
|
this.props.onError(error);
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ class Alert extends React.Component {
|
|||||||
alert.rearm = pendingRearm || null;
|
alert.rearm = pendingRearm || null;
|
||||||
|
|
||||||
return AlertService.save(alert)
|
return AlertService.save(alert)
|
||||||
.then(alert => {
|
.then((alert) => {
|
||||||
notification.success("Saved.");
|
notification.success("Saved.");
|
||||||
navigateTo(`alerts/${alert.id}`, true);
|
navigateTo(`alerts/${alert.id}`, true);
|
||||||
this.setState({ alert, mode: MODES.VIEW });
|
this.setState({ alert, mode: MODES.VIEW });
|
||||||
@@ -122,7 +123,7 @@ class Alert extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onQuerySelected = query => {
|
onQuerySelected = (query) => {
|
||||||
this.setState(({ alert }) => ({
|
this.setState(({ alert }) => ({
|
||||||
alert: Object.assign(alert, { query }),
|
alert: Object.assign(alert, { query }),
|
||||||
queryResult: null,
|
queryResult: null,
|
||||||
@@ -130,7 +131,7 @@ class Alert extends React.Component {
|
|||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
// get cached result for column names and values
|
// get cached result for column names and values
|
||||||
new QueryService(query).getQueryResultPromise().then(queryResult => {
|
new QueryService(query).getQueryResultPromise().then((queryResult) => {
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.setState({ queryResult });
|
this.setState({ queryResult });
|
||||||
let { column } = this.state.alert.options;
|
let { column } = this.state.alert.options;
|
||||||
@@ -146,18 +147,18 @@ class Alert extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onNameChange = name => {
|
onNameChange = (name) => {
|
||||||
const { alert } = this.state;
|
const { alert } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
alert: Object.assign(alert, { name }),
|
alert: Object.assign(alert, { name }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onRearmChange = pendingRearm => {
|
onRearmChange = (pendingRearm) => {
|
||||||
this.setState({ pendingRearm });
|
this.setState({ pendingRearm });
|
||||||
};
|
};
|
||||||
|
|
||||||
setAlertOptions = obj => {
|
setAlertOptions = (obj) => {
|
||||||
const { alert } = this.state;
|
const { alert } = this.state;
|
||||||
const options = { ...alert.options, ...obj };
|
const options = { ...alert.options, ...obj };
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -258,7 +259,7 @@ routes.register(
|
|||||||
routeWithUserSession({
|
routeWithUserSession({
|
||||||
path: "/alerts/new",
|
path: "/alerts/new",
|
||||||
title: "New Alert",
|
title: "New Alert",
|
||||||
render: pageProps => <Alert {...pageProps} mode={MODES.NEW} />,
|
render: (pageProps) => <Alert {...pageProps} mode={MODES.NEW} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
routes.register(
|
routes.register(
|
||||||
@@ -266,7 +267,7 @@ routes.register(
|
|||||||
routeWithUserSession({
|
routeWithUserSession({
|
||||||
path: "/alerts/:alertId",
|
path: "/alerts/:alertId",
|
||||||
title: "Alert",
|
title: "Alert",
|
||||||
render: pageProps => <Alert {...pageProps} mode={MODES.VIEW} />,
|
render: (pageProps) => <Alert {...pageProps} mode={MODES.VIEW} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
routes.register(
|
routes.register(
|
||||||
@@ -274,6 +275,6 @@ routes.register(
|
|||||||
routeWithUserSession({
|
routeWithUserSession({
|
||||||
path: "/alerts/:alertId/edit",
|
path: "/alerts/:alertId/edit",
|
||||||
title: "Alert",
|
title: "Alert",
|
||||||
render: pageProps => <Alert {...pageProps} mode={MODES.EDIT} />,
|
render: (pageProps) => <Alert {...pageProps} mode={MODES.EDIT} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,23 +54,70 @@ export default function Criteria({ columnNames, resultValues, alertOptions, onCh
|
|||||||
return null;
|
return null;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const columnHint = (
|
let columnHint;
|
||||||
<small className="alert-criteria-hint">
|
|
||||||
Top row value is <code className="p-0">{toString(columnValue) || "unknown"}</code>
|
if (alertOptions.selector === "first") {
|
||||||
</small>
|
columnHint = (
|
||||||
);
|
<small className="alert-criteria-hint">
|
||||||
|
Top row value is <code className="p-0">{toString(columnValue) || "unknown"}</code>
|
||||||
|
</small>
|
||||||
|
);
|
||||||
|
} else if (alertOptions.selector === "max") {
|
||||||
|
columnHint = (
|
||||||
|
<small className="alert-criteria-hint">
|
||||||
|
Max column value is{" "}
|
||||||
|
<code className="p-0">
|
||||||
|
{toString(Math.max(...resultValues.map((o) => o[alertOptions.column]))) || "unknown"}
|
||||||
|
</code>
|
||||||
|
</small>
|
||||||
|
);
|
||||||
|
} else if (alertOptions.selector === "min") {
|
||||||
|
columnHint = (
|
||||||
|
<small className="alert-criteria-hint">
|
||||||
|
Min column value is{" "}
|
||||||
|
<code className="p-0">
|
||||||
|
{toString(Math.min(...resultValues.map((o) => o[alertOptions.column]))) || "unknown"}
|
||||||
|
</code>
|
||||||
|
</small>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-test="Criteria">
|
<div data-test="Criteria">
|
||||||
|
<div className="input-title">
|
||||||
|
<span className="input-label">Selector</span>
|
||||||
|
{editMode ? (
|
||||||
|
<Select
|
||||||
|
value={alertOptions.selector}
|
||||||
|
onChange={(selector) => onChange({ selector })}
|
||||||
|
optionLabelProp="label"
|
||||||
|
dropdownMatchSelectWidth={false}
|
||||||
|
style={{ width: 80 }}
|
||||||
|
>
|
||||||
|
<Select.Option value="first" label="first">
|
||||||
|
first
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="min" label="min">
|
||||||
|
min
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="max" label="max">
|
||||||
|
max
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<DisabledInput minWidth={60}>{alertOptions.selector}</DisabledInput>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="input-title">
|
<div className="input-title">
|
||||||
<span className="input-label">Value column</span>
|
<span className="input-label">Value column</span>
|
||||||
{editMode ? (
|
{editMode ? (
|
||||||
<Select
|
<Select
|
||||||
value={alertOptions.column}
|
value={alertOptions.column}
|
||||||
onChange={column => onChange({ column })}
|
onChange={(column) => onChange({ column })}
|
||||||
dropdownMatchSelectWidth={false}
|
dropdownMatchSelectWidth={false}
|
||||||
style={{ minWidth: 100 }}>
|
style={{ minWidth: 100 }}
|
||||||
{columnNames.map(name => (
|
>
|
||||||
|
{columnNames.map((name) => (
|
||||||
<Select.Option key={name}>{name}</Select.Option>
|
<Select.Option key={name}>{name}</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -83,10 +130,11 @@ export default function Criteria({ columnNames, resultValues, alertOptions, onCh
|
|||||||
{editMode ? (
|
{editMode ? (
|
||||||
<Select
|
<Select
|
||||||
value={alertOptions.op}
|
value={alertOptions.op}
|
||||||
onChange={op => onChange({ op })}
|
onChange={(op) => onChange({ op })}
|
||||||
optionLabelProp="label"
|
optionLabelProp="label"
|
||||||
dropdownMatchSelectWidth={false}
|
dropdownMatchSelectWidth={false}
|
||||||
style={{ width: 55 }}>
|
style={{ width: 55 }}
|
||||||
|
>
|
||||||
<Select.Option value=">" label={CONDITIONS[">"]}>
|
<Select.Option value=">" label={CONDITIONS[">"]}>
|
||||||
{CONDITIONS[">"]} greater than
|
{CONDITIONS[">"]} greater than
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
@@ -125,7 +173,7 @@ export default function Criteria({ columnNames, resultValues, alertOptions, onCh
|
|||||||
id="threshold-criterion"
|
id="threshold-criterion"
|
||||||
style={{ width: 90 }}
|
style={{ width: 90 }}
|
||||||
value={alertOptions.value}
|
value={alertOptions.value}
|
||||||
onChange={e => onChange({ value: e.target.value })}
|
onChange={(e) => onChange({ value: e.target.value })}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DisabledInput minWidth={50}>{alertOptions.value}</DisabledInput>
|
<DisabledInput minWidth={50}>{alertOptions.value}</DisabledInput>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
const { extend, get, merge, find } = Cypress._;
|
const { extend, get, merge, find } = Cypress._;
|
||||||
|
|
||||||
const post = options =>
|
const post = (options) =>
|
||||||
cy
|
cy
|
||||||
.getCookie("csrf_token")
|
.getCookie("csrf_token")
|
||||||
.then(csrf => cy.request({ ...options, method: "POST", headers: { "X-CSRF-TOKEN": csrf.value } }));
|
.then((csrf) => cy.request({ ...options, method: "POST", headers: { "X-CSRF-TOKEN": csrf.value } }));
|
||||||
|
|
||||||
Cypress.Commands.add("createDashboard", name => {
|
Cypress.Commands.add("createDashboard", (name) => {
|
||||||
return post({ url: "api/dashboards", body: { name } }).then(({ body }) => body);
|
return post({ url: "api/dashboards", body: { name } }).then(({ body }) => body);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Cypress.Commands.add("createQuery", (data, shouldPublish = true) => {
|
|||||||
// eslint-disable-next-line cypress/no-assigning-return-values
|
// eslint-disable-next-line cypress/no-assigning-return-values
|
||||||
let request = post({ url: "/api/queries", body: merged }).then(({ body }) => body);
|
let request = post({ url: "/api/queries", body: merged }).then(({ body }) => body);
|
||||||
if (shouldPublish) {
|
if (shouldPublish) {
|
||||||
request = request.then(query =>
|
request = request.then((query) =>
|
||||||
post({ url: `/api/queries/${query.id}`, body: { is_draft: false } }).then(() => query)
|
post({ url: `/api/queries/${query.id}`, body: { is_draft: false } }).then(() => query)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,7 @@ Cypress.Commands.add("addWidget", (dashboardId, visualizationId, options = {}) =
|
|||||||
Cypress.Commands.add("createAlert", (queryId, options = {}, name) => {
|
Cypress.Commands.add("createAlert", (queryId, options = {}, name) => {
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
column: "?column?",
|
column: "?column?",
|
||||||
|
selector: "first",
|
||||||
op: "greater than",
|
op: "greater than",
|
||||||
rearm: 0,
|
rearm: 0,
|
||||||
value: 1,
|
value: 1,
|
||||||
@@ -109,7 +110,7 @@ Cypress.Commands.add("createUser", ({ name, email, password }) => {
|
|||||||
url: "api/users?no_invite=yes",
|
url: "api/users?no_invite=yes",
|
||||||
body: { name, email },
|
body: { name, email },
|
||||||
failOnStatusCode: false,
|
failOnStatusCode: false,
|
||||||
}).then(xhr => {
|
}).then((xhr) => {
|
||||||
const { status, body } = xhr;
|
const { status, body } = xhr;
|
||||||
if (status < 200 || status > 400) {
|
if (status < 200 || status > 400) {
|
||||||
throw new Error(xhr);
|
throw new Error(xhr);
|
||||||
@@ -146,7 +147,7 @@ Cypress.Commands.add("getDestinations", () => {
|
|||||||
Cypress.Commands.add("addDestinationSubscription", (alertId, destinationName) => {
|
Cypress.Commands.add("addDestinationSubscription", (alertId, destinationName) => {
|
||||||
return cy
|
return cy
|
||||||
.getDestinations()
|
.getDestinations()
|
||||||
.then(destinations => {
|
.then((destinations) => {
|
||||||
const destination = find(destinations, { name: destinationName });
|
const destination = find(destinations, { name: destinationName });
|
||||||
if (!destination) {
|
if (!destination) {
|
||||||
throw new Error("Destination not found");
|
throw new Error("Destination not found");
|
||||||
@@ -166,6 +167,6 @@ Cypress.Commands.add("addDestinationSubscription", (alertId, destinationName) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("updateOrgSettings", settings => {
|
Cypress.Commands.add("updateOrgSettings", (settings) => {
|
||||||
return post({ url: "api/settings/organization", body: settings }).then(({ body }) => body);
|
return post({ url: "api/settings/organization", body: settings }).then(({ body }) => body);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -926,6 +926,7 @@ class Alert(TimestampMixin, BelongsToOrgMixin, db.Model):
|
|||||||
UNKNOWN_STATE = "unknown"
|
UNKNOWN_STATE = "unknown"
|
||||||
OK_STATE = "ok"
|
OK_STATE = "ok"
|
||||||
TRIGGERED_STATE = "triggered"
|
TRIGGERED_STATE = "triggered"
|
||||||
|
TEST_STATE = "test"
|
||||||
|
|
||||||
id = primary_key("Alert")
|
id = primary_key("Alert")
|
||||||
name = Column(db.String(255))
|
name = Column(db.String(255))
|
||||||
@@ -960,7 +961,24 @@ class Alert(TimestampMixin, BelongsToOrgMixin, db.Model):
|
|||||||
if data["rows"] and self.options["column"] in data["rows"][0]:
|
if data["rows"] and self.options["column"] in data["rows"][0]:
|
||||||
op = OPERATORS.get(self.options["op"], lambda v, t: False)
|
op = OPERATORS.get(self.options["op"], lambda v, t: False)
|
||||||
|
|
||||||
value = data["rows"][0][self.options["column"]]
|
if "selector" not in self.options:
|
||||||
|
selector = "first"
|
||||||
|
else:
|
||||||
|
selector = self.options["selector"]
|
||||||
|
|
||||||
|
if selector == "max":
|
||||||
|
max_val = float("-inf")
|
||||||
|
for i in range(0, len(data["rows"])):
|
||||||
|
max_val = max(max_val, data["rows"][i][self.options["column"]])
|
||||||
|
value = max_val
|
||||||
|
elif selector == "min":
|
||||||
|
min_val = float("inf")
|
||||||
|
for i in range(0, len(data["rows"])):
|
||||||
|
min_val = min(min_val, data["rows"][i][self.options["column"]])
|
||||||
|
value = min_val
|
||||||
|
else:
|
||||||
|
value = data["rows"][0][self.options["column"]]
|
||||||
|
|
||||||
threshold = self.options["value"]
|
threshold = self.options["value"]
|
||||||
|
|
||||||
new_state = next_state(op, value, threshold)
|
new_state = next_state(op, value, threshold)
|
||||||
@@ -988,11 +1006,12 @@ class Alert(TimestampMixin, BelongsToOrgMixin, db.Model):
|
|||||||
result_table = [] # A two-dimensional array which can rendered as a table in Mustache
|
result_table = [] # A two-dimensional array which can rendered as a table in Mustache
|
||||||
for row in data["rows"]:
|
for row in data["rows"]:
|
||||||
result_table.append([row[col["name"]] for col in data["columns"]])
|
result_table.append([row[col["name"]] for col in data["columns"]])
|
||||||
|
print("OPTIONS", self.options)
|
||||||
context = {
|
context = {
|
||||||
"ALERT_NAME": self.name,
|
"ALERT_NAME": self.name,
|
||||||
"ALERT_URL": "{host}/alerts/{alert_id}".format(host=host, alert_id=self.id),
|
"ALERT_URL": "{host}/alerts/{alert_id}".format(host=host, alert_id=self.id),
|
||||||
"ALERT_STATUS": self.state.upper(),
|
"ALERT_STATUS": self.state.upper(),
|
||||||
|
"ALERT_SELECTOR": self.options["selector"],
|
||||||
"ALERT_CONDITION": self.options["op"],
|
"ALERT_CONDITION": self.options["op"],
|
||||||
"ALERT_THRESHOLD": self.options["value"],
|
"ALERT_THRESHOLD": self.options["value"],
|
||||||
"QUERY_NAME": self.query_rel.name,
|
"QUERY_NAME": self.query_rel.name,
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ class TestAlertEvaluate(BaseTestCase):
|
|||||||
def create_alert(self, results, column="foo", value="1"):
|
def create_alert(self, results, column="foo", value="1"):
|
||||||
result = self.factory.create_query_result(data=results)
|
result = self.factory.create_query_result(data=results)
|
||||||
query = self.factory.create_query(latest_query_data_id=result.id)
|
query = self.factory.create_query(latest_query_data_id=result.id)
|
||||||
alert = self.factory.create_alert(query_rel=query, options={"op": "equals", "column": column, "value": value})
|
alert = self.factory.create_alert(
|
||||||
|
query_rel=query, options={"selector": "first", "op": "equals", "column": column, "value": value}
|
||||||
|
)
|
||||||
return alert
|
return alert
|
||||||
|
|
||||||
def test_evaluate_triggers_alert_when_equal(self):
|
def test_evaluate_triggers_alert_when_equal(self):
|
||||||
@@ -69,6 +71,24 @@ class TestAlertEvaluate(BaseTestCase):
|
|||||||
alert = self.create_alert(results)
|
alert = self.create_alert(results)
|
||||||
self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)
|
self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)
|
||||||
|
|
||||||
|
def test_evaluates_correctly_with_max_selector(self):
|
||||||
|
results = {"rows": [{"foo": 1}, {"foo": 2}], "columns": [{"name": "foo", "type": "STRING"}]}
|
||||||
|
alert = self.create_alert(results)
|
||||||
|
alert.options["selector"] = "max"
|
||||||
|
self.assertEqual(alert.evaluate(), Alert.OK_STATE)
|
||||||
|
|
||||||
|
def test_evaluates_correctly_with_min_selector(self):
|
||||||
|
results = {"rows": [{"foo": 2}, {"foo": 1}], "columns": [{"name": "foo", "type": "STRING"}]}
|
||||||
|
alert = self.create_alert(results)
|
||||||
|
alert.options["selector"] = "min"
|
||||||
|
self.assertEqual(alert.evaluate(), Alert.TRIGGERED_STATE)
|
||||||
|
|
||||||
|
def test_evaluates_correctly_with_first_selector(self):
|
||||||
|
results = {"rows": [{"foo": 1}, {"foo": 2}], "columns": [{"name": "foo", "type": "STRING"}]}
|
||||||
|
alert = self.create_alert(results)
|
||||||
|
alert.options["selector"] = "first"
|
||||||
|
self.assertEqual(alert.evaluate(), Alert.TRIGGERED_STATE)
|
||||||
|
|
||||||
|
|
||||||
class TestNextState(TestCase):
|
class TestNextState(TestCase):
|
||||||
def test_numeric_value(self):
|
def test_numeric_value(self):
|
||||||
@@ -94,7 +114,9 @@ class TestAlertRenderTemplate(BaseTestCase):
|
|||||||
def create_alert(self, results, column="foo", value="5"):
|
def create_alert(self, results, column="foo", value="5"):
|
||||||
result = self.factory.create_query_result(data=results)
|
result = self.factory.create_query_result(data=results)
|
||||||
query = self.factory.create_query(latest_query_data_id=result.id)
|
query = self.factory.create_query(latest_query_data_id=result.id)
|
||||||
alert = self.factory.create_alert(query_rel=query, options={"op": "equals", "column": column, "value": value})
|
alert = self.factory.create_alert(
|
||||||
|
query_rel=query, options={"selector": "first", "op": "equals", "column": column, "value": value}
|
||||||
|
)
|
||||||
return alert
|
return alert
|
||||||
|
|
||||||
def test_render_custom_alert_template(self):
|
def test_render_custom_alert_template(self):
|
||||||
@@ -102,6 +124,7 @@ class TestAlertRenderTemplate(BaseTestCase):
|
|||||||
custom_alert = """
|
custom_alert = """
|
||||||
<pre>
|
<pre>
|
||||||
ALERT_STATUS {{ALERT_STATUS}}
|
ALERT_STATUS {{ALERT_STATUS}}
|
||||||
|
ALERT_SELECTOR {{ALERT_SELECTOR}}
|
||||||
ALERT_CONDITION {{ALERT_CONDITION}}
|
ALERT_CONDITION {{ALERT_CONDITION}}
|
||||||
ALERT_THRESHOLD {{ALERT_THRESHOLD}}
|
ALERT_THRESHOLD {{ALERT_THRESHOLD}}
|
||||||
ALERT_NAME {{ALERT_NAME}}
|
ALERT_NAME {{ALERT_NAME}}
|
||||||
@@ -116,6 +139,7 @@ class TestAlertRenderTemplate(BaseTestCase):
|
|||||||
expected = """
|
expected = """
|
||||||
<pre>
|
<pre>
|
||||||
ALERT_STATUS UNKNOWN
|
ALERT_STATUS UNKNOWN
|
||||||
|
ALERT_SELECTOR first
|
||||||
ALERT_CONDITION equals
|
ALERT_CONDITION equals
|
||||||
ALERT_THRESHOLD 5
|
ALERT_THRESHOLD 5
|
||||||
ALERT_NAME %s
|
ALERT_NAME %s
|
||||||
|
|||||||
Reference in New Issue
Block a user