From e3819d8f4ef8abddda8dbbe972b5ddf92a785098 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 31 Mar 2012 11:09:10 +0100 Subject: [PATCH 01/11] [#51,bugfix][xs]: fix bug in pulling of fields from document in createDataset (corrects error in 5fc486d8083a27db21993fc7d58c2c2dae75c17f). --- src/backend/memory.js | 4 ++-- test/backend.test.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/memory.js b/src/backend/memory.js index 7ca76932..be86c936 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -29,8 +29,8 @@ this.recline.Backend = this.recline.Backend || {}; datasetInfo.fields = fields; } else { if (data) { - datasetInfo.fields = _.map(data[0], function(cell) { - return {id: cell}; + datasetInfo.fields = _.map(data[0], function(value, key) { + return {id: key}; }); } } diff --git a/test/backend.test.js b/test/backend.test.js index 27e0bd53..332b8eb1 100644 --- a/test/backend.test.js +++ b/test/backend.test.js @@ -7,7 +7,7 @@ var memoryData = { , name: '1-my-test-dataset' , id: 'test-dataset' }, - fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}], + fields: [{id: 'id'}, {id: 'x'}, {id: 'y'}, {id: 'z'}], documents: [ {id: 0, x: 1, y: 2, z: 3} , {id: 1, x: 2, y: 4, z: 6} @@ -32,6 +32,8 @@ test('Memory Backend: createDataset', function () { test('Memory Backend: createDataset 2', function () { var dataset = recline.Backend.createDataset(memoryData.documents); + equal(dataset.fields.length, 4); + deepEqual(['id', 'x', 'y', 'z'], dataset.fields.pluck('id')); dataset.query(); equal(memoryData.documents.length, dataset.currentDocuments.length); }); From 905659d86fdb900360bbd27147346e365870a5f9 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 31 Mar 2012 15:08:27 +0100 Subject: [PATCH 02/11] [#62,view/query][m]: start of faceting support with QueryFacetEditor. * QueryFacetEditor in DataExplorer which displays facets but nothing yet happens. * NB: very much less than half way through (things look worse rather than better atm). --- css/data-explorer.css | 4 +++ demo/js/app.js | 33 ++++++++++++++++----- src/model.js | 21 ++++++++++++++ src/view.js | 67 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 7 deletions(-) diff --git a/css/data-explorer.css b/css/data-explorer.css index 0fab70e1..a88f53cd 100644 --- a/css/data-explorer.css +++ b/css/data-explorer.css @@ -60,6 +60,10 @@ clear: both; } +.recline-query-facet-editor { + clear: both; +} + /********************************************************** * Notifications *********************************************************/ diff --git a/demo/js/app.js b/demo/js/app.js index 9198a913..e921031c 100755 --- a/demo/js/app.js +++ b/demo/js/app.js @@ -81,19 +81,38 @@ function localDataset() { , name: '1-my-test-dataset' , id: datasetId }, - fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}, {id: 'label'}], + fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}, {id: 'country'}, {id: 'label'}], documents: [ - {id: 0, x: 1, y: 2, z: 3, label: 'first'} - , {id: 1, x: 2, y: 4, z: 6, label: 'second'} - , {id: 2, x: 3, y: 6, z: 9, label: 'third'} - , {id: 3, x: 4, y: 8, z: 12, label: 'fourth'} - , {id: 4, x: 5, y: 10, z: 15, label: 'fifth'} - , {id: 5, x: 6, y: 12, z: 18, label: 'sixth'} + {id: 0, x: 1, y: 2, z: 3, country: 'DE', label: 'first'} + , {id: 1, x: 2, y: 4, z: 6, country: 'UK', label: 'second'} + , {id: 2, x: 3, y: 6, z: 9, country: 'US', label: 'third'} + , {id: 3, x: 4, y: 8, z: 12, country: 'UK', label: 'fourth'} + , {id: 4, x: 5, y: 10, z: 15, country: 'UK', label: 'fifth'} + , {id: 5, x: 6, y: 12, z: 18, country: 'DE', label: 'sixth'} ] }; var backend = new recline.Backend.Memory(); backend.addDataset(inData); var dataset = new recline.Model.Dataset({id: datasetId}, backend); + // TODO: auto-compute in Memory backend ?? + dataset.facets = { + 'country': { + terms: [ + { + term: 'UK', + count: 3 + }, + { + term: 'DE', + count: 2 + }, + { + term: 'US', + count: 1 + } + ] + } + }; return dataset; } diff --git a/src/model.js b/src/model.js index 207a9872..41934e67 100644 --- a/src/model.js +++ b/src/model.js @@ -23,6 +23,7 @@ my.Dataset = Backbone.Model.extend({ } this.fields = new my.FieldList(); this.currentDocuments = new my.DocumentList(); + this.facets = {}; this.docCount = null; this.queryState = new my.Query(); this.queryState.bind('change', this.query); @@ -116,6 +117,26 @@ my.Query = Backbone.Model.extend({ defaults: { size: 100 , from: 0 + , facets: {} + }, + initialize: function() { + _.bindAll(this, 'addFacet'); + }, + + addFacet: function(fieldId) { + // TODO: utilize field info to determine facet type ?? + var facets = this.get('facets'); + if (fieldId in facets) { + return; + } + facets[fieldId] = { + terms: { + field: fieldId + } + } + this.set({facets: facets}); + // trigger change event (does not seem to be triggered o/w) + // this.change(); } }); diff --git a/src/view.js b/src/view.js index 72b1c0d3..ae9c24f4 100644 --- a/src/view.js +++ b/src/view.js @@ -168,6 +168,12 @@ my.DataExplorer = Backbone.View.extend({ model: this.model.queryState }); this.el.find('.header').append(queryEditor.el); + var queryFacetEditor = new my.QueryFacetEditor({ + model: this.model.queryState + }, + this.model + ); + this.el.find('.header').append(queryFacetEditor.el); }, setupRouting: function() { @@ -275,6 +281,67 @@ my.QueryEditor = Backbone.View.extend({ } }); +my.QueryFacetEditor = Backbone.View.extend({ + className: 'recline-query-facet-editor', + template: ' \ + Add filter on \ + \ +
\ + {{#facets}} \ + {{label}} \ + \ + {{/facets}} \ +
\ + ', + + events: { + 'change .js-faceton': 'onAddFilter', + 'click .js-facet-show-toggle': 'onFacetShowToggle' + }, + initialize: function(model, dataset) { + _.bindAll(this, 'render'); + this.el = $(this.el); + this.model.bind('change', this.render); + this.dataset = dataset; + this.dataset.fields.bind('add', this.render); + this.dataset.fields.bind('reset', this.render); + this.dataset.fields.bind('remove', this.render); + this.render(); + }, + render: function() { + var tmplData = { + query: this.model.toJSON(), + fields: this.dataset.fields.toJSON() + }; + tmplData.facets = _.map(this.dataset.facets, function(data, key) { + var out = _.extend({label: key}, data); + return out; + }) + var templated = $.mustache(this.template, tmplData); + this.el.html(templated); + }, + onAddFilter: function(e) { + var fieldId = $(e.target).find('option:selected').attr('name'); + this.model.addFacet(fieldId); + // calculate facets for that field (if not already there??) + // re-render will happen automatically as dataset will have updated + }, + onFacetShowToggle: function(e) { + e.preventDefault(); + var $a = $(e.target); + var facetId = $a.attr('data-facet'); + var $ul = this.el.find('.facet-items[data-facet="' + facetId + '"]'); + $ul.toggle(); + } +}); /* ========================================================== */ // ## Miscellaneous Utilities From 3412962a358f11907794e96f8523ec52c54c38ea Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 31 Mar 2012 18:06:41 +0100 Subject: [PATCH 03/11] [#62,faceting][s]: introduce Facet and FacetList models and use them. * Conceptual breakthrough on how Faceting and Filtering interact (have updated issue #62 as a result). --- demo/js/app.js | 9 +++---- src/model.js | 40 +++++++++++++++++++++----------- src/view.js | 58 ++++++++++++++++++++-------------------------- test/model.test.js | 7 ++++++ 4 files changed, 63 insertions(+), 51 deletions(-) diff --git a/demo/js/app.js b/demo/js/app.js index e921031c..a6ca9f63 100755 --- a/demo/js/app.js +++ b/demo/js/app.js @@ -95,9 +95,10 @@ function localDataset() { backend.addDataset(inData); var dataset = new recline.Model.Dataset({id: datasetId}, backend); // TODO: auto-compute in Memory backend ?? - dataset.facets = { - 'country': { - terms: [ + dataset.facets = new recline.Model.FacetList([ + { + id: 'country', + result: [ { term: 'UK', count: 3 @@ -112,7 +113,7 @@ function localDataset() { } ] } - }; + ]); return dataset; } diff --git a/src/model.js b/src/model.js index 41934e67..b3743381 100644 --- a/src/model.js +++ b/src/model.js @@ -23,7 +23,7 @@ my.Dataset = Backbone.Model.extend({ } this.fields = new my.FieldList(); this.currentDocuments = new my.DocumentList(); - this.facets = {}; + this.facets = new my.FacetList(); this.docCount = null; this.queryState = new my.Query(); this.queryState.bind('change', this.query); @@ -119,24 +119,36 @@ my.Query = Backbone.Model.extend({ , from: 0 , facets: {} }, - initialize: function() { - _.bindAll(this, 'addFacet'); - }, + // Set (update or add) a terms filter + // http://www.elasticsearch.org/guide/reference/query-dsl/terms-filter.html + setFilter: function(fieldId, values) { + } +}); +my.Facet = Backbone.Model.extend({ + defaults: { + query: null, + result: null + } +}); + +my.FacetList = Backbone.Collection.extend({ + model: my.Facet, addFacet: function(fieldId) { - // TODO: utilize field info to determine facet type ?? - var facets = this.get('facets'); - if (fieldId in facets) { + // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) + if (fieldId in this) { return; } - facets[fieldId] = { - terms: { - field: fieldId + // TODO: utilize field info to determine facet type ?? + var facet = new my.Facet({ + id: fieldId, + query: { + terms: { + field: fieldId + } } - } - this.set({facets: facets}); - // trigger change event (does not seem to be triggered o/w) - // this.change(); + }); + this.add(facet); } }); diff --git a/src/view.js b/src/view.js index ae9c24f4..a463d8b5 100644 --- a/src/view.js +++ b/src/view.js @@ -168,11 +168,9 @@ my.DataExplorer = Backbone.View.extend({ model: this.model.queryState }); this.el.find('.header').append(queryEditor.el); - var queryFacetEditor = new my.QueryFacetEditor({ - model: this.model.queryState - }, - this.model - ); + var queryFacetEditor = new my.FacetQueryEditor({ + model: this.model + }); this.el.find('.header').append(queryFacetEditor.el); }, @@ -281,58 +279,52 @@ my.QueryEditor = Backbone.View.extend({ } }); -my.QueryFacetEditor = Backbone.View.extend({ +my.FacetQueryEditor = Backbone.View.extend({ className: 'recline-query-facet-editor', template: ' \ - Add filter on \ -