Skip to content

Commit

Permalink
complete rule editing/deletion
Browse files Browse the repository at this point in the history
 - creating rules is now done via json as well
 - properly returns id instead of _id
 - changes go into effect immediately, no simulate mode yet
 - load rule before deletion, to simplify 404 return value
  • Loading branch information
kroepke committed Jan 25, 2016
1 parent d7baecc commit ae87313
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 36 deletions.
Expand Up @@ -26,6 +26,7 @@
import org.graylog.plugins.pipelineprocessor.functions.HasField; import org.graylog.plugins.pipelineprocessor.functions.HasField;
import org.graylog.plugins.pipelineprocessor.functions.InputFunction; import org.graylog.plugins.pipelineprocessor.functions.InputFunction;
import org.graylog.plugins.pipelineprocessor.functions.LongCoercion; import org.graylog.plugins.pipelineprocessor.functions.LongCoercion;
import org.graylog.plugins.pipelineprocessor.functions.SetField;
import org.graylog.plugins.pipelineprocessor.functions.StringCoercion; import org.graylog.plugins.pipelineprocessor.functions.StringCoercion;
import org.graylog.plugins.pipelineprocessor.processors.NaiveRuleProcessor; import org.graylog.plugins.pipelineprocessor.processors.NaiveRuleProcessor;
import org.graylog.plugins.pipelineprocessor.rest.PipelineResource; import org.graylog.plugins.pipelineprocessor.rest.PipelineResource;
Expand Down Expand Up @@ -56,8 +57,9 @@ protected void configure() {
addMessageProcessorFunction(StringCoercion.NAME, StringCoercion.class); addMessageProcessorFunction(StringCoercion.NAME, StringCoercion.class);


addMessageProcessorFunction(HasField.NAME, HasField.class); addMessageProcessorFunction(HasField.NAME, HasField.class);
addMessageProcessorFunction(InputFunction.NAME, InputFunction.class); addMessageProcessorFunction(SetField.NAME, SetField.class);
addMessageProcessorFunction(DropMessageFunction.NAME, DropMessageFunction.class); addMessageProcessorFunction(DropMessageFunction.NAME, DropMessageFunction.class);
addMessageProcessorFunction(InputFunction.NAME, InputFunction.class);
} }


protected void addMessageProcessorFunction(String name, Class<? extends Function<?>> functionClass) { protected void addMessageProcessorFunction(String name, Class<? extends Function<?>> functionClass) {
Expand Down
Expand Up @@ -72,6 +72,9 @@ public Collection<RuleSource> loadAll() {
} }


public void delete(String id) { public void delete(String id) {
dbCollection.removeById(id); final WriteResult<RuleSource, String> result = dbCollection.removeById(id);
if (result.getN() != 1) {
log.error("Unable to delete rule {}", id);
}
} }
} }
Expand Up @@ -49,6 +49,8 @@


@Api(value = "Pipeline Rules", description = "Rules for the pipeline message processor") @Api(value = "Pipeline Rules", description = "Rules for the pipeline message processor")
@Path("/system/pipelines") @Path("/system/pipelines")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class RuleResource extends RestResource implements PluginRestResource { public class RuleResource extends RestResource implements PluginRestResource {


private static final Logger log = LoggerFactory.getLogger(RuleResource.class); private static final Logger log = LoggerFactory.getLogger(RuleResource.class);
Expand All @@ -68,18 +70,18 @@ public RuleResource(RuleSourceService ruleSourceService,




@ApiOperation(value = "Create a processing rule from source", notes = "") @ApiOperation(value = "Create a processing rule from source", notes = "")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@POST @POST
@Path("/rule") @Path("/rule")
public RuleSource createFromParser(@ApiParam(name = "rule", required = true) @NotNull String ruleSource) throws ParseException { public RuleSource createFromParser(@ApiParam(name = "rule", required = true) @NotNull RuleSource ruleSource) throws ParseException {
try { try {
pipelineRuleParser.parseRule(ruleSource); pipelineRuleParser.parseRule(ruleSource.source());
} catch (ParseException e) { } catch (ParseException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(e.getErrors()).build()); throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(e.getErrors()).build());
} }
final RuleSource newRuleSource = RuleSource.builder() final RuleSource newRuleSource = RuleSource.builder()
.source(ruleSource) .title(ruleSource.title())
.description(ruleSource.description())
.source(ruleSource.source())
.createdAt(DateTime.now()) .createdAt(DateTime.now())
.modifiedAt(DateTime.now()) .modifiedAt(DateTime.now())
.build(); .build();
Expand All @@ -90,26 +92,20 @@ public RuleSource createFromParser(@ApiParam(name = "rule", required = true) @No
} }


@ApiOperation(value = "Get all processing rules") @ApiOperation(value = "Get all processing rules")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@GET @GET
@Path("/rule") @Path("/rule")
public Collection<RuleSource> getAll() { public Collection<RuleSource> getAll() {
return ruleSourceService.loadAll(); return ruleSourceService.loadAll();
} }


@ApiOperation(value = "Get a processing rule", notes = "It can take up to a second until the change is applied") @ApiOperation(value = "Get a processing rule", notes = "It can take up to a second until the change is applied")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/rule/{id}") @Path("/rule/{id}")
@GET @GET
public RuleSource get(@ApiParam(name = "id") @PathParam("id") String id) throws NotFoundException { public RuleSource get(@ApiParam(name = "id") @PathParam("id") String id) throws NotFoundException {
return ruleSourceService.load(id); return ruleSourceService.load(id);
} }


@ApiOperation(value = "Modify a processing rule", notes = "It can take up to a second until the change is applied") @ApiOperation(value = "Modify a processing rule", notes = "It can take up to a second until the change is applied")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/rule/{id}") @Path("/rule/{id}")
@PUT @PUT
public RuleSource update(@ApiParam(name = "id") @PathParam("id") String id, public RuleSource update(@ApiParam(name = "id") @PathParam("id") String id,
Expand All @@ -131,14 +127,12 @@ public RuleSource update(@ApiParam(name = "id") @PathParam("id") String id,
} }


@ApiOperation(value = "Delete a processing rule", notes = "It can take up to a second until the change is applied") @ApiOperation(value = "Delete a processing rule", notes = "It can take up to a second until the change is applied")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/rule/{id}") @Path("/rule/{id}")
@DELETE @DELETE
public void delete(@ApiParam(name = "id") @PathParam("id") String id) { public void delete(@ApiParam(name = "id") @PathParam("id") String id) throws NotFoundException {
ruleSourceService.load(id);
ruleSourceService.delete(id); ruleSourceService.delete(id);
clusterBus.post(RulesChangedEvent.deletedRuleId(id)); clusterBus.post(RulesChangedEvent.deletedRuleId(id));
} }



} }
Expand Up @@ -21,6 +21,7 @@
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.mongojack.Id;
import org.mongojack.ObjectId; import org.mongojack.ObjectId;


import javax.annotation.Nullable; import javax.annotation.Nullable;
Expand All @@ -29,18 +30,28 @@
@JsonAutoDetect @JsonAutoDetect
public abstract class RuleSource { public abstract class RuleSource {


@JsonProperty("_id") @JsonProperty("id")
@Nullable @Nullable
@Id
@ObjectId @ObjectId
public abstract String id(); public abstract String id();


@JsonProperty
public abstract String title();

@JsonProperty
@Nullable
public abstract String description();

@JsonProperty @JsonProperty
public abstract String source(); public abstract String source();


@JsonProperty @JsonProperty
@Nullable
public abstract DateTime createdAt(); public abstract DateTime createdAt();


@JsonProperty @JsonProperty
@Nullable
public abstract DateTime modifiedAt(); public abstract DateTime modifiedAt();


public static Builder builder() { public static Builder builder() {
Expand All @@ -50,13 +61,17 @@ public static Builder builder() {
public abstract Builder toBuilder(); public abstract Builder toBuilder();


@JsonCreator @JsonCreator
public static RuleSource create(@JsonProperty("_id") @ObjectId @Nullable String id, public static RuleSource create(@Id @ObjectId @JsonProperty("_id") @Nullable String id,
@JsonProperty("title") String title,
@JsonProperty("description") @Nullable String description,
@JsonProperty("source") String source, @JsonProperty("source") String source,
@JsonProperty("created_at") DateTime createdAt, @JsonProperty("created_at") @Nullable DateTime createdAt,
@JsonProperty("modified_at") DateTime modifiedAt) { @JsonProperty("modified_at") @Nullable DateTime modifiedAt) {
return builder() return builder()
.id(id) .id(id)
.source(source) .source(source)
.title(title)
.description(description)
.createdAt(createdAt) .createdAt(createdAt)
.modifiedAt(modifiedAt) .modifiedAt(modifiedAt)
.build(); .build();
Expand All @@ -68,6 +83,10 @@ public abstract static class Builder {


public abstract Builder id(String id); public abstract Builder id(String id);


public abstract Builder title(String title);

public abstract Builder description(String description);

public abstract Builder source(String source); public abstract Builder source(String source);


public abstract Builder createdAt(DateTime createdAt); public abstract Builder createdAt(DateTime createdAt);
Expand Down
39 changes: 38 additions & 1 deletion src/web/Rule.jsx
@@ -1,13 +1,50 @@
import React, {PropTypes} from 'react'; import React, {PropTypes} from 'react';
import {Row, Col, Button} from 'react-bootstrap';

import AceEditor from 'react-ace';
import brace from 'brace';

import 'brace/mode/text';
import 'brace/theme/chrome';

import RuleForm from 'RuleForm';


const Rule = React.createClass({ const Rule = React.createClass({
propTypes: { propTypes: {
rule: PropTypes.object.isRequired, rule: PropTypes.object.isRequired,
}, },


_delete() {
this.props.delete(this.props.rule);
},

render() { render() {
return <li> return <li>
<h2>{this.props.rule.name}</h2> <h2>{this.props.rule.title}</h2>
<Row>
<Col md={3}>{this.props.rule.description}</Col>
<Col md={8}>
<div style={{border: "1px solid lightgray", borderRadius: 5, width: 502}}>
<AceEditor
mode="text"
theme="chrome"
name={"rule-source-show-" + this.props.rule.id}
fontSize={11}
height="14em"
width="500px"
readonly
value={this.props.rule.source}
/>
</div>
</Col>
<Col md={1}>
<RuleForm rule={this.props.rule} save={this.props.save} validateName={this.props.validateName}/>
<Button style={{marginRight: 5}} bsStyle="primary" bsSize="xs"
onClick={this._delete}>
Delete
</Button>
</Col>
</Row>
</li>; </li>;
} }
}); });
Expand Down
17 changes: 12 additions & 5 deletions src/web/RuleForm.jsx
Expand Up @@ -22,6 +22,7 @@ const RuleForm = React.createClass({
getDefaultProps() { getDefaultProps() {
return { return {
rule: { rule: {
id: '',
title: '', title: '',
description: '', description: '',
source: '', source: '',
Expand All @@ -30,9 +31,15 @@ const RuleForm = React.createClass({
}, },


getInitialState() { getInitialState() {
const rule = this.props.rule;
return { return {
// when editing, take the rule that's been passed in // when editing, take the rule that's been passed in
rule: this.props.rule, rule: {
id: rule.id,
title: rule.title,
description: rule.description,
source: rule.source
},
error: false, error: false,
error_message: '', error_message: '',
} }
Expand Down Expand Up @@ -103,7 +110,7 @@ const RuleForm = React.createClass({
submitButtonText="Save"> submitButtonText="Save">
<fieldset> <fieldset>
<Input type="text" <Input type="text"
id={this._getId('rule-name')} id={this._getId('title')}
label="Name" label="Name"
onChange={this._onTitleChange} onChange={this._onTitleChange}
value={this.state.rule.title} value={this.state.rule.title}
Expand All @@ -120,9 +127,9 @@ const RuleForm = React.createClass({
<label>Rule source</label> <label>Rule source</label>
<div style={{border: "1px solid lightgray", borderRadius: 5}}> <div style={{border: "1px solid lightgray", borderRadius: 5}}>
<AceEditor <AceEditor
mode="javascript" mode="text"
theme="github" theme="chrome"
name="source" name={"source" + (this.props.create ? "-create" : "-edit")}
fontSize={11} fontSize={11}
height="14em" height="14em"
width="100%" width="100%"
Expand Down
17 changes: 16 additions & 1 deletion src/web/RulesComponent.jsx
Expand Up @@ -5,14 +5,20 @@ import { Input, Alert } from 'react-bootstrap';


import RulesActions from 'RulesActions'; import RulesActions from 'RulesActions';
import RuleForm from 'RuleForm'; import RuleForm from 'RuleForm';
import Rule from 'Rule';


const RulesComponent = React.createClass({ const RulesComponent = React.createClass({
propTypes: { propTypes: {
rules: PropTypes.array.isRequired, rules: PropTypes.array.isRequired,
}, },


_formatRule(rule) { _formatRule(rule) {
return <Rule key={"rule-" + rule._id} rule={rule} user={this.props.user} />; return <Rule key={"rule-" + rule.id}
rule={rule}
user={this.props.user}
delete={this._delete}
save={this._save}
validateName={this._validateName}/>;
}, },


_sortByTitle(rule1, rule2) { _sortByTitle(rule1, rule2) {
Expand All @@ -21,9 +27,18 @@ const RulesComponent = React.createClass({


_save(rule, callback) { _save(rule, callback) {
console.log(rule); console.log(rule);
if (rule.id) {
RulesActions.update(rule);
} else {
RulesActions.save(rule);
}
callback(); callback();
}, },


_delete(rule) {
RulesActions.delete(rule.id);
},

// TODO this should really validate the rule (as in parsing it server side) // TODO this should really validate the rule (as in parsing it server side)
_validateName(name) { _validateName(name) {
return true; return true;
Expand Down
49 changes: 42 additions & 7 deletions src/web/RulesStore.js
Expand Up @@ -25,19 +25,54 @@ const RulesStore = Reflux.createStore({
}, failCallback); }, failCallback);
}, },


get(pipelineId) { get(ruleId) {


}, },


save(pipelineSource) { save(ruleSource) {

const failCallback = (error) => {
UserNotification.error('Saving rule failed with status: ' + error.message,
'Could not save processing rule');
};
const url = URLUtils.qualifyUrl(urlPrefix + '/system/pipelines/rule');
const rule = {
title: ruleSource.title,
description: ruleSource.description,
source: ruleSource.source
};
return fetch('POST', url, rule).then((response) => {
this.rules = response;
this.trigger({rules: response});
}, failCallback);
}, },


update(pipelineId) { update(ruleSource) {

const failCallback = (error) => {
UserNotification.error('Updating rule failed with status: ' + error.message,
'Could not update processing rule');
};
const url = URLUtils.qualifyUrl(urlPrefix + '/system/pipelines/rule/' + ruleSource.id);
const rule = {
id: ruleSource.id,
title: ruleSource.title,
description: ruleSource.description,
source: ruleSource.source
};
return fetch('PUT', url, rule).then((response) => {
this.rules = this.rules.map((e) => e.id === response.id ? response : e);
this.trigger({rules: this.rules});
}, failCallback);
}, },
delete(pipelineId) { delete(ruleId) {

const failCallback = (error) => {
UserNotification.error('Updating rule failed with status: ' + error.message,
'Could not update processing rule');
};
const url = URLUtils.qualifyUrl(urlPrefix + '/system/pipelines/rule/' + ruleId);
return fetch('DELETE', url).then(() => {
this.rules = this.rules.filter((el) => el.id !== ruleId);
this.trigger({rules: this.rules});
}, failCallback);
}, },
}); });


Expand Down

0 comments on commit ae87313

Please sign in to comment.