Skip to content

Commit 766358d

Browse files
committed
Add server-sent events for tests.
1 parent f339680 commit 766358d

File tree

4 files changed

+87
-2
lines changed

4 files changed

+87
-2
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ This server exposes the following REST API's:
209209
}
210210
```
211211

212+
- **GET `/test_stream`**
213+
214+
This allows you to test a rule and get a [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) event stream back. Pass params `rule` (yaml string) and `options` (JSON string) to start receiving events.
215+
212216
- **GET `/metadata/:type`**
213217

214218
Returns metadata from elasticsearch related to elasalert's state. `:type` should be one of: elastalert_status, elastalert, elastalert_error, or silence. See [docs about the elastalert metadata index](https://elastalert.readthedocs.io/en/latest/elastalert_status.html).
@@ -217,6 +221,10 @@ This server exposes the following REST API's:
217221

218222
Returns field mapping from elasticsearch for a given index.
219223

224+
- **GET `/search/:index`**
225+
226+
Performs elasticsearch query on behalf of the API. JSON body to this endpoint will become body of an ES search.
227+
220228
- **[WIP] GET `/config`**
221229

222230
Gets the ElastAlert configuration from `config.yaml` in `elastalertPath` (from the config).

src/controllers/test/index.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class TestController {
1919
});
2020
}
2121

22-
testRule(rule, options) {
22+
testRule(rule, options, stream, response) {
2323
const self = this;
2424
let tempFileName = '~' + randomstring.generate() + '.temp';
2525
let tempFilePath = path.join(self.testFolder, tempFileName);
@@ -61,14 +61,25 @@ export default class TestController {
6161
});
6262

6363
testProcess.stdout.on('data', function (data) {
64+
if (stream) {
65+
response.write('event: result\ndata: ' + data.toString() + '\n\n');
66+
}
6467
stdoutLines.push(data.toString());
6568
});
6669

6770
testProcess.stderr.on('data', function (data) {
71+
if (stream) {
72+
response.write('event: progress\ndata: ' + data.toString() + '\n\n');
73+
}
6874
stderrLines.push(data.toString());
6975
});
7076

7177
testProcess.on('exit', function (statusCode) {
78+
if (stream) {
79+
response.write('event: done\ndata: DONE\n\n');
80+
response.end();
81+
}
82+
7283
if (statusCode === 0) {
7384
if (options.format === 'json') {
7485
resolve(stdoutLines.join(''));

src/handlers/test/stream.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import RouteLogger from '../../routes/route_logger';
2+
import {sendRequestError} from '../../common/errors/utils';
3+
import { RuleNotSendError, OptionsInvalidError} from '../../common/errors/test_request_errors';
4+
import Joi from 'joi';
5+
6+
let logger = new RouteLogger('/test_stream', 'POST');
7+
8+
const optionsSchema = Joi.object().keys({
9+
testType: Joi.string().valid('all', 'schemaOnly', 'countOnly').default('all'),
10+
days: Joi.number().min(1).default(1),
11+
alert: Joi.boolean().default(false),
12+
format: Joi.string().default(''),
13+
maxResults: Joi.number().default(0)
14+
}).default();
15+
16+
function analyzeRequest(request) {
17+
if (!request.query.rule) {
18+
return new RuleNotSendError();
19+
}
20+
21+
const validationResult = Joi.validate(JSON.parse(request.query.options), optionsSchema);
22+
23+
if (validationResult.error) {
24+
return new OptionsInvalidError(validationResult.error);
25+
}
26+
27+
return request.body;
28+
}
29+
30+
export default function testStreamGetHandler(request, response) {
31+
/**
32+
* @type {ElastalertServer}
33+
*/
34+
let server = request.app.get('server');
35+
let body = analyzeRequest(request);
36+
37+
try {
38+
var options = JSON.parse(request.query.options);
39+
} catch (error) {
40+
response.status(500).send();
41+
}
42+
43+
if (body.error) {
44+
logger.sendFailed(body.error);
45+
sendRequestError(response, body.error);
46+
}
47+
48+
response.writeHead(200, {
49+
'Content-Type': 'text/event-stream',
50+
'Cache-Control': 'no-cache',
51+
'Connection': 'keep-alive'
52+
});
53+
54+
try {
55+
server.testController.testRule(request.query.rule, options, true, response);
56+
} catch (error) {
57+
response.status(500).send();
58+
}
59+
}

src/routes/routes.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import templateGetHandler from '../handlers/templates/id/get';
1212
import templatePostHandler from '../handlers/templates/id/post';
1313
import templateDeleteHandler from '../handlers/templates/id/delete';
1414
import testPostHandler from '../handlers/test/post';
15+
import testStreamGetHandler from '../handlers/test/stream';
1516
import configGetHandler from '../handlers/config/get';
1617
import configPostHandler from '../handlers/config/post';
1718
import metadataHandler from '../handlers/metadata/get';
@@ -68,7 +69,13 @@ let routes = [
6869
path: 'test',
6970
method: 'POST',
7071
handler: testPostHandler
71-
}, {
72+
},
73+
{
74+
path: 'test_stream',
75+
method: 'GET',
76+
handler: testStreamGetHandler
77+
},
78+
{
7279
path: 'config',
7380
method: ['GET', 'POST'],
7481
handler: [configGetHandler, configPostHandler]

0 commit comments

Comments
 (0)