diff --git a/.meteor/packages b/.meteor/packages index 25d50c7..72fcc9d 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -21,9 +21,9 @@ mizzao:animated-each@0.1.0 mizzao:openlayers@2.13.1 # Temporary crap that needs to be moved due to shitty migration -mrt:bootstrap-3@3.2.0-2 +mizzao:bootstrap-3 mizzao:jquery-ui -natestrauser:x-editable-bootstrap +natestrauser:x-editable-bootstrap@1.5.1 # Development packages mizzao:user-status diff --git a/.meteor/release b/.meteor/release index 2815f39..6165b3d 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR-CORE@0.9.0-rc12 +METEOR@0.9.0 diff --git a/.meteor/versions b/.meteor/versions index eb4b941..de2c7d3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -43,19 +43,19 @@ minimongo@1.0.1 mizzao:animated-each@0.1.0 mizzao:autocomplete@0.4.7 mizzao:bootboxjs@4.3.0 +mizzao:bootstrap-3@3.2.0 mizzao:build-fetcher@0.2.0 mizzao:jquery-ui@1.11.0 mizzao:openlayers@2.13.1 -mizzao:partitioner@0.5.2 +mizzao:partitioner@0.5.3 mizzao:sharejs@0.6.0 mizzao:timesync@0.2.2 mizzao:turkserver@0.0.0 mizzao:tutorials@0.6.3 mizzao:user-status@0.6.1 mongo-livedata@1.0.3 -mrt:bootstrap-3@3.2.0-2 -mrt:moment@2.6.0 -natestrauser:x-editable-bootstrap@1.5.2-meteor0.9.1 +mrt:moment@2.8.1 +natestrauser:x-editable-bootstrap@1.5.2 npm-bcrypt@0.7.7 observe-sequence@1.0.1 ordered-dict@1.0.0 diff --git a/client/views/events.coffee b/client/views/events.coffee index b6d2233..b0a610d 100644 --- a/client/views/events.coffee +++ b/client/views/events.coffee @@ -168,9 +168,17 @@ acceptDrop = (draggable) -> # drop check if the tweet is part of an event, below return false unless tweet? - # Don't accept drops to the same event - check the event as it will be more - # up to date than the one-shot data context being used to render the helper, - # which may have changed. + ### + Don't accept drops to the same event. There are two ways to do this: + + - check the event as it will be more up to date than the one-shot data + context being used to render the helper, which may have changed. + + - check the tweet to see if the event is attached. + + TODO we need to implement something that will allow for admin cleanup of + multi-tagged events in the ground truth. + ### return false if $.inArray(tweet._id, event.sources) >= 0 return true diff --git a/lib/data.coffee b/lib/data.coffee index fa39c5b..bdc29b6 100644 --- a/lib/data.coffee +++ b/lib/data.coffee @@ -37,12 +37,18 @@ TurkServer.partitionCollection(Notifications, { } }) +# Admin cannot edit unless it is a special ground truth instance +checkPermissions = -> + return unless TurkServer.isAdmin() + unless TurkServer.treatment().treatments[0] is "groundtruth" + throw new Meteor.Error(403, "Can't edit as admin") + Meteor.methods ### Data Methods ### dataHide: (tweetId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(tweetId, String) # Can't hide tagged events @@ -62,7 +68,7 @@ Meteor.methods return dataLink: (tweetId, eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(tweetId, String) check(eventId, String) @@ -86,7 +92,7 @@ Meteor.methods return dataUnlink: (tweetId, eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(tweetId, String) check(eventId, String) @@ -109,7 +115,7 @@ Meteor.methods # Dragging a tweet frome one event to another dataMove: (tweetId, fromEventId, toEventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(tweetId, String) check(fromEventId, String) check(toEventId, String) @@ -142,7 +148,7 @@ Meteor.methods Event Methods ### createEvent: (eventId, fields) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) obj = { @@ -170,7 +176,7 @@ Meteor.methods return editEvent: (eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) userId = Meteor.userId() @@ -198,7 +204,7 @@ Meteor.methods return updateEvent: (eventId, fields) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) Events.update eventId, @@ -218,7 +224,7 @@ Meteor.methods return unmapEvent: (eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) Events.update eventId, @@ -249,7 +255,7 @@ Meteor.methods return voteEvent: (eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) userId = Meteor.userId() @@ -291,7 +297,7 @@ Meteor.methods return deleteEvent: (eventId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(eventId, String) # Pull all tweet links @@ -318,7 +324,7 @@ Meteor.methods Doc Methods ### createDocument: (docName) -> - TurkServer.checkNotAdmin() + checkPermissions() check(docName, String) docId = Documents.insert @@ -336,7 +342,7 @@ Meteor.methods return docId renameDocument: (docId, newTitle) -> - TurkServer.checkNotAdmin() + checkPermissions() check(docId, String) check(newTitle, String) @@ -353,7 +359,7 @@ Meteor.methods return deleteDocument: (docId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(docId, String) Documents.update docId, @@ -371,7 +377,7 @@ Meteor.methods Chat Methods ### createChat: (roomName) -> - TurkServer.checkNotAdmin() + checkPermissions() check(roomName, String) roomId = ChatRooms.insert @@ -390,7 +396,7 @@ Meteor.methods return roomId renameChat: (roomId, newName) -> - TurkServer.checkNotAdmin() + checkPermissions() check(roomId, String) check(newName, String) @@ -415,7 +421,7 @@ Meteor.methods ### deleteChat: (roomId) -> - TurkServer.checkNotAdmin() + checkPermissions() check(roomId, String) if @isSimulation diff --git a/packages/.gitignore b/packages/.gitignore index 09c0140..04aeb44 100644 --- a/packages/.gitignore +++ b/packages/.gitignore @@ -11,3 +11,4 @@ /mrt:bootstrap-3 /mrt:moment /natestrauser:x-editable-bootstrap +/mizzao:bootstrap-3 diff --git a/packages/csv/versions.json b/packages/csv/versions.json index 3cd2df4..9d7fe5e 100644 --- a/packages/csv/versions.json +++ b/packages/csv/versions.json @@ -10,6 +10,6 @@ ] ], "pluginDependencies": [], - "toolVersion": "meteor-tool@1.0.16", + "toolVersion": "meteor-tool@1.0.25", "format": "1.0" } \ No newline at end of file diff --git a/server/analysis.coffee b/server/analysis.coffee new file mode 100644 index 0000000..7663744 --- /dev/null +++ b/server/analysis.coffee @@ -0,0 +1,119 @@ +# For debugging query errors: https://groups.google.com/forum/#!msg/meteor-talk/dnnEseBCCiE/l_LHsw-XAWsJ + +#Meteor.startup -> +# wrappedFind = Meteor.Collection.prototype.find +# +# console.log('[startup] wrapping Collection.find') +# +# Meteor.Collection.prototype.find = -> +# console.log(this._name + '.find', JSON.stringify(arguments)) +# return wrappedFind.apply(this, arguments) + +# Special groundtruth tag for these instances +TurkServer.ensureTreatmentExists + name: "groundtruth" + +Meteor.methods + # Create and populate a world that represents the Pablo data from groups of + # 16 and 32 + "cm-aggregate-pablo-gt": (force) -> + TurkServer.checkAdmin() + + instanceName = "groundtruth-pablo" + + if Experiments.findOne(instanceName)? + throw new Meteor.Error(403, "aggregated instance already exists") unless force? + # Reuse same tweets that are loaded + console.log ("removing old event aggregation data") + Events.direct.remove({_groupId: instanceName}) + instance = TurkServer.Instance.getInstance(instanceName) + + else + Experiments.upsert(instanceName, $set: {}) + + # First run, load new tweets + instance = TurkServer.Instance.getInstance(instanceName) + instance.bindOperation -> + Mapper.loadCSVTweets("PabloPh_UN_cm.csv", 2000) + console.log("Loaded new tweets") + + # Sleep a moment until all tweets are loaded, before proceeding + sleep = Meteor._wrapAsync((time, cb) -> Meteor.setTimeout (-> cb undefined), time) + sleep(2000) + + # Fake identifier for this instance to allow admin editing + Experiments.upsert(instanceName, $set: { treatments: [ "groundtruth" ]}) + + batch = Batches.findOne({name: "group sizes redux"}) + + exps = Experiments.find({ + batchId: batch._id + treatments: $in: [ "group_16", "group_32" ] + users: $exists: true + }).fetch() + + console.log "Found #{exps.length} experiments" + + instance.bindOperation -> + # Start with all tweets hidden. un-hide them if any group has them + # hidden or attached + Datastream.update({}, { + $set: {hidden: true, events: []} + }, {multi: true}) + + remapSources = (sources) -> + for source in sources + num = Datastream.direct.findOne(source).num + Datastream.direct.findOne({_groupId: instanceName, num})._id + + ### + Process existing groups as follows: + - Create all (non-deleted) events referencing the tweet with the same number + - Re-map sources and events to the new ids + - Unhide any tweets that are attached or not hidden + ### + + currentNum = 0 + + for exp in exps + console.log "Processing #{exp._id} (#{exp.users.length})" + + Events.direct.find({ + _groupId: exp._id + deleted: $exists: false + }, { + fields: {num: 0, editor: 0} + }).forEach (event) -> + + # omit the _id field, and transform the sources array + delete event._id + event.sources = remapSources(event.sources) + event.num = ++currentNum + + instance.bindOperation -> + # Insert the transformed event + newEventId = Events.insert(event) + # Push this event on to remapped tweets + Datastream.update({ + _id: { $in: event.sources } + }, { + $addToSet: { events: newEventId } + }, {multi: true}) + + console.log "Done copying events" + + # unhide any tweets that were not hidden or attached + remainingTweets = Datastream.direct.find({ + _groupId: exp._id, + hidden: { $exists: false } + }).map (tweet) -> tweet._id + + Datastream.direct.update({ + _id: $in: remapSources(remainingTweets) + }, { + $unset: {hidden: null} + }, { + multi: true + }) + + console.log "done" diff --git a/server/experiment_init.coffee b/server/experiment_init.coffee index eebb2ac..be2ae1e 100644 --- a/server/experiment_init.coffee +++ b/server/experiment_init.coffee @@ -1,36 +1,12 @@ -loadCSVTweets = (file, limit) -> - # csv is exported by the csv package - - Assets.getText file, (err, res) -> - throw err if err - - csv() - .from.string(res, { - columns: true - trim: true - }) - .to.array Meteor.bindEnvironment ( arr, count ) -> - - i = 0 - while i < limit and i < arr.length - Datastream.insert - num: i+1 # Indexed from 1 - text: arr[i].text - i++ - # console.log(i + " tweets inserted") - - , (e) -> - Meteor._debug "Exception while reading CSV:", e - TurkServer.initialize -> return if Datastream.find().count() > 0 if @instance.treatment().tutorialEnabled - loadCSVTweets("tutorial.csv", 10) + Mapper.loadCSVTweets("tutorial.csv", 10) else # Load initial tweets on first start # Meta-cleaned version has 1567 tweets - loadCSVTweets("PabloPh_UN_cm.csv", 2000) + Mapper.loadCSVTweets("PabloPh_UN_cm.csv", 2000) # Create a seed instructions document for the app docId = Documents.insert title: "Instructions" diff --git a/server/lib/mapper_utils.coffee b/server/lib/mapper_utils.coffee new file mode 100644 index 0000000..6bc98c3 --- /dev/null +++ b/server/lib/mapper_utils.coffee @@ -0,0 +1,25 @@ +@Mapper ?= {} + +Mapper.loadCSVTweets = (file, limit) -> + # csv is exported by the csv package + + Assets.getText file, (err, res) -> + throw err if err + + csv() + .from.string(res, { + columns: true + trim: true + }) + .to.array Meteor.bindEnvironment ( arr, count ) -> + + i = 0 + while i < limit and i < arr.length + Datastream.insert + num: i+1 # Indexed from 1 + text: arr[i].text + i++ + # console.log(i + " tweets inserted") + + , (e) -> + Meteor._debug "Exception while reading CSV:", e diff --git a/smart.json b/smart.json index 0a0ef92..c2c9d97 100644 --- a/smart.json +++ b/smart.json @@ -30,11 +30,8 @@ "mizzao:openlayers": { "path": "../mizzao:openlayers" }, - "mrt:bootstrap-3": { - "path": "../mrt:bootstrap-3" - }, - "mrt:moment": { - "path": "../mrt:moment" + "mizzao:bootstrap-3": { + "path": "../mizzao:bootstrap-3" }, "natestrauser:x-editable-bootstrap": { "path": "../natestrauser:x-editable-bootstrap" diff --git a/smart.lock b/smart.lock index dbd3941..47fcd78 100644 --- a/smart.lock +++ b/smart.lock @@ -32,11 +32,8 @@ "mizzao:openlayers": { "path": "../mizzao:openlayers" }, - "mrt:bootstrap-3": { - "path": "../mrt:bootstrap-3" - }, - "mrt:moment": { - "path": "../mrt:moment" + "mizzao:bootstrap-3": { + "path": "../mizzao:bootstrap-3" }, "natestrauser:x-editable-bootstrap": { "path": "../natestrauser:x-editable-bootstrap" @@ -73,11 +70,8 @@ "mizzao:openlayers": { "path": "../mizzao:openlayers" }, - "mrt:bootstrap-3": { - "path": "../mrt:bootstrap-3" - }, - "mrt:moment": { - "path": "../mrt:moment" + "mizzao:bootstrap-3": { + "path": "../mizzao:bootstrap-3" }, "natestrauser:x-editable-bootstrap": { "path": "../natestrauser:x-editable-bootstrap"