Skip to content
This repository was archived by the owner on Mar 21, 2023. It is now read-only.

Commit 6b35cec

Browse files
committed
Pipeline simulator v1 (#34)
* Add REST resource to simulate pipeline processing * Adapt default stream title and description Reflects better which messages are routed in there * Add first version of pipeline simulator UI * Move simulator message comparison to a component * Display errors simulating processing inline * Allow simulating messages in default stream * Change action buttons on simulation page * Display text if message is dropped in processing * Adapt to changes in server PR
1 parent e625d90 commit 6b35cec

File tree

13 files changed

+491
-7
lines changed

13 files changed

+491
-7
lines changed

src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.graylog.plugins.pipelineprocessor.rest.PipelineResource;
2323
import org.graylog.plugins.pipelineprocessor.rest.PipelineRestPermissions;
2424
import org.graylog.plugins.pipelineprocessor.rest.RuleResource;
25+
import org.graylog.plugins.pipelineprocessor.rest.SimulatorResource;
2526
import org.graylog2.plugin.PluginConfigBean;
2627
import org.graylog2.plugin.PluginModule;
2728

@@ -41,6 +42,7 @@ protected void configure() {
4142
addRestResource(RuleResource.class);
4243
addRestResource(PipelineResource.class);
4344
addRestResource(PipelineConnectionsResource.class);
45+
addRestResource(SimulatorResource.class);
4446
addPermissions(PipelineRestPermissions.class);
4547

4648
install(new ProcessorFunctionsModule());
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.graylog.plugins.pipelineprocessor.rest;
2+
3+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4+
import com.fasterxml.jackson.annotation.JsonCreator;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.google.auto.value.AutoValue;
7+
8+
@AutoValue
9+
@JsonAutoDetect
10+
public abstract class SimulationRequest {
11+
@JsonProperty
12+
public abstract String streamId();
13+
14+
@JsonProperty
15+
public abstract String index();
16+
17+
@JsonProperty
18+
public abstract String messageId();
19+
20+
public static Builder builder() {
21+
return new AutoValue_SimulationRequest.Builder();
22+
}
23+
24+
@JsonCreator
25+
public static SimulationRequest create (@JsonProperty("stream_id") String streamId,
26+
@JsonProperty("index") String index,
27+
@JsonProperty("message_id") String messageId) {
28+
return builder()
29+
.streamId(streamId)
30+
.index(index)
31+
.messageId(messageId)
32+
.build();
33+
}
34+
35+
@AutoValue.Builder
36+
public abstract static class Builder {
37+
public abstract SimulationRequest build();
38+
39+
public abstract Builder streamId(String streamId);
40+
41+
public abstract Builder index(String index);
42+
43+
public abstract Builder messageId(String messageId);
44+
}
45+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.graylog.plugins.pipelineprocessor.rest;
2+
3+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4+
import com.fasterxml.jackson.annotation.JsonCreator;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.google.auto.value.AutoValue;
7+
import org.graylog2.rest.models.messages.responses.ResultMessageSummary;
8+
9+
import java.util.List;
10+
11+
@AutoValue
12+
@JsonAutoDetect
13+
public abstract class SimulationResponse {
14+
@JsonProperty
15+
public abstract List<ResultMessageSummary> messages();
16+
17+
public static SimulationResponse.Builder builder() {
18+
return new AutoValue_SimulationResponse.Builder();
19+
}
20+
21+
@JsonCreator
22+
public static SimulationResponse create (@JsonProperty("messages") List<ResultMessageSummary> messages) {
23+
return builder()
24+
.messages(messages)
25+
.build();
26+
}
27+
28+
@AutoValue.Builder
29+
public abstract static class Builder {
30+
public abstract SimulationResponse build();
31+
32+
public abstract SimulationResponse.Builder messages(List<ResultMessageSummary> messages);
33+
}
34+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.graylog.plugins.pipelineprocessor.rest;
2+
3+
import io.swagger.annotations.Api;
4+
import io.swagger.annotations.ApiOperation;
5+
import io.swagger.annotations.ApiParam;
6+
import org.apache.shiro.authz.annotation.RequiresAuthentication;
7+
import org.apache.shiro.authz.annotation.RequiresPermissions;
8+
import org.graylog.plugins.pipelineprocessor.processors.PipelineInterpreter;
9+
import org.graylog2.database.NotFoundException;
10+
import org.graylog2.indexer.messages.DocumentNotFoundException;
11+
import org.graylog2.indexer.messages.Messages;
12+
import org.graylog2.indexer.results.ResultMessage;
13+
import org.graylog2.messageprocessors.OrderedMessageProcessors;
14+
import org.graylog2.plugin.Message;
15+
import org.graylog2.plugin.messageprocessors.MessageProcessor;
16+
import org.graylog2.plugin.rest.PluginRestResource;
17+
import org.graylog2.plugin.streams.Stream;
18+
import org.graylog2.rest.models.messages.responses.ResultMessageSummary;
19+
import org.graylog2.shared.rest.resources.RestResource;
20+
import org.graylog2.shared.security.RestPermissions;
21+
import org.graylog2.streams.StreamService;
22+
23+
import javax.inject.Inject;
24+
import javax.validation.constraints.NotNull;
25+
import javax.ws.rs.Consumes;
26+
import javax.ws.rs.POST;
27+
import javax.ws.rs.Path;
28+
import javax.ws.rs.Produces;
29+
import javax.ws.rs.core.MediaType;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
@Api(value = "Pipelines/Simulator", description = "Simulate pipeline message processor")
34+
@Path("/system/pipelines/simulate")
35+
@Consumes(MediaType.APPLICATION_JSON)
36+
@Produces(MediaType.APPLICATION_JSON)
37+
@RequiresAuthentication
38+
public class SimulatorResource extends RestResource implements PluginRestResource {
39+
private final OrderedMessageProcessors orderedMessageProcessors;
40+
private final Messages messages;
41+
private final StreamService streamService;
42+
43+
@Inject
44+
public SimulatorResource(OrderedMessageProcessors orderedMessageProcessors, Messages messages, StreamService streamService) {
45+
this.orderedMessageProcessors = orderedMessageProcessors;
46+
this.messages = messages;
47+
this.streamService = streamService;
48+
}
49+
50+
@ApiOperation(value = "Simulate the execution of the pipeline message processor")
51+
@POST
52+
@RequiresPermissions(PipelineRestPermissions.PIPELINE_RULE_READ)
53+
public SimulationResponse simulate(@ApiParam(name = "simulation", required = true) @NotNull SimulationRequest request) throws NotFoundException {
54+
checkPermission(RestPermissions.MESSAGES_READ, request.messageId());
55+
checkPermission(RestPermissions.STREAMS_READ, request.streamId());
56+
try {
57+
final ResultMessage resultMessage = messages.get(request.messageId(), request.index());
58+
final Message message = resultMessage.getMessage();
59+
if (!request.streamId().equals("default")) {
60+
final Stream stream = streamService.load(request.streamId());
61+
message.addStream(stream);
62+
}
63+
64+
List<ResultMessageSummary> simulationResults = new ArrayList<>();
65+
66+
for (MessageProcessor messageProcessor : orderedMessageProcessors) {
67+
if (messageProcessor instanceof PipelineInterpreter) {
68+
org.graylog2.plugin.Messages processedMessages = messageProcessor.process(message);
69+
for (Message processedMessage : processedMessages) {
70+
simulationResults.add(ResultMessageSummary.create(null, processedMessage.getFields(), ""));
71+
}
72+
}
73+
}
74+
75+
return SimulationResponse.create(simulationResults);
76+
} catch (DocumentNotFoundException e) {
77+
throw new NotFoundException(e);
78+
}
79+
}
80+
}

src/web/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PluginManifest, PluginStore } from 'graylog-web-plugin/plugin';
33
import PipelinesOverviewPage from 'pipelines/PipelinesOverviewPage';
44
import PipelineDetailsPage from 'pipelines/PipelineDetailsPage';
55
import PipelineConnectionsPage from 'pipeline-connections/PipelineConnectionsPage';
6+
import SimulatorPage from 'simulator/SimulatorPage';
67
import RulesPage from 'rules/RulesPage';
78
import RuleDetailsPage from 'rules/RuleDetailsPage';
89

@@ -12,6 +13,7 @@ PluginStore.register(new PluginManifest(packageJson, {
1213
{ path: '/system/pipelines/overview', component: PipelinesOverviewPage },
1314
{ path: '/system/pipelines/rules', component: RulesPage },
1415
{ path: '/system/pipelines/rules/:ruleId', component: RuleDetailsPage },
16+
{ path: '/system/pipelines/simulate/:streamId', component: SimulatorPage },
1517
{ path: '/system/pipelines/:pipelineId', component: PipelineDetailsPage },
1618
],
1719

src/web/pipeline-connections/Connection.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Col } from 'react-bootstrap';
2+
import { Button, Col } from 'react-bootstrap';
33
import { LinkContainer } from 'react-router-bootstrap';
44

55
import { DataTable, EntityListItem, Timestamp } from 'components/common';
@@ -46,10 +46,14 @@ const Connection = React.createClass({
4646
},
4747

4848
render() {
49-
const actions = (
50-
<ConnectionForm connection={{ stream: this.props.stream, pipelines: this.props.pipelines }}
51-
save={this.props.onUpdate} />
52-
);
49+
const actions = [
50+
<LinkContainer to={`/system/pipelines/simulate/${this.props.stream.id}`}>
51+
<Button bsStyle="info" key={`simulate-${this.props.stream.id}`}>Simulate processing</Button>
52+
</LinkContainer>,
53+
<ConnectionForm key={`connection-${this.props.stream.id}`}
54+
connection={{ stream: this.props.stream, pipelines: this.props.pipelines }}
55+
save={this.props.onUpdate} />,
56+
];
5357

5458
const content = (
5559
<Col md={12}>

src/web/pipeline-connections/PipelineConnectionsPage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ const PipelineConnectionsPage = React.createClass({
3333
StreamsStore.listStreams().then((streams) => {
3434
streams.push({
3535
id: 'default',
36-
title: 'Incoming messages',
37-
description: 'Default stream of all incoming messages.',
36+
title: 'Default',
37+
description: 'Stream used by default for messages not matching another stream.',
3838
});
3939
this.setState({ streams });
4040
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react';
2+
import { Col, Row } from 'react-bootstrap';
3+
4+
import LoaderTabs from 'components/messageloaders/LoaderTabs';
5+
import SimulationPreview from './SimulationPreview';
6+
7+
import SimulatorActions from './SimulatorActions';
8+
import SimulatorStore from './SimulatorStore';
9+
10+
const ProcessorSimulator = React.createClass({
11+
propTypes: {
12+
stream: React.PropTypes.object.isRequired,
13+
},
14+
15+
getInitialState() {
16+
return {
17+
message: undefined,
18+
simulation: undefined,
19+
loading: false,
20+
error: undefined,
21+
};
22+
},
23+
24+
_onMessageLoad(message) {
25+
this.setState({ message: message, simulation: undefined, loading: true, error: undefined });
26+
27+
SimulatorActions.simulate.triggerPromise(this.props.stream, message.index, message.id).then(
28+
messages => {
29+
this.setState({ simulation: messages, loading: false });
30+
},
31+
error => {
32+
this.setState({ loading: false, error: error });
33+
}
34+
);
35+
},
36+
37+
render() {
38+
return (
39+
<div>
40+
<Row>
41+
<Col md={12}>
42+
<h1>Load a message</h1>
43+
<p>Load a message to be used in the simulation. <strong>No changes will be done in your stored
44+
messages.</strong></p>
45+
<LoaderTabs onMessageLoaded={this._onMessageLoad} disableMessagePreview />
46+
</Col>
47+
</Row>
48+
<SimulationPreview stream={this.props.stream}
49+
originalMessage={this.state.message}
50+
simulationResults={this.state.simulation}
51+
isLoading={this.state.loading}
52+
error={this.state.error} />
53+
</div>
54+
);
55+
},
56+
});
57+
58+
export default ProcessorSimulator;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.message-preview-wrapper {
2+
margin-left: 15px;
3+
margin-right: 15px;
4+
}
5+
6+
.message-preview-wrapper dl {
7+
margin-top: 5px;
8+
margin-bottom: 0;
9+
}

0 commit comments

Comments
 (0)