diff --git a/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/FiltersAT.java b/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/FiltersAT.java new file mode 100644 index 00000000..f32d6eea --- /dev/null +++ b/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/FiltersAT.java @@ -0,0 +1,56 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seyren.acceptancetests; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.restdriver.serverdriver.http.response.Response; +import org.junit.Test; + +import static com.github.restdriver.serverdriver.Matchers.hasJsonPath; +import static com.github.restdriver.serverdriver.Matchers.hasStatusCode; +import static com.github.restdriver.serverdriver.RestServerDriver.*; +import static com.seyren.acceptancetests.util.SeyrenDriver.checks; +import static com.seyren.acceptancetests.util.SeyrenDriver.filters; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class FiltersAT { + + @Test + public void testFiltersShouldBeSavedAndRetrievedAndDeleted() { + String body = "{ \"name\" : \"TestFilter\", \"filter\" : \"app.mx.test.rate5\" }"; + Response response = post(filters(), body(body, "application/json")); + assertThat(response, hasStatusCode(201)); + + body = "{ \"name\" : \"TestFilter2\", \"filter\" : \"app.ar.test.rate5\" }"; + response = post(filters(), body(body, "application/json")); + assertThat(response, hasStatusCode(201)); + + Response newResponse = get(filters()); + assertThat(newResponse, hasStatusCode(200)); + JsonNode responseJson = newResponse.asJson(); + assertThat(responseJson.findValues("id").size(), greaterThanOrEqualTo(2)); + + String idToBeDeleted = responseJson.findValues("id").get(0).asText(); + response = delete(new StringBuilder().append(filters()).append("/").append(idToBeDeleted)); + assertThat(response, hasStatusCode(204)); + + newResponse = get(filters()); + responseJson = newResponse.asJson(); + assertThat(newResponse, hasStatusCode(200)); + assertThat(responseJson.findValues("id").size(), is(1)); + } + + +} diff --git a/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/util/SeyrenDriver.java b/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/util/SeyrenDriver.java index bd18e15c..bd311447 100644 --- a/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/util/SeyrenDriver.java +++ b/seyren-acceptance-tests/src/test/java/com/seyren/acceptancetests/util/SeyrenDriver.java @@ -41,7 +41,11 @@ public static Url check(String checkId) { public static Url alerts(String checkId) { return check(checkId).withPath("/alerts"); } - + + public static Url filters() { + return baseUri().withPath("checks/filters"); + } + public static Url subscriptions(String checkId) { return check(checkId).withPath("/subscriptions"); } diff --git a/seyren-api/src/main/java/com/seyren/api/bean/FiltersBean.java b/seyren-api/src/main/java/com/seyren/api/bean/FiltersBean.java new file mode 100644 index 00000000..43b32096 --- /dev/null +++ b/seyren-api/src/main/java/com/seyren/api/bean/FiltersBean.java @@ -0,0 +1,58 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seyren.api.bean; + +import com.seyren.api.jaxrs.FiltersResource; +import com.seyren.core.domain.Filter; +import com.seyren.core.domain.SeyrenResponse; +import com.seyren.core.store.FiltersStore; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.net.URISyntaxException; + +@Named +public class FiltersBean implements FiltersResource { + + @Inject + private FiltersStore filtersStore; + + @Override + public Response getFilters() { + SeyrenResponse filter = filtersStore.getFilters(); + return Response.ok(filter).build(); + } + + @Override + public Response saveFilter(Filter filter) { + Filter stored = filtersStore.createFilter(filter); + return Response.created(uri(stored.getId())).build(); + } + + @Override + public Response deleteFilter(String filterId) { + filtersStore.deleteFilter(filterId); + return Response.noContent().build(); + } + + private URI uri(String filterId) { + try { + return new URI("checks/filters/" + filterId); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/seyren-api/src/main/java/com/seyren/api/jaxrs/FiltersResource.java b/seyren-api/src/main/java/com/seyren/api/jaxrs/FiltersResource.java new file mode 100644 index 00000000..635a4614 --- /dev/null +++ b/seyren-api/src/main/java/com/seyren/api/jaxrs/FiltersResource.java @@ -0,0 +1,39 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seyren.api.jaxrs; + +import com.seyren.core.domain.Filter; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/") +public interface FiltersResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/checks/filters") + Response getFilters(); + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/checks/filters") + Response saveFilter(Filter filter); + + @DELETE + @Path("/checks/filters/{filterId}") + Response deleteFilter(@PathParam("filterId") String filterId); + +} diff --git a/seyren-core/src/main/java/com/seyren/core/domain/Filter.java b/seyren-core/src/main/java/com/seyren/core/domain/Filter.java new file mode 100644 index 00000000..10a80bac --- /dev/null +++ b/seyren-core/src/main/java/com/seyren/core/domain/Filter.java @@ -0,0 +1,70 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seyren.core.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * This class represents a saved filter + * + * @author tamas + * + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Filter { + + private String id; + private String name; + private String filter; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Filter withId(String id) { + setId(id); + return this; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Filter withName(String name) { + setName(name); + return this; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public Filter withFilter(String filter) { + this.filter = filter; + return this; + } + +} diff --git a/seyren-core/src/main/java/com/seyren/core/store/FiltersStore.java b/seyren-core/src/main/java/com/seyren/core/store/FiltersStore.java new file mode 100644 index 00000000..cc973b9e --- /dev/null +++ b/seyren-core/src/main/java/com/seyren/core/store/FiltersStore.java @@ -0,0 +1,40 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seyren.core.store; + +import com.seyren.core.domain.Filter; +import com.seyren.core.domain.SeyrenResponse; + +public interface FiltersStore { + + Filter createFilter(Filter filter); + + SeyrenResponse getFilters(); + + void deleteFilter(String filterId); + +} diff --git a/seyren-mongo/src/main/java/com/seyren/mongo/MongoMapper.java b/seyren-mongo/src/main/java/com/seyren/mongo/MongoMapper.java index f4f7e1cd..a6867a5b 100644 --- a/seyren-mongo/src/main/java/com/seyren/mongo/MongoMapper.java +++ b/seyren-mongo/src/main/java/com/seyren/mongo/MongoMapper.java @@ -13,26 +13,17 @@ */ package com.seyren.mongo; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import com.google.common.base.Strings; +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.seyren.core.domain.*; import org.bson.types.ObjectId; import org.joda.time.DateTime; import org.joda.time.LocalTime; -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; -import com.seyren.core.domain.Alert; -import com.seyren.core.domain.AlertType; -import com.seyren.core.domain.Check; -import com.seyren.core.domain.Subscription; -import com.seyren.core.domain.SubscriptionType; +import java.math.BigDecimal; +import java.util.*; public class MongoMapper { @@ -131,7 +122,18 @@ public Alert alertFrom(DBObject dbo) { .withToType(toType) .withTimestamp(timestamp); } - + + public Filter filterFrom(DBObject dbo) { + String id = dbo.get("_id").toString(); + String name = getString(dbo, "name"); + String filter = getString(dbo, "filter"); + + return new Filter().withId(id) + .withName(name) + .withFilter(filter); + } + + public DBObject checkToDBObject(Check check) { return new BasicDBObject(propertiesToMap(check)); } @@ -143,6 +145,10 @@ public DBObject subscriptionToDBObject(Subscription subscription) { public DBObject alertToDBObject(Alert alert) { return new BasicDBObject(propertiesToMap(alert)); } + + public DBObject filterToDBObject(Filter filter) { + return new BasicDBObject(propertiesToMap(filter)); + } @SuppressWarnings({ "unchecked", "rawtypes" }) private Map propertiesToMap(Check check) { @@ -232,6 +238,15 @@ private Map propertiesToMap(Alert alert) { map.put("timestamp", new Date(alert.getTimestamp().getMillis())); return map; } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Map propertiesToMap(Filter filter) { + Map map = new HashMap(); + map.put("_id", filter.getId()); + map.put("name", filter.getName()); + map.put("filter", filter.getFilter()); + return map; + } private boolean getBoolean(DBObject dbo, String key) { return (Boolean) dbo.get(key); diff --git a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java index 34adbdae..1cf90ee0 100644 --- a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java +++ b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java @@ -13,48 +13,34 @@ */ package com.seyren.mongo; -import static com.seyren.mongo.NiceDBObject.*; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.inject.Inject; -import javax.inject.Named; - import com.google.common.base.Strings; +import com.mongodb.*; +import com.seyren.core.domain.*; +import com.seyren.core.store.AlertsStore; +import com.seyren.core.store.ChecksStore; +import com.seyren.core.store.FiltersStore; +import com.seyren.core.store.SubscriptionsStore; +import com.seyren.core.util.config.SeyrenConfig; +import com.seyren.core.util.hashing.TargetHash; import org.apache.commons.lang.Validate; import org.bson.types.ObjectId; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.mongodb.BasicDBObject; -import com.mongodb.Bytes; -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBCursor; -import com.mongodb.DBObject; -import com.mongodb.MongoClient; -import com.mongodb.MongoClientURI; -import com.mongodb.MongoCommandException; -import com.mongodb.MongoException; -import com.mongodb.WriteConcern; -import com.seyren.core.domain.Alert; -import com.seyren.core.domain.AlertType; -import com.seyren.core.domain.Check; -import com.seyren.core.domain.SeyrenResponse; -import com.seyren.core.domain.Subscription; -import com.seyren.core.store.AlertsStore; -import com.seyren.core.store.ChecksStore; -import com.seyren.core.store.SubscriptionsStore; -import com.seyren.core.util.config.SeyrenConfig; -import com.seyren.core.util.hashing.TargetHash; +import javax.inject.Inject; +import javax.inject.Named; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import static com.seyren.mongo.NiceDBObject.forId; +import static com.seyren.mongo.NiceDBObject.object; @Named -public class MongoStore implements ChecksStore, AlertsStore, SubscriptionsStore { +public class MongoStore implements ChecksStore, AlertsStore, SubscriptionsStore, FiltersStore { private static final Logger LOGGER = LoggerFactory.getLogger(MongoStore.class); @@ -134,6 +120,10 @@ private DBCollection getAlertsCollection() { return mongo.getCollection("alerts"); } + private DBCollection getFiltersCollection() { + return mongo.getCollection("filters"); + } + protected SeyrenResponse executeQueryAndCollectResponse(DBObject query) { List checks = new ArrayList(); DBCursor dbc = getChecksCollection().find(query); @@ -356,5 +346,31 @@ public void updateSubscription(String checkId, Subscription subscription) { DBObject updateObject = object("$set", object("subscriptions.$", subscriptionObject)); getChecksCollection().update(checkFindObject, updateObject); } - + + @Override + public Filter createFilter(Filter filter) { + filter.setId(ObjectId.get().toString()); + getFiltersCollection().insert(mapper.filterToDBObject(filter)); + + return filter; + } + + @Override + public SeyrenResponse getFilters() { + DBCursor dbc = getFiltersCollection().find(); + List filters = new ArrayList(); + while (dbc.hasNext()) { + filters.add(mapper.filterFrom(dbc.next())); + } + dbc.close(); + return new SeyrenResponse() + .withValues(filters) + .withTotal(dbc.count()); + } + + @Override + public void deleteFilter(String filterId) { + getFiltersCollection().remove(forId(filterId)); + } + } diff --git a/seyren-web/src/main/webapp/css/filter-panel.css b/seyren-web/src/main/webapp/css/filter-panel.css new file mode 100644 index 00000000..a1093f41 --- /dev/null +++ b/seyren-web/src/main/webapp/css/filter-panel.css @@ -0,0 +1,48 @@ +#filter-panel { + position: absolute; + z-index: 1; + display: none; + -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175); + box-shadow: 0 6px 12px rgba(0,0,0,.175); +} + +#filter-panel .filter-panel-row { + margin-bottom: 10px; + display:table; + width: 100%; +} + +#filter-panel .filter-panel-row .filter-panel-row-delete { + padding-left: 10px; + width: 22px; +} + +#filter-panel .filter-panel-row .filter-panel-column { + display: table-cell; + margin: 10px; +} + +#filter-panel .filter-panel-row .filter-panel-row-label { + padding-right: 10px; + padding-left: 10px; +} + +#filter-panel .filter-panel-row .filter-panel-row-label:hover { + border-radius: .25em; + cursor: pointer; + background-color: #ddd; +} + +#filter-panel-row-delete .btn { + color: #fff; + border-color: #a7a7a7; + background-color: #8e8e8e; +} +#filter-panel-row-delete .btn:hover { + border-color: #d43f3a; + background-color: #d9534f; +} + +#filter-panel input { + width: 100%; +} \ No newline at end of file diff --git a/seyren-web/src/main/webapp/css/override-bootstrap.css b/seyren-web/src/main/webapp/css/override-bootstrap.css index 8399ea3f..583d0023 100644 --- a/seyren-web/src/main/webapp/css/override-bootstrap.css +++ b/seyren-web/src/main/webapp/css/override-bootstrap.css @@ -53,3 +53,7 @@ body { padding-right: 10px; padding-left: 10px; } + +#filter-form { + margin-bottom: 15px; +} \ No newline at end of file diff --git a/seyren-web/src/main/webapp/html/checks.html b/seyren-web/src/main/webapp/html/checks.html index 9fee25a2..06e205ca 100755 --- a/seyren-web/src/main/webapp/html/checks.html +++ b/seyren-web/src/main/webapp/html/checks.html @@ -8,10 +8,24 @@

Checks


-
-
- -
+
+
+
+ +
+
+ +
+
+ +
+
+
@@ -27,7 +41,7 @@

Checks

- + {{ check.name }} @@ -49,5 +63,5 @@

Checks

- + + diff --git a/seyren-web/src/main/webapp/html/filter-panel-partial.html b/seyren-web/src/main/webapp/html/filter-panel-partial.html new file mode 100644 index 00000000..1f281365 --- /dev/null +++ b/seyren-web/src/main/webapp/html/filter-panel-partial.html @@ -0,0 +1,16 @@ +
+

+ +

+
+
+
+
+ {{ savedFilter.name }} +
+
+
+
+
+

We've got no filters. Why not create one?

+
\ No newline at end of file diff --git a/seyren-web/src/main/webapp/html/modal-partial-add-filter.html b/seyren-web/src/main/webapp/html/modal-partial-add-filter.html new file mode 100644 index 00000000..bad4e144 --- /dev/null +++ b/seyren-web/src/main/webapp/html/modal-partial-add-filter.html @@ -0,0 +1,30 @@ + diff --git a/seyren-web/src/main/webapp/index.html b/seyren-web/src/main/webapp/index.html index 263160d5..69a560c0 100755 --- a/seyren-web/src/main/webapp/index.html +++ b/seyren-web/src/main/webapp/index.html @@ -6,6 +6,7 @@ +