Implemented entry exclusions in UI.

* Resolved D0020: Add exclusion filter for entries.
* Refactored ``timeline`` fields of several objects in ``ts.js`` to be
  ``timelineModel`` to be explicit.
* Added support for exclusions in ``EntryListView.renderOne`` and
  ``EntryListView.render``.
This commit is contained in:
Jonathan Bernard 2011-06-16 17:50:47 -05:00
parent 602bdca7e3
commit 1fff94b622
14 changed files with 219 additions and 22 deletions

143
.ide/vim-views/ts.js.view Normal file
View File

@ -0,0 +1,143 @@
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
argglobal
edit /mnt/secure/projects/jdb-labs/timestamper/web-app/www/js/ts.js
setlocal keymap=
setlocal noarabic
setlocal autoindent
setlocal balloonexpr=
setlocal nobinary
setlocal bufhidden=
setlocal buflisted
setlocal buftype=
setlocal nocindent
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
setlocal cinoptions=
setlocal cinwords=if,else,while,do,for,switch
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
setlocal commentstring=/*%s*/
setlocal complete=.,w,b,u,t,i
setlocal completefunc=
setlocal nocopyindent
setlocal nocursorcolumn
setlocal nocursorline
setlocal define=
setlocal dictionary=
setlocal nodiff
setlocal equalprg=
setlocal errorformat=
setlocal expandtab
if &filetype != 'javascript'
setlocal filetype=javascript
endif
setlocal foldcolumn=0
setlocal foldenable
setlocal foldexpr=0
setlocal foldignore=#
setlocal foldlevel=0
setlocal foldmarker={{{,}}}
setlocal foldmethod=manual
setlocal foldminlines=1
setlocal foldnestmax=20
setlocal foldtext=foldtext()
setlocal formatexpr=
setlocal formatoptions=tcq
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
setlocal grepprg=
setlocal iminsert=2
setlocal imsearch=2
setlocal include=
setlocal includeexpr=
setlocal indentexpr=
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
setlocal noinfercase
setlocal iskeyword=@,48-57,_,192-255
setlocal keywordprg=
setlocal nolinebreak
setlocal nolisp
setlocal nolist
setlocal makeprg=
setlocal matchpairs=(:),{:},[:]
setlocal nomodeline
setlocal modifiable
setlocal nrformats=octal,hex
setlocal number
setlocal numberwidth=4
setlocal omnifunc=
setlocal path=
setlocal nopreserveindent
setlocal nopreviewwindow
setlocal quoteescape=\\
setlocal noreadonly
setlocal norightleft
setlocal rightleftcmd=search
setlocal noscrollbind
setlocal shiftwidth=4
setlocal noshortname
setlocal nosmartindent
setlocal softtabstop=0
setlocal nospell
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
setlocal spellfile=
setlocal spelllang=en
setlocal statusline=
setlocal suffixesadd=
setlocal swapfile
setlocal synmaxcol=3000
if &syntax != 'javascript'
setlocal syntax=javascript
endif
setlocal tabstop=4
setlocal tags=
setlocal textwidth=80
setlocal thesaurus=
setlocal nowinfixheight
setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
9,18fold
20,28fold
30,43fold
45,62fold
64,82fold
87,289fold
291,378fold
380,448fold
450,487fold
489,603fold
605,664fold
666,702fold
9
normal zc
20
normal zc
30
normal zc
45
normal zo
64
normal zc
87
normal zc
291
normal zo
380
normal zc
450
normal zc
489
normal zo
605
normal zc
666
normal zc
let s:l = 334 - ((273 * winheight(0) + 35) / 71)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
334
normal! 042l
let &so = s:so_save | let &siso = s:siso_save
doautoall SessionLoadPost
" vim: set ft=vim :
syntax on

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
db/test/ts_ext_data.DCL Normal file

Binary file not shown.

Binary file not shown.

BIN
db/test/ts_user.DCL Normal file

Binary file not shown.

View File

@ -1,10 +0,0 @@
Add exclusion filter for entries.
=================================
Add a way for users to add regexes for entries to be ignored in the
display. Exclusions may be per-timeline or per-user.
========= ==========
Created: 2011-06-10
Resolved: YYYY-MM-DD
========= ==========

View File

@ -0,0 +1,22 @@
Add exclusion filter for entries.
=================================
Add a way for users to add regexes for entries to be ignored in the
display. Exclusions may be per-timeline or per-user.
Resolution
----------
``ts_user`` and ``ts_timeline`` both support an exnteded data property called
``entry_exclusions`` which accepts a list of regular expression strings. Any entries
whose marks match any of the regular expressions should be excluded from view.
EntryListView.render filters out any matching entries from its display. It still
takes them into account when calculating the duration of other entries.
----
========= ==========
Created: 2011-06-10
Resolved: 2011-06-15
========= ==========

View File

@ -0,0 +1,12 @@
Check for exclusion after mark update.
======================================
If a user rewrites the mark of an entry, we should check to see if it now
matches one of the defined entry exclusions.
----
========= ==========
Created: YYYY-MM-DD
Resolved: YYYY-MM-DD
========= ==========

View File

@ -36,7 +36,7 @@ update(ER = #ts_entry{}, ExtData) when is_list(ExtData) ->
write(ER = #ts_entry{}) -> mnesia:dirty_write(ER). write(ER = #ts_entry{}) -> mnesia:dirty_write(ER).
write(ER = #ts_entry{}, ExtData) -> write(ER = #ts_entry{}, ExtData) ->
{atomic, Result} = mnesia:transcation(fun() -> {atomic, Result} = mnesia:transaction(fun() ->
ok = mnesia:write(ER), ok = mnesia:write(ER),
ok = ts_common:do_set_ext_data(ER, ExtData) ok = ts_common:do_set_ext_data(ER, ExtData)
end), end),

View File

@ -48,15 +48,16 @@ $(document).ready(function(){
comparator: function(entry) { return entry.get('timestamp'); }, comparator: function(entry) { return entry.get('timestamp'); },
initialize: function(model, options) { initialize: function(model, options) {
if (options.timeline == undefined) { if (options.timelineModel == undefined) {
throw "Cannot create an EntryList without a TimelineModel reference." throw "Cannot create an EntryList without a TimelineModel reference."
} else { this.timeline = options.timeline; } } else { this.timelineModel = options.timelineModel; }
_.bindAll(this, "url"); _.bindAll(this, "url");
}, },
url: function() { url: function() {
return "/ts_api/entries/" + this.timeline.get('user_id') + "/" + this.timeline.get('id'); return "/ts_api/entries/" + this.timelineModel.get('user_id') + "/"
+ this.timelineModel.get('id');
} }
}); });
@ -312,10 +313,26 @@ $(document).ready(function(){
}, },
renderOne: function(entry, nextEntry) { renderOne: function(entry, nextEntry) {
// exclude if any exclusion RegExps match
var excluded = _.any(this.entryExclusions,
function(exclusion) { return exclusion.test(entry.get("mark"))});
// create the view if it does not exist
if (!entry.view) { new TS.EntryView( if (!entry.view) { new TS.EntryView(
{model: entry, markdownConverter: this.markdownConverter}); } {model: entry, markdownConverter: this.markdownConverter}); }
entry.view.nextModel = nextEntry entry.view.nextModel = nextEntry
this.entryContainer.prepend(entry.view.render().el);
// render the element
var el = entry.view.render().el;
// add it to the container
this.entryContainer.prepend(el);
// hide it if excluded
if (excluded) {
$(el).fadeOut('slow');
$(el).addClass('excluded');
}
}, },
createNewEntryOnEnter: function(e) { createNewEntryOnEnter: function(e) {
@ -337,6 +354,19 @@ $(document).ready(function(){
}, },
render: function() { render: function() {
// get our user exclusions
var userExclusions = TS.app.user.model.get("entry_exclusions") || [];
// get the current timeline exclusions
var timelineExclusions =
this.collection.timelineModel.get("entry_exclusions") || [];
// turn them into RegExps and store them
this.entryExclusions = _.map(
userExclusions.concat(timelineExclusions),
function(exclusion) { return new RegExp(exclusion)} );
this.entryContainer.empty(); this.entryContainer.empty();
for (var i = 0, len = this.collection.length; i < len; i++) { for (var i = 0, len = this.collection.length; i < len; i++) {
var entry = this.collection.at(i); var entry = this.collection.at(i);
@ -367,7 +397,7 @@ $(document).ready(function(){
if (options.initialTimelineId == undefined) { if (options.initialTimelineId == undefined) {
throw "Can not create a TimelineListView without an initial timeline." throw "Can not create a TimelineListView without an initial timeline."
} else { } else {
this.selected = this.collection.get(options.initialTimelineId); this.selectedModel = this.collection.get(options.initialTimelineId);
} }
this.collection.bind('add', this.renderOne); this.collection.bind('add', this.renderOne);
@ -381,10 +411,10 @@ $(document).ready(function(){
render: function() { render: function() {
// render the basic template // render the basic template
$(this.el).html(ich.timelineTemplate(this.selected.toJSON())); $(this.el).html(ich.timelineTemplate(this.selectedModel.toJSON()));
// render the selection list // render the selection list
_.each(this.collection.without([this.selected]), this.renderOne); _.each(this.collection.without([this.selectedModel]), this.renderOne);
}, },
editId: function() { editId: function() {
@ -400,7 +430,7 @@ $(document).ready(function(){
}, },
close: function() { close: function() {
this.selected.save({ this.selectedModel.save({
id: this.$('.timeline-id-input').val(), id: this.$('.timeline-id-input').val(),
description: this.$('.timeline-desc-input').val()}); description: this.$('.timeline-desc-input').val()});
$(this.el).removeClass('edit-id edit-desc'); $(this.el).removeClass('edit-id edit-desc');
@ -515,7 +545,7 @@ $(document).ready(function(){
// create the entry collection // create the entry collection
this.entries = {}; this.entries = {};
this.entries.collection = new TS.EntryList(entryModels, this.entries.collection = new TS.EntryList(entryModels,
{timeline: this.timelines.view.selected}); {timelineModel: this.timelines.view.selectedModel});
this.entries.view = new TS.EntryListView( this.entries.view = new TS.EntryListView(
{collection: this.entries.collection}); {collection: this.entries.collection});
@ -553,10 +583,10 @@ $(document).ready(function(){
var tl = this.timelines.collection.get(e.srcElement.text); var tl = this.timelines.collection.get(e.srcElement.text);
// set the on the timeline view // set the on the timeline view
this.timelines.view.selected = tl; this.timelines.view.selectedModel = tl;
// set the timeline on the EntryList // set the timeline on the EntryList
this.entries.collection.timeline = tl; this.entries.collection.timelineModel = tl;
// update the last_timeline field of the user model // update the last_timeline field of the user model
this.user.model.set({last_timeline: tl.get('id')}); this.user.model.set({last_timeline: tl.get('id')});