[APM] Minor watcher improvements (#18602)

* [APM] Improve watcher tests

* Update tests
This commit is contained in:
Søren Louv-Jansen 2018-04-26 17:44:31 +02:00 committed by GitHub
parent bed97a27b0
commit fdc24f5b3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 276 additions and 35 deletions

View file

@ -53,6 +53,7 @@
"jest-cli": "^22.4.3",
"jest-styled-components": "^5.0.1",
"mocha": "^5.0.5",
"mustache": "^2.3.0",
"node-fetch": "^2.1.2",
"pdf-image": "1.1.0",
"pixelmatch": "4.0.2",

View file

@ -25,7 +25,7 @@ describe('ErrorGroupOverview -> List', () => {
const storeState = {};
const wrapper = mount(
<MemoryRouter>
<List items={[]} urlParams={props.urlParams} />
<List items={[]} urlParams={props.urlParams} location={{}} />
</MemoryRouter>,
storeState
);

View file

@ -149,8 +149,14 @@ export default class WatcherFlyout extends Component {
const timeRange =
this.state.schedule === 'interval'
? `now-${this.state.interval.value}${this.state.interval.unit}`
: 'now-24h';
? {
value: this.state.interval.value,
unit: this.state.interval.unit
}
: {
value: 24,
unit: 'h'
};
return createErrorGroupWatch({
emails,

View file

@ -1,31 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`createErrorGroupWatch should call createWatch with correct args 1`] = `
exports[`createErrorGroupWatch should format email correctly 1`] = `
"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"
<strong>this is a string</strong>
N/A
7761 occurrences
<strong>foo</strong>
<anonymous> (server/coffee.js)
7752 occurrences
<strong>socket hang up</strong>
createHangUpError (_http_client.js)
3887 occurrences
<strong>this will not get captured by express</strong>
<anonymous> (server/coffee.js)
3886 occurrences
"
`;
exports[`createErrorGroupWatch should format entire template correctly 1`] = `
Object {
"actions": Object {
"email": Object {
"email": Object {
"body": Object {
"html": "Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"<br/><br/>{{#ctx.payload.aggregations.error_groups.buckets}}<br/><strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong><br/>{{sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}<br/>{{doc_count}} occurrences<br/>{{/ctx.payload.aggregations.error_groups.buckets}}",
"html": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"<br/><br/><br/><strong>this is a string</strong><br/>N/A<br/>7761 occurrences<br/><br/><strong>foo</strong><br/><anonymous> (server/coffee.js)<br/>7752 occurrences<br/><br/><strong>socket hang up</strong><br/>createHangUpError (_http_client.js)<br/>3887 occurrences<br/><br/><strong>this will not get captured by express</strong><br/><anonymous> (server/coffee.js)<br/>3886 occurrences<br/>",
},
"subject": "\\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds the threshold",
"to": "{{ctx.metadata.emails}}",
"subject": "\\"opbeans-node\\" has error groups which exceeds the threshold",
"to": "my@email.dk",
},
},
"log_error": Object {
"logging": Object {
"text": "Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"<br/><br/>{{#ctx.payload.aggregations.error_groups.buckets}}<br/><strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong><br/>{{sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}<br/>{{doc_count}} occurrences<br/>{{/ctx.payload.aggregations.error_groups.buckets}}",
"text": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"<br/><br/><br/><strong>this is a string</strong><br/>N/A<br/>7761 occurrences<br/><br/><strong>foo</strong><br/><anonymous> (server/coffee.js)<br/>7752 occurrences<br/><br/><strong>socket hang up</strong><br/>createHangUpError (_http_client.js)<br/>3887 occurrences<br/><br/><strong>this will not get captured by express</strong><br/><anonymous> (server/coffee.js)<br/>3886 occurrences<br/>",
},
},
"slack_webhook": Object {
"webhook": Object {
"body": "{\\"text\\":\\"Your service \\\\\\"{{ctx.metadata.serviceName}}\\\\\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\\\\\"{{ctx.metadata.timeRangeHumanReadable}}\\\\\\"\\\\n{{#ctx.payload.aggregations.error_groups.buckets}}\\\\n>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*\\\\n>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}\\\\n>{{doc_count}} occurrences\\\\n{{/ctx.payload.aggregations.error_groups.buckets}}\\"}",
"body": "{\\"text\\":\\"Your service \\\\\\"opbeans-node\\\\\\" has error groups which exceeds 10 occurrences within \\\\\\"24h\\\\\\"\\\\n\\\\n>*this is a string*\\\\n>N/A\\\\n>7761 occurrences\\\\n\\\\n>*foo*\\\\n>\`<anonymous> (server/coffee.js)\`\\\\n>7752 occurrences\\\\n\\\\n>*socket hang up*\\\\n>\`createHangUpError (_http_client.js)\`\\\\n>3887 occurrences\\\\n\\\\n>*this will not get captured by express*\\\\n>\`<anonymous> (server/coffee.js)\`\\\\n>3886 occurrences\\\\n\\"}",
"headers": Object {
"Content-Type": "application/json",
},
"host": "hooks.slack.com",
"method": "POST",
"path": "{{ctx.metadata.slackUrlPath}}",
"path": "/services/slackid1/slackid2/slackid3",
"port": 443,
"scheme": "https",
},
@ -64,7 +86,7 @@ Object {
},
"terms": Object {
"field": "error.grouping_key",
"min_doc_count": "{{ctx.metadata.threshold}}",
"min_doc_count": "10",
"order": Object {
"_count": "desc",
},
@ -77,7 +99,7 @@ Object {
"filter": Array [
Object {
"term": Object {
"context.service.name": "{{ctx.metadata.serviceName}}",
"context.service.name": "opbeans-node",
},
},
Object {
@ -88,7 +110,7 @@ Object {
Object {
"range": Object {
"@timestamp": Object {
"gte": "{{ctx.metadata.timeRange}}",
"gte": "now-24h",
},
},
},
@ -110,8 +132,8 @@ Object {
"serviceName": "opbeans-node",
"slackUrlPath": "/services/slackid1/slackid2/slackid3",
"threshold": 10,
"timeRange": "now-24h",
"timeRangeHumanReadable": "24h",
"timeRangeUnit": "h",
"timeRangeValue": 24,
"trigger": "This value must be changed in trigger section",
},
"trigger": Object {
@ -125,10 +147,22 @@ Object {
`;
exports[`createErrorGroupWatch should format slack message correctly 1`] = `
"Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"
{{#ctx.payload.aggregations.error_groups.buckets}}
>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*
>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}
>{{doc_count}} occurrences
{{/ctx.payload.aggregations.error_groups.buckets}}"
"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"
>*this is a string*
>N/A
>7761 occurrences
>*foo*
>\`<anonymous> (server/coffee.js)\`
>7752 occurrences
>*socket hang up*
>\`createHangUpError (_http_client.js)\`
>3887 occurrences
>*this will not get captured by express*
>\`<anonymous> (server/coffee.js)\`
>3886 occurrences
"
`;

View file

@ -5,16 +5,27 @@
*/
import { createErrorGroupWatch } from '../createErrorGroupWatch';
import mustache from 'mustache';
import chrome from 'ui/chrome';
import * as rest from '../../../../../services/rest';
import { isObject, isArray, isString } from 'lodash';
import esResponse from './esResponse.json';
jest.mock('uuid', () => ({
v4: jest.fn(() => 'mocked-uuid')
}));
// disable html escaping since this is also disabled in watcher\s mustache implementation
mustache.escape = value => value;
describe('createErrorGroupWatch', () => {
let res;
let createWatchResponse;
let tmpl;
beforeEach(async () => {
chrome.getInjected = jest.fn().mockReturnValue('myIndexPattern');
jest.spyOn(rest, 'createWatch').mockReturnValue();
res = await createErrorGroupWatch({
createWatchResponse = await createErrorGroupWatch({
emails: ['my@email.dk'],
schedule: {
daily: {
@ -24,27 +35,70 @@ describe('createErrorGroupWatch', () => {
serviceName: 'opbeans-node',
slackUrl: 'https://hooks.slack.com/services/slackid1/slackid2/slackid3',
threshold: 10,
timeRange: 'now-24h'
timeRange: { value: 24, unit: 'h' }
});
const watchBody = rest.createWatch.mock.calls[0][1];
const templateCtx = {
payload: esResponse,
metadata: watchBody.metadata
};
tmpl = renderMustache(rest.createWatch.mock.calls[0][1], templateCtx);
});
afterEach(() => jest.restoreAllMocks());
it('should call createWatch with correct args', () => {
expect(rest.createWatch.mock.calls[0][0]).toContain('apm-');
expect(rest.createWatch.mock.calls[0][1]).toMatchSnapshot();
expect(rest.createWatch.mock.calls[0][0]).toBe('apm-mocked-uuid');
});
it('should format slack message correctly', () => {
expect(tmpl.actions.slack_webhook.webhook.path).toBe(
'/services/slackid1/slackid2/slackid3'
);
expect(
JSON.parse(
rest.createWatch.mock.calls[0][1].actions.slack_webhook.webhook.body
).text
JSON.parse(tmpl.actions.slack_webhook.webhook.body).text
).toMatchSnapshot();
});
it('should format email correctly', () => {
expect(tmpl.actions.email.email.to).toBe('my@email.dk');
expect(tmpl.actions.email.email.subject).toBe(
'"opbeans-node" has error groups which exceeds the threshold'
);
expect(
tmpl.actions.email.email.body.html.replace(/<br\/>/g, '\n')
).toMatchSnapshot();
});
it('should format entire template correctly', () => {
expect(tmpl).toMatchSnapshot();
});
it('should return watch id', async () => {
const id = rest.createWatch.mock.calls[0][0];
expect(res).toEqual(id);
expect(createWatchResponse).toEqual(id);
});
});
// Recusively iterate a nested structure and render strings as mustache templates
function renderMustache(input, ctx) {
if (isString(input)) {
return mustache.render(input, { ctx });
}
if (isArray(input)) {
return input.map(itemValue => renderMustache(itemValue, ctx));
}
if (isObject(input)) {
return Object.keys(input).reduce((acc, key) => {
const value = input[key];
return { ...acc, [key]: renderMustache(value, ctx) };
}, {});
}
return input;
}

View file

@ -0,0 +1,141 @@
{
"took": 454,
"timed_out": false,
"_shards": {
"total": 10,
"successful": 10,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 23287,
"max_score": 0,
"hits": []
},
"aggregations": {
"error_groups": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "63925d00b445cdf4b532dd09d185f5c6",
"doc_count": 7761,
"sample": {
"hits": {
"total": 7761,
"max_score": null,
"hits": [
{
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
"_type": "doc",
"_id": "qH7C_WIBcmGuKeCHJvvT",
"_score": null,
"_source": {
"@timestamp": "2018-04-25T17:03:02.296Z",
"error": {
"log": {
"message": "this is a string"
},
"grouping_key": "63925d00b445cdf4b532dd09d185f5c6"
}
},
"sort": [1524675782296]
}
]
}
}
},
{
"key": "89bb1a1f644c7f4bbe8d1781b5cb5fd5",
"doc_count": 7752,
"sample": {
"hits": {
"total": 7752,
"max_score": null,
"hits": [
{
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
"_type": "doc",
"_id": "_3_D_WIBcmGuKeCHFwOW",
"_score": null,
"_source": {
"@timestamp": "2018-04-25T17:04:03.504Z",
"error": {
"exception": {
"handled": true,
"message": "foo"
},
"culprit": "<anonymous> (server/coffee.js)",
"grouping_key": "89bb1a1f644c7f4bbe8d1781b5cb5fd5"
}
},
"sort": [1524675843504]
}
]
}
}
},
{
"key": "7a17ea60604e3531bd8de58645b8631f",
"doc_count": 3887,
"sample": {
"hits": {
"total": 3887,
"max_score": null,
"hits": [
{
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
"_type": "doc",
"_id": "dn_D_WIBcmGuKeCHQgXJ",
"_score": null,
"_source": {
"@timestamp": "2018-04-25T17:04:14.575Z",
"error": {
"exception": {
"handled": false,
"message": "socket hang up"
},
"culprit": "createHangUpError (_http_client.js)",
"grouping_key": "7a17ea60604e3531bd8de58645b8631f"
}
},
"sort": [1524675854575]
}
]
}
}
},
{
"key": "b9e1027f29c221763f864f6fa2ad9f5e",
"doc_count": 3886,
"sample": {
"hits": {
"total": 3886,
"max_score": null,
"hits": [
{
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
"_type": "doc",
"_id": "dX_D_WIBcmGuKeCHQgXJ",
"_score": null,
"_source": {
"@timestamp": "2018-04-25T17:04:14.533Z",
"error": {
"exception": {
"handled": false,
"message": "this will not get captured by express"
},
"culprit": "<anonymous> (server/coffee.js)",
"grouping_key": "b9e1027f29c221763f864f6fa2ad9f5e"
}
},
"sort": [1524675854533]
}
]
}
}
}
]
}
}
}

View file

@ -38,7 +38,7 @@ export async function createErrorGroupWatch({
const apmIndexPattern = chrome.getInjected('apmIndexPattern');
const slackUrlPath = getSlackPathUrl(slackUrl);
const emailTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeHumanReadable}}"
const emailTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"
{{#ctx.payload.aggregations.error_groups.buckets}}
<strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong>
@ -46,7 +46,7 @@ export async function createErrorGroupWatch({
{{doc_count}} occurrences
{{/ctx.payload.aggregations.error_groups.buckets}}`.replace(/\n/g, '<br/>');
const slackTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeHumanReadable}}"
const slackTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"
{{#ctx.payload.aggregations.error_groups.buckets}}
>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*
>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}
@ -59,8 +59,8 @@ export async function createErrorGroupWatch({
trigger: 'This value must be changed in trigger section',
serviceName,
threshold,
timeRange,
timeRangeHumanReadable: timeRange.replace('now-', ''),
timeRangeValue: timeRange.value,
timeRangeUnit: timeRange.unit,
slackUrlPath
},
trigger: {
@ -80,7 +80,8 @@ export async function createErrorGroupWatch({
{
range: {
'@timestamp': {
gte: '{{ctx.metadata.timeRange}}'
gte:
'now-{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}'
}
}
}

View file

@ -5175,6 +5175,10 @@ multipipe@^0.1.2:
dependencies:
duplexer2 "0.0.2"
mustache@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
mute-stream@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"