From c550b48da350f1d634be62aec881a8d7667a092a Mon Sep 17 00:00:00 2001 From: "Suzy (Sujung) Lee" Date: Wed, 18 Apr 2018 21:08:09 -0500 Subject: [PATCH 001/132] Setting up the basics --- imports/api/locations/methods.js | 134 ++++++++++---------- imports/api/testing/createNewExperiences.js | 79 ++++++++++++ imports/api/testing/testingconstants.js | 11 ++ imports/ui/pages/api_custom.html | 29 +++++ 4 files changed, 189 insertions(+), 64 deletions(-) diff --git a/imports/api/locations/methods.js b/imports/api/locations/methods.js index f4cfea3c..955f1f54 100644 --- a/imports/api/locations/methods.js +++ b/imports/api/locations/methods.js @@ -1,28 +1,26 @@ -import { ValidatedMethod } from 'meteor/mdg:validated-method'; -import { SimpleSchema } from 'meteor/aldeed:simple-schema'; -import { log } from '../logs.js'; -import { Locations } from './locations.js'; +import { ValidatedMethod } from "meteor/mdg:validated-method"; +import { SimpleSchema } from "meteor/aldeed:simple-schema"; +import { log } from "../logs.js"; +import { Locations } from "./locations.js"; -import { findMatchesForUser } from '../experiences/methods' -import { runCoordinatorAfterUserLocationChange } from '../coordinator/server/methods' +import { findMatchesForUser } from "../experiences/methods"; +import { runCoordinatorAfterUserLocationChange } from "../coordinator/server/methods"; import { updateAssignmentDbdAfterUserLocationChange } from "../coordinator/methods"; -import { getAffordancesFromLocation } from '../detectors/methods'; -import {CONFIG} from "../config"; -import {Availability} from "../coordinator/availability"; -import {Meteor} from "meteor/meteor"; -import {Location_log} from "./location_log"; -import {serverLog} from "../logs"; - - +import { getAffordancesFromLocation } from "../detectors/methods"; +import { CONFIG } from "../config"; +import { Availability } from "../coordinator/availability"; +import { Meteor } from "meteor/meteor"; +import { Location_log } from "./location_log"; +import { serverLog } from "../logs"; Meteor.methods({ - triggerUpdate(lat, lng, uid){ - onLocationUpdate(uid, lat, lng, function () { - serverLog.call({message: "triggering manual location update for: " + uid}); + triggerUpdate(lat, lng, uid) { + onLocationUpdate(uid, lat, lng, function() { + serverLog.call({ + message: "triggering manual location update for: " + uid + }); }); - } - }); /** @@ -34,47 +32,47 @@ Meteor.methods({ * @param lng {float} longitude of new location */ export const onLocationUpdate = (uid, lat, lng, callback) => { - //TODO: this could def be a clearner call or its own function let availabilityObjects = Availability.find().fetch(); - _.forEach(availabilityObjects, (av) => { - _.forEach(av.needUserMaps, (needEntry) => { - Availability.update({ - _id: av._id, - 'needUserMaps.needName': needEntry.needName, - }, { - $pull: {'needUserMaps.$.uids': uid} - }); + _.forEach(availabilityObjects, av => { + _.forEach(av.needUserMaps, needEntry => { + Availability.update( + { + _id: av._id, + "needUserMaps.needName": needEntry.needName + }, + { + $pull: { "needUserMaps.$.uids": uid } + } + ); }); - }); - getAffordancesFromLocation(lat, lng, function (affordances) { + getAffordancesFromLocation(lat, lng, function(affordances) { let user = Meteor.users.findOne(uid); - if(user){ + if (user) { let userAffordances = user.profile.staticAffordances; affordances = Object.assign({}, affordances, userAffordances); updateLocationInDb(uid, lat, lng, affordances); callback(); - Meteor.setTimeout(function(){ - let newAffs = Locations.findOne({uid: user._id}).affordances; - let sharedKeys = _.intersection(Object.keys(newAffs), Object.keys(affordances)); + Meteor.setTimeout(function() { + let newAffs = Locations.findOne({ uid: user._id }).affordances; + let sharedKeys = _.intersection( + Object.keys(newAffs), + Object.keys(affordances) + ); let sharedAffs = []; - _.forEach(sharedKeys, (key)=>{ + _.forEach(sharedKeys, key => { sharedAffs[key] = newAffs[key]; }); updateAssignmentDbdAfterUserLocationChange(uid, sharedAffs); sendToMatcher(uid, sharedAffs); - - }, 5*60000); - + }, 5 * 0); } - }); - }; /** @@ -92,7 +90,7 @@ const sendToMatcher = (uid, affordances) => { if (userCanParticipate) { let availabilityDictionary = findMatchesForUser(uid, affordances); - runCoordinatorAfterUserLocationChange(uid, availabilityDictionary); + runCoordinatorAfterUserLocationChange(uid, availabilityDictionary); } }; @@ -105,7 +103,7 @@ const sendToMatcher = (uid, affordances) => { * @param debug {boolean} choose to run in debug mode or not * @returns {boolean} whether a user can participate in an experience */ -const userIsAvailableToParticipate = (uid) => { +const userIsAvailableToParticipate = uid => { let time = 60 * 1000; if (CONFIG.MODE === "DEV") { @@ -121,7 +119,8 @@ const userIsAvailableToParticipate = (uid) => { // // console.log("IS USER AVAIABLE TO PARTICPATE", (Date.now() - Meteor.users.findOne(uid).profile.lastNotified) > time) - return (Date.now() - Meteor.users.findOne(uid).profile.lastNotified) > time}; + return Date.now() - Meteor.users.findOne(uid).profile.lastNotified > time; +}; /** * Updates the location for a user in the database. @@ -133,37 +132,44 @@ const userIsAvailableToParticipate = (uid) => { */ const updateLocationInDb = (uid, lat, lng, affordances) => { const entry = Locations.findOne({ uid: uid }); - if (entry) { - Locations.update(entry._id, { - $set: { + if (entry) { + Locations.update( + entry._id, + { + $set: { + lat: lat, + lng: lng, + timestamp: Date.now(), + affordances: affordances + } + }, + err => { + if (err) { + log.error("Locations/methods, can't update a location", err); + } + } + ); + } else { + Locations.insert( + { + uid: uid, lat: lat, lng: lng, timestamp: Date.now(), affordances: affordances + }, + err => { + if (err) { + log.error("Locations/methods, can't add a new location", err); + } } - }, (err) => { - if (err) { - log.error("Locations/methods, can't update a location", err); - } - }); - } else { - Locations.insert({ - uid: uid, - lat: lat, - lng: lng, - timestamp: Date.now(), - affordances: affordances - }, (err) => { - if (err) { - log.error("Locations/methods, can't add a new location", err); - } - }); + ); } Location_log.insert({ uid: uid, lat: lat, lng: lng, timestamp: Date.now(), - affordances: affordances, + affordances: affordances }); }; diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js index 58dcc6f6..a089099f 100644 --- a/imports/api/testing/createNewExperiences.js +++ b/imports/api/testing/createNewExperiences.js @@ -31,6 +31,9 @@ Meteor.methods({ }, startFreshSpookyNevilleStorytime() { createNewSpookyNevilleStorytime(); + }, + startFreshFoodFight() { + createNewFoodFight(); } }); @@ -718,3 +721,79 @@ function createNewSpookyNevilleStorytime() { let incident = createIncidentFromExperience(exp); startRunningIncident(incident); } + +function createNewFoodFight() { + + let newVars = JSON.parse( + JSON.stringify(CONSTANTS.DETECTORS["eating_alone"]["variables"]) + ); + newVars.push("var goodVariety1;"); + + let det = { + _id: eG4no7zpSnthwwcv9, + description: + CONSTANTS.DETECTORS["eating_alone"].description + "goodVariety1", + variables: newVars, + rules: [ + "(" + + CONSTANTS.DETECTORS["eating_alone"].rules[0] + + " ) && goodVariety1;" + ] + }; + + Detectors.insert(det); + + + let startStage2 = function(sub) { + let need1 = { + needName: "warning1", + situation: { detector: "", number: "1" }, + toPass: { + instruction: "your diet sucks" + }, + numberNeeded: 2 + }; + + let need2 = { + needName: "good1", + situation: { detector: "eG4no7zpSnthwwcv9", number: "1" }, + toPass: { + instruction: "We're having a food fight! Share what you're eating" + }, + numberNeeded: 8 + } + + addNeed(sub.eid, need1); + addNeed(sub.eid, need2); +} + + let exp = { + _id: Random.id(), + name: "Food Fight", + participateTemplate: "submitText", + resultsTemplate: "foodFightResult", + contributionTypes: [ + { + needName: "foodFightStage1", + situation: { detector: "eG4no7zpSnthwwcv6", number: "1" }, + toPass: { + instruction: "We're having a food fight! Share what you're eating" + + }, + numberNeeded: 10 + } + ], + description: "food", + notificationText: "Help write a spooky Neville Longbottom story!", + callbacks: [ + { + trigger: "cb.needFinished("foodFightStage1")", + function: startStage2.toString() + } + ] + }; + + Experiences.insert(exp); + let incident = createIncidentFromExperience(exp); + startRunningIncident(incident); +} diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index 1718d1e1..d95c1a12 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -184,6 +184,17 @@ let DETECTORS = { '(men_s_hair_salons || hair_salons || hair_stylists || blow_dry_out_services || barbers)' ] }, + 'eating_alone': { + '_id': 'eG4no7zpSnthwwcv6', + 'description': 'eating_alone', + 'variables': [ + 'var food_court;', + 'var dinning_hall;', + ], + 'rules': [ + '(food_court || dinning_hall)' + ] + } 'gas_station': { '_id': 'xZBgjwdPw8rtg86eo', 'description': 'gas_stations', diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index 26a3125f..f00a91e8 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -126,6 +126,35 @@ + + + + + From fafe1c1c8fbcabe4f9de8fe1a3a2d7839e229ff9 Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Tue, 8 May 2018 16:29:25 -0500 Subject: [PATCH 003/132] Finished writing food fight --- imports/api/testing/createNewExperiences.js | 813 ++++++++++---------- imports/api/testing/testingconstants.js | 2 +- 2 files changed, 407 insertions(+), 408 deletions(-) diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js index 823aabef..8b40c8f7 100644 --- a/imports/api/testing/createNewExperiences.js +++ b/imports/api/testing/createNewExperiences.js @@ -12,6 +12,8 @@ import { Meteor } from "meteor/meteor"; Meteor.methods({ startFreshBumped() { + console.log("GOING IN BUMPED"); + createNewBumped(); }, startFreshBumped2() { @@ -23,9 +25,6 @@ Meteor.methods({ startFreshNatureHunt() { createNewNatureHunt(); }, - startFreshFoodFight() { - createNewFoodFight(); - }, startFreshSpookyStorytime() { createNewSpookyStorytime(); }, @@ -38,6 +37,7 @@ Meteor.methods({ }); function createNewBumped() { + console.log("IN BUMPED"); let experience = { name: "Bumped", participateTemplate: "bumped", @@ -61,23 +61,23 @@ function createNewBumped() { "See a photo from who you bumped into!", "", "/apicustomresults/" + sub.iid + "/" + sub.eid - ); + ); }; let places = [ - ["bar", "at a bar"], - ["coffee", "at a coffee shop"], - ["grocery", "at a grocery store"], - ["restaurant", "at a restaurant"], - ["train", "commuting"], - ["exercising", "exercising"] + ["bar", "at a bar"], + ["coffee", "at a coffee shop"], + ["grocery", "at a grocery store"], + ["restaurant", "at a restaurant"], + ["train", "commuting"], + ["exercising", "exercising"] ]; _.forEach(places, place => { /* For each value in places array (defined above) */ let newVars = JSON.parse( JSON.stringify(CONSTANTS.DETECTORS[place[0]]["variables"]) - ); + ); newVars.push("var lovesDTR;"); newVars.push("var lovesDTRAlumni;"); @@ -85,12 +85,12 @@ function createNewBumped() { let detector = { _id: Random.id(), description: - CONSTANTS.DETECTORS[place[0]].description + "lovesDTR_lovesDTRAlumni", + CONSTANTS.DETECTORS[place[0]].description + "lovesDTR_lovesDTRAlumni", variables: newVars, rules: [ - "((" + - CONSTANTS.DETECTORS[place[0]].rules[0] + - ") && (lovesDTR || lovesDTRAlumni) );" + "((" + + CONSTANTS.DETECTORS[place[0]].rules[0] + + ") && (lovesDTR || lovesDTRAlumni) );" ] }; CONSTANTS.DETECTORS[place[0] + "lovesDTR_lovesDTRAlumni"] = detector; @@ -109,11 +109,11 @@ function createNewBumped() { }; let callback = { trigger: - "cb.numberOfSubmissions('" + - place[0] + - "lovesDTR_lovesDTRAlumni" + - i + - "') === 2", + "cb.numberOfSubmissions('" + + place[0] + + "lovesDTR_lovesDTRAlumni" + + i + + "') === 2", function: bumpedCallback.toString() }; @@ -151,26 +151,26 @@ function createNewCheers() { "See what the other person is drinking!", "", "/apicustomresults/" + sub.iid + "/" + sub.eid - ); + ); }; let places = [["bar", "at a bar"]]; _.forEach(places, place => { let newVars = JSON.parse( JSON.stringify(CONSTANTS.DETECTORS[place[0]]["variables"]) - ); + ); newVars.push("var lovesDTR;"); newVars.push("var lovesDTRAlumni;"); let detector = { _id: Random.id(), description: - CONSTANTS.DETECTORS[place[0]].description + "lovesDTR_lovesDTRAlumni", + CONSTANTS.DETECTORS[place[0]].description + "lovesDTR_lovesDTRAlumni", variables: newVars, rules: [ - "((" + - CONSTANTS.DETECTORS[place[0]].rules[0] + - ") && (lovesDTR || lovesDTRAlumni) );" + "((" + + CONSTANTS.DETECTORS[place[0]].rules[0] + + ") && (lovesDTR || lovesDTRAlumni) );" ] }; CONSTANTS.DETECTORS[place[0] + "lovesDTR_lovesDTRAlumni"] = detector; @@ -187,11 +187,11 @@ function createNewCheers() { }; let callback = { trigger: - "cb.numberOfSubmissions('" + - place[0] + - "lovesDTR_lovesDTRAlumni" + - i + - "') === 2", + "cb.numberOfSubmissions('" + + place[0] + + "lovesDTR_lovesDTRAlumni" + + i + + "') === 2", function: bumpedCallback.toString() }; @@ -212,48 +212,48 @@ function createNewStorytime() { participateTemplate: "storyPage", resultsTemplate: "storybook", contributionTypes: [ - { - needName: "pageOne", - situation: { - detector: "x7EgLErQx3qmiemqt", - number: 1 - }, - toPass: { - instruction: - "Harry Potter looked up at the clouds swirling above him.", - firstSentence: - "Harry Potter looked up at the clouds swirling above him.", - dropdownChoices: { - name: "affordance", - options: [ - ["Drinking butterbeer", "N3uajhH3chDssFq3r"], - ["Hogwarts Express at Platform 9 3/4", "Ly9vMvepymC4QNJqA"], - [ - "Sneaking around at night under the invisibility cloak", - "AcstpXRyNFhmgPDfF" - ], - ["Dinner at the Great Hall", "AKxSxuYBFqKP3auie"], - ["Hogwarts Castle", "LTnK6z94KQTJKTmZ8"], - ["Training in the Room of Requirement ", "H5P9ga8HHpCbxBza8"] - ] - } - }, - numberNeeded: 1 - } + { + needName: "pageOne", + situation: { + detector: "x7EgLErQx3qmiemqt", + number: 1 + }, + toPass: { + instruction: + "Harry Potter looked up at the clouds swirling above him.", + firstSentence: + "Harry Potter looked up at the clouds swirling above him.", + dropdownChoices: { + name: "affordance", + options: [ + ["Drinking butterbeer", "N3uajhH3chDssFq3r"], + ["Hogwarts Express at Platform 9 3/4", "Ly9vMvepymC4QNJqA"], + [ + "Sneaking around at night under the invisibility cloak", + "AcstpXRyNFhmgPDfF" + ], + ["Dinner at the Great Hall", "AKxSxuYBFqKP3auie"], + ["Hogwarts Castle", "LTnK6z94KQTJKTmZ8"], + ["Training in the Room of Requirement ", "H5P9ga8HHpCbxBza8"] + ] + } + }, + numberNeeded: 1 + } ], description: "We're writing a Harry Potter spinoff story!", notificationText: "Help us write a story!", callbacks: [ - { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", - function: - "function (sub) { // 445\n Meteor.users.update({ // 447\n _id: sub.uid // 448\n }, { // 447\n $set: { // 450\n 'profile.staticAffordances.participatedInStorytime': true // 450\n } // 450\n }); // 449\n var affordance = sub.content.affordance; // 453\n var options = [['Drinking butterbeer', CONSTANTS.DETECTORS.beer_storytime._id], ['Hogwarts Express at Platform 9 3/4', CONSTANTS.DETECTORS.train_storytime._id], ['Forbidden Forest', CONSTANTS.DETECTORS.forest_storytime._id], ['Dinner at the Great Hall', CONSTANTS.DETECTORS.dinning_hall_storytime._id], ['Hogwarts Castle', CONSTANTS.DETECTORS.castle_storytime._id], ['Quidditch Pitch', CONSTANTS.DETECTORS.field_storytime._id], ['Training in the Room of Requirement ', CONSTANTS.DETECTORS.gym_storytime._id]];\n options = options.filter(function (x) { // 464\n return x[1] !== affordance; // 465\n }); // 466\n var needName = 'page' + Random.id(3); // 468\n //\n if (cb.numberOfSubmissions() == 7) { // 469\n needName = 'pageFinal'; // 470\n } // 471\n //\n var contribution = { // 472\n needName: needName, // 473\n situation: { // 473\n detector: affordance, // 473\n number: '1' // 473\n }, // 473\n toPass: { // 474\n instruction: sub.content.sentence, // 475\n dropdownChoices: { // 476\n name: 'affordance', // 476\n options: options // 476\n } // 476\n }, // 474\n numberNeeded: 1 // 477\n }; // 472\n addContribution(sub.iid, contribution); // 479\n }" - }, - { - trigger: "cb.incidentFinished()", - function: - "function (sub) { // 512\n var uids = Submissions.find({ // 513\n iid: sub.iid // 513\n }).fetch().map(function (x) { // 513\n return x.uid; // 514\n }); // 515\n notify(uids, sub.iid, 'Our story is finally complete. Click here to read it!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n }" - } + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", + function: + "function (sub) { // 445\n Meteor.users.update({ // 447\n _id: sub.uid // 448\n }, { // 447\n $set: { // 450\n 'profile.staticAffordances.participatedInStorytime': true // 450\n } // 450\n }); // 449\n var affordance = sub.content.affordance; // 453\n var options = [['Drinking butterbeer', CONSTANTS.DETECTORS.beer_storytime._id], ['Hogwarts Express at Platform 9 3/4', CONSTANTS.DETECTORS.train_storytime._id], ['Forbidden Forest', CONSTANTS.DETECTORS.forest_storytime._id], ['Dinner at the Great Hall', CONSTANTS.DETECTORS.dinning_hall_storytime._id], ['Hogwarts Castle', CONSTANTS.DETECTORS.castle_storytime._id], ['Quidditch Pitch', CONSTANTS.DETECTORS.field_storytime._id], ['Training in the Room of Requirement ', CONSTANTS.DETECTORS.gym_storytime._id]];\n options = options.filter(function (x) { // 464\n return x[1] !== affordance; // 465\n }); // 466\n var needName = 'page' + Random.id(3); // 468\n //\n if (cb.numberOfSubmissions() == 7) { // 469\n needName = 'pageFinal'; // 470\n } // 471\n //\n var contribution = { // 472\n needName: needName, // 473\n situation: { // 473\n detector: affordance, // 473\n number: '1' // 473\n }, // 473\n toPass: { // 474\n instruction: sub.content.sentence, // 475\n dropdownChoices: { // 476\n name: 'affordance', // 476\n options: options // 476\n } // 476\n }, // 474\n numberNeeded: 1 // 477\n }; // 472\n addContribution(sub.iid, contribution); // 479\n }" + }, + { + trigger: "cb.incidentFinished()", + function: + "function (sub) { // 512\n var uids = Submissions.find({ // 513\n iid: sub.iid // 513\n }).fetch().map(function (x) { // 513\n return x.uid; // 514\n }); // 515\n notify(uids, sub.iid, 'Our story is finally complete. Click here to read it!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n }" + } ] }; Experiences.insert(exp); @@ -268,114 +268,114 @@ function createNewNatureHunt() { participateTemplate: "scavengerHuntParticipate", resultsTemplate: "scavengerHunt", contributionTypes: [ - { - needName: "tree", - situation: { - detector: "FfZnzP72ip4SLY4eR", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of a tree?" - }, - numberNeeded: 1 + { + needName: "tree", + situation: { + detector: "FfZnzP72ip4SLY4eR", + number: 1 }, - { - needName: "leaf", - situation: { - detector: "FfZnzP72ip4SLY4eR", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of a leaf?" - }, - numberNeeded: 1 + toPass: { + instruction: "Can you take a photo of a tree?" }, - { - needName: "grass", - situation: { - detector: "rEbK6WMQnPPAGAXMX", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the grass?" - }, - numberNeeded: 1 + numberNeeded: 1 + }, + { + needName: "leaf", + situation: { + detector: "FfZnzP72ip4SLY4eR", + number: 1 }, - { - needName: "lake", - situation: { - detector: "9iEpW4mb4ysHY5thP", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the lake?" - }, - numberNeeded: 1 + toPass: { + instruction: "Can you take a photo of a leaf?" }, - { - needName: "moon", - situation: { - detector: "Wth3TB9Lcf6me6vgy", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the moon?" - }, - numberNeeded: 1 + numberNeeded: 1 + }, + { + needName: "grass", + situation: { + detector: "rEbK6WMQnPPAGAXMX", + number: 1 }, - { - needName: "sun", - situation: { - detector: "6vyrBtdDAyRArMasj", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of a the sun?" - }, - numberNeeded: 1 + toPass: { + instruction: "Can you take a photo of the grass?" }, - { - needName: "blueSky", - situation: { - detector: "6vyrBtdDAyRArMasj", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the blue sky?" - }, - numberNeeded: 1 + numberNeeded: 1 + }, + { + needName: "lake", + situation: { + detector: "9iEpW4mb4ysHY5thP", + number: 1 }, - { - needName: "clouds", - situation: { - detector: "sorCvK53fyi5orAmj", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the clouds?" - }, - numberNeeded: 1 + toPass: { + instruction: "Can you take a photo of the lake?" }, - { - needName: "puddle", - situation: { - detector: "puLHKiGkLCJWpKc62", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of the puddle?" - }, - numberNeeded: 1 - } + numberNeeded: 1 + }, + { + needName: "moon", + situation: { + detector: "Wth3TB9Lcf6me6vgy", + number: 1 + }, + toPass: { + instruction: "Can you take a photo of the moon?" + }, + numberNeeded: 1 + }, + { + needName: "sun", + situation: { + detector: "6vyrBtdDAyRArMasj", + number: 1 + }, + toPass: { + instruction: "Can you take a photo of a the sun?" + }, + numberNeeded: 1 + }, + { + needName: "blueSky", + situation: { + detector: "6vyrBtdDAyRArMasj", + number: 1 + }, + toPass: { + instruction: "Can you take a photo of the blue sky?" + }, + numberNeeded: 1 + }, + { + needName: "clouds", + situation: { + detector: "sorCvK53fyi5orAmj", + number: 1 + }, + toPass: { + instruction: "Can you take a photo of the clouds?" + }, + numberNeeded: 1 + }, + { + needName: "puddle", + situation: { + detector: "puLHKiGkLCJWpKc62", + number: 1 + }, + toPass: { + instruction: "Can you take a photo of the puddle?" + }, + numberNeeded: 1 + } ], description: "Help us complete a nature scavenger hunt", notificationText: "Help us out with our nature scavenger hunt", callbacks: [ - { - trigger: "cb.incidentFinished()", - function: - "function (sub) { // 611\n var uids = Submissions.find({ // 612\n iid: sub.iid // 612\n }).fetch().map(function (x) { // 612\n return x.uid; // 613\n }); // 614\n notify(uids, sub.iid, 'Wooh! All the scavenger hunt items were found. Click here to see all of them.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n}" - } + { + trigger: "cb.incidentFinished()", + function: + "function (sub) { // 611\n var uids = Submissions.find({ // 612\n iid: sub.iid // 612\n }).fetch().map(function (x) { // 612\n return x.uid; // 613\n }); // 614\n notify(uids, sub.iid, 'Wooh! All the scavenger hunt items were found. Click here to see all of them.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n}" + } ] }; @@ -384,76 +384,76 @@ function createNewNatureHunt() { startRunningIncident(incident); } -function createNewFoodFight() { - let exp = { - _id: Random.id(), - name: "Food Fight!", - participateTemplate: "scavengerHuntParticipate", - resultsTemplate: "scavengerHunt", - contributionTypes: [ - { - needName: "foodPhoto", - situation: { - /* ID of 'restaurant' in testingconstants */ - detector: "MzyBGuc6fLGR8Kjii", - /* Eating by yourself */ - number: 1 - }, - toPass: { - instruction: "Can you take a photo of what you're eating?" - }, - numberNeeded: 1 - }, - { - needName: "foodPhoto", - situation: { - detector: "MzyBGuc6fLGR8Kjii", - number: 1 - }, - toPass: { - instruction: "Can you take a photo of what you're eating?" - }, - numberNeeded: 1 - } - ], - description: "Food fight!", - notificationText: "Food fight!", - callbacks: [ - { - trigger: "cb.incidentFinished()", - function: - "function (sub) { // 611\n var uids = Submissions.find({ // 612\n iid: sub.iid // 612\n }).fetch().map(function (x) { // 612\n return x.uid; // 613\n }); // 614\n notify(uids, sub.iid, 'Wooh! Both participants have attacked each other with food pics', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n}" - } - ] - }; - - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); -} +// function createNewFoodFight() { +// let exp = { +// _id: Random.id(), +// name: "Food Fight!", +// participateTemplate: "scavengerHuntParticipate", +// resultsTemplate: "scavengerHunt", +// contributionTypes: [ +// { +// needName: "foodPhoto", +// situation: { +// /* ID of 'restaurant' in testingconstants */ +// detector: "MzyBGuc6fLGR8Kjii", +// /* Eating by yourself */ +// number: 1 +// }, +// toPass: { +// instruction: "Can you take a photo of what you're eating?" +// }, +// numberNeeded: 1 +// }, +// { +// needName: "foodPhoto", +// situation: { +// detector: "MzyBGuc6fLGR8Kjii", +// number: 1 +// }, +// toPass: { +// instruction: "Can you take a photo of what you're eating?" +// }, +// numberNeeded: 1 +// } +// ], +// description: "Food fight!", +// notificationText: "Food fight!", +// callbacks: [ +// { +// trigger: "cb.incidentFinished()", +// function: +// "function (sub) { // 611\n var uids = Submissions.find({ // 612\n iid: sub.iid // 612\n }).fetch().map(function (x) { // 612\n return x.uid; // 613\n }); // 614\n notify(uids, sub.iid, 'Wooh! Both participants have attacked each other with food pics', '', '/apicustomresults/' + sub.iid + '/' + sub.eid);\n}" +// } +// ] +// }; + +// Experiences.insert(exp); +// let incident = createIncidentFromExperience(exp); +// startRunningIncident(incident); +// } function createNewSpookyStorytime() { let storytimeCallback = function(sub) { Meteor.users.update( - { - _id: sub.uid - }, - { - $set: { - "profile.staticAffordances.participatedInSpookyStorytime": true - } + { + _id: sub.uid + }, + { + $set: { + "profile.staticAffordances.participatedInSpookyStorytime": true } + } ); let affordance = sub.content.affordance; let options = [ - ["Wolves howling at a full moon", "Dw9z8eTBvvF6EeqaR"], - ["Ominous clouds swirling above", "eqsBY5BBRZsFWfsS4"], - ["Deserted neighborhood businesses", "Hewrfn8R87Z9EfjKh"], - ["Eerily quiet day", "eqsBY5BBRZsFWfsS4"], - ["Night falling a little too quickly", "3EML6ZvzjiKTK3Myy"], - ["Haunted coffee shop", "vj4M9wajY9HzgmM48"] + ["Wolves howling at a full moon", "Dw9z8eTBvvF6EeqaR"], + ["Ominous clouds swirling above", "eqsBY5BBRZsFWfsS4"], + ["Deserted neighborhood businesses", "Hewrfn8R87Z9EfjKh"], + ["Eerily quiet day", "eqsBY5BBRZsFWfsS4"], + ["Night falling a little too quickly", "3EML6ZvzjiKTK3Myy"], + ["Haunted coffee shop", "vj4M9wajY9HzgmM48"] ]; /* remove options just chosen */ @@ -483,18 +483,18 @@ function createNewSpookyStorytime() { let places = ["night", "niceish_day", "restaurant", "sunset", "coffee"]; let detectorIds = [ - "Dw9z8eTBvvF6EeqaR", - "eqsBY5BBRZsFWfsS4", - "Hewrfn8R87Z9EfjKh", - "XHj47XpSWEE6Yrmm4", - "3EML6ZvzjiKTK3Myy", - "vj4M9wajY9HzgmM48" + "Dw9z8eTBvvF6EeqaR", + "eqsBY5BBRZsFWfsS4", + "Hewrfn8R87Z9EfjKh", + "XHj47XpSWEE6Yrmm4", + "3EML6ZvzjiKTK3Myy", + "vj4M9wajY9HzgmM48" ]; let i = 0; _.forEach(places, place => { let newVars = JSON.parse( JSON.stringify(CONSTANTS.DETECTORS[place]["variables"]) - ); + ); newVars.push("var participatedInSpookyStorytime;"); let det = { @@ -502,9 +502,9 @@ function createNewSpookyStorytime() { description: CONSTANTS.DETECTORS[place].description + "_SpookyStorytime", variables: newVars, rules: [ - "(" + - CONSTANTS.DETECTORS[place].rules[0] + - " ) && !participatedInSpookyStorytime;" + "(" + + CONSTANTS.DETECTORS[place].rules[0] + + " ) && !participatedInSpookyStorytime;" ] }; Detectors.insert(det); @@ -513,16 +513,16 @@ function createNewSpookyStorytime() { }); let dropdownOptions = [ - ["Wolves howling at a full moon", "Dw9z8eTBvvF6EeqaR"], - ["Ominous clouds swirling above", "eqsBY5BBRZsFWfsS4"], - ["Deserted neighborhood businesses", "Hewrfn8R87Z9EfjKh"], - ["Eerily quiet day", "XHj47XpSWEE6Yrmm4"], - ["Night falling a little too quickly", "3EML6ZvzjiKTK3Myy"], - ["Haunted coffee shop", "vj4M9wajY9HzgmM48"] + ["Wolves howling at a full moon", "Dw9z8eTBvvF6EeqaR"], + ["Ominous clouds swirling above", "eqsBY5BBRZsFWfsS4"], + ["Deserted neighborhood businesses", "Hewrfn8R87Z9EfjKh"], + ["Eerily quiet day", "XHj47XpSWEE6Yrmm4"], + ["Night falling a little too quickly", "3EML6ZvzjiKTK3Myy"], + ["Haunted coffee shop", "vj4M9wajY9HzgmM48"] ]; let firstSentence = - "Something was happening in this small town of seemingly happy people."; + "Something was happening in this small town of seemingly happy people."; let sendNotification = function(sub) { let uids = Submissions.find({ iid: sub.iid }).fetch().map(function(x) { @@ -534,7 +534,7 @@ function createNewSpookyStorytime() { "Our spooky story is finally complete. Click here to read it!", "", "/apicustomresults/" + sub.iid + "/" + sub.eid - ); + ); }; let exp = { @@ -543,31 +543,31 @@ function createNewSpookyStorytime() { participateTemplate: "storyPage", resultsTemplate: "storybook", contributionTypes: [ - { - needName: "pageOne", - situation: { detector: "x7EgLErQx3qmiemqt", number: "1" }, - toPass: { - instruction: firstSentence, - firstSentence: firstSentence, - dropdownChoices: { - name: "affordance", - options: dropdownOptions - } - }, - numberNeeded: 1 - } + { + needName: "pageOne", + situation: { detector: "x7EgLErQx3qmiemqt", number: "1" }, + toPass: { + instruction: firstSentence, + firstSentence: firstSentence, + dropdownChoices: { + name: "affordance", + options: dropdownOptions + } + }, + numberNeeded: 1 + } ], description: "We're writing a spooky story", notificationText: "Help write a spooky story!", callbacks: [ - { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", - function: storytimeCallback.toString() - }, - { - trigger: "cb.incidentFinished()", - function: sendNotification.toString() - } + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", + function: storytimeCallback.toString() + }, + { + trigger: "cb.incidentFinished()", + function: sendNotification.toString() + } ] }; @@ -579,29 +579,29 @@ function createNewSpookyStorytime() { function createNewSpookyNevilleStorytime() { let storytimeCallback = function(sub) { Meteor.users.update( - { - _id: sub.uid - }, - { - $set: { - "profile.staticAffordances.participatedInSpookyHarryStorytime": true - } + { + _id: sub.uid + }, + { + $set: { + "profile.staticAffordances.participatedInSpookyHarryStorytime": true } + } ); let affordance = sub.content.affordance; let options = [ - [ - "Sneaking around in the invisibility cloak after hours", - "F8YqP3AEbyguQMJ9i" - ], - ["Werewolves howling at the moon", "F8YqP3AEbyguQMJ9i"], - ["Getting food in Diagon Alley", "yxQP8QrCdAWakjMaY"], - ["Eating in the Great Hall", "yxQP8QrCdAWakjMaY"], - ["Exploring the Hogwarts grounds", "ueBZrF5mCRrcFBc8g"], - ["Drinking coffee while studying for O.W.L.S.", "DPxfkTQQFggzNJBXD"], - ["Looking for magical beasts flying overhead", "ueBZrF5mCRrcFBc8g"] + [ + "Sneaking around in the invisibility cloak after hours", + "F8YqP3AEbyguQMJ9i" + ], + ["Werewolves howling at the moon", "F8YqP3AEbyguQMJ9i"], + ["Getting food in Diagon Alley", "yxQP8QrCdAWakjMaY"], + ["Eating in the Great Hall", "yxQP8QrCdAWakjMaY"], + ["Exploring the Hogwarts grounds", "ueBZrF5mCRrcFBc8g"], + ["Drinking coffee while studying for O.W.L.S.", "DPxfkTQQFggzNJBXD"], + ["Looking for magical beasts flying overhead", "ueBZrF5mCRrcFBc8g"] ]; options = options.filter(function(x) { @@ -626,27 +626,27 @@ function createNewSpookyNevilleStorytime() { let places = ["night", "niceish_day", "restaurant", "coffee"]; let detectorIds = [ - "F8YqP3AEbyguQMJ9i", - "ueBZrF5mCRrcFBc8g", - "yxQP8QrCdAWakjMaY", - "DPxfkTQQFggzNJBXD" + "F8YqP3AEbyguQMJ9i", + "ueBZrF5mCRrcFBc8g", + "yxQP8QrCdAWakjMaY", + "DPxfkTQQFggzNJBXD" ]; let i = 0; _.forEach(places, place => { let newVars = JSON.parse( JSON.stringify(CONSTANTS.DETECTORS[place]["variables"]) - ); + ); newVars.push("var participatedInSpookyHarryStorytime;"); let det = { _id: detectorIds[i], description: - CONSTANTS.DETECTORS[place].description + "_SpookyHarryStorytime", + CONSTANTS.DETECTORS[place].description + "_SpookyHarryStorytime", variables: newVars, rules: [ - "(" + - CONSTANTS.DETECTORS[place].rules[0] + - " ) && !participatedInSpookyHarryStorytime;" + "(" + + CONSTANTS.DETECTORS[place].rules[0] + + " ) && !participatedInSpookyHarryStorytime;" ] }; Detectors.insert(det); @@ -655,20 +655,20 @@ function createNewSpookyNevilleStorytime() { }); let dropdownOptions = [ - [ - "Sneaking around in the invisibility cloak after hours", - "F8YqP3AEbyguQMJ9i" - ], - ["Werewolves howling at the moon", "F8YqP3AEbyguQMJ9i"], - ["Getting food in Diagon Alley", "yxQP8QrCdAWakjMaY"], - ["Eating in the Great Hall", "yxQP8QrCdAWakjMaY"], - ["Exploring the Hogwarts grounds", "ueBZrF5mCRrcFBc8g"], - ["Drinking coffee while studying for O.W.L.S.", "DPxfkTQQFggzNJBXD"], - ["Looking for magical beasts flying overhead", "ueBZrF5mCRrcFBc8g"] + [ + "Sneaking around in the invisibility cloak after hours", + "F8YqP3AEbyguQMJ9i" + ], + ["Werewolves howling at the moon", "F8YqP3AEbyguQMJ9i"], + ["Getting food in Diagon Alley", "yxQP8QrCdAWakjMaY"], + ["Eating in the Great Hall", "yxQP8QrCdAWakjMaY"], + ["Exploring the Hogwarts grounds", "ueBZrF5mCRrcFBc8g"], + ["Drinking coffee while studying for O.W.L.S.", "DPxfkTQQFggzNJBXD"], + ["Looking for magical beasts flying overhead", "ueBZrF5mCRrcFBc8g"] ]; let firstSentence = - "Neville Longbottom looked out the castle into the darkness of the night as he snuck out of the common room"; + "Neville Longbottom looked out the castle into the darkness of the night as he snuck out of the common room"; let sendNotification = function(sub) { let uids = Submissions.find({ iid: sub.iid }).fetch().map(function(x) { @@ -680,7 +680,7 @@ function createNewSpookyNevilleStorytime() { "Our spooky Neville Longbottom story is finally complete. Click here to read it!", "", "/apicustomresults/" + sub.iid + "/" + sub.eid - ); + ); }; let exp = { @@ -689,31 +689,31 @@ function createNewSpookyNevilleStorytime() { participateTemplate: "storyPage", resultsTemplate: "storybook", contributionTypes: [ - { - needName: "pageOne", - situation: { detector: "F8YqP3AEbyguQMJ9i", number: "1" }, - toPass: { - instruction: firstSentence, - firstSentence: firstSentence, - dropdownChoices: { - name: "affordance", - options: dropdownOptions - } - }, - numberNeeded: 1 - } + { + needName: "pageOne", + situation: { detector: "F8YqP3AEbyguQMJ9i", number: "1" }, + toPass: { + instruction: firstSentence, + firstSentence: firstSentence, + dropdownChoices: { + name: "affordance", + options: dropdownOptions + } + }, + numberNeeded: 1 + } ], description: "We're writing a spooky Neville Longbottom spin-off story", notificationText: "Help write a spooky Neville Longbottom story!", callbacks: [ - { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", - function: storytimeCallback.toString() - }, - { - trigger: "cb.incidentFinished()", - function: sendNotification.toString() - } + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 7)", + function: storytimeCallback.toString() + }, + { + trigger: "cb.incidentFinished()", + function: sendNotification.toString() + } ] }; @@ -722,10 +722,12 @@ function createNewSpookyNevilleStorytime() { startRunningIncident(incident); } +var foodFightDetectors =[]; + function createNewBadNeed(sub){ let need = { - needName: "bad" + (((int) sub.toString()[10]) - 1).toString(), - situation: { detector: "eG4no7zpSnthwwcv8", number: "1" }, + needName: "bad" + sub[10], + situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)], number: "1" }, toPass: { instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" }, @@ -736,161 +738,158 @@ function createNewBadNeed(sub){ function createNewGoodNeed(sub){ let need = { - needName: "good" + (((int) sub.toString()[10]) - 1).toString(), - situation: { detector: "eG4no7zpSnthwwcv8", number: "1" }, + needName: "good" + sub[10], + situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)+1], number: "1" }, toPass: { instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" }, - numberNeeded: 2 * (6 - (int) sub.toString()[10]) + numberNeeded: 2 * (6 - parseInt(sub[10])) }; return need; } function createNewFoodFight() { - + console.log("HERE"); //this section "fakes" history through adding detectors with each step of the narrative let newVars = JSON.parse( JSON.stringify(CONSTANTS.DETECTORS["eating_alone"]["variables"]) - ); - - for (int i = 1; i < 7; i++) - { - newVars.push("var badVariety" + i.toString()); + ); + for (var i = 1; i < 7; i++) { var badVarietyX = "badVariety" + i.toString(); - eval("detBad" + i) = { - _id: eG4no7zpSnthwwcv9, + newVars.push(badVarietyX); + detBad = { + _id: Random.id(), description: - CONSTANTS.DETECTORS["eating_alone"].description +badVarietyX, + CONSTANTS.DETECTORS["eating_alone"].description +badVarietyX, variables: newVars, rules: [ - "(" + - CONSTANTS.DETECTORS["eating_alone"].rules[0] + - " ) && " + badVarietyX + " ;" + "(" + + CONSTANTS.DETECTORS["eating_alone"].rules[0] + + " ) && " + badVarietyX + " ;" ] - }; //Bad detectors + }; + //Bad detectors + foodFightDetectors.push(detBad._id); Detectors.insert(detBad); newVars.push("var goodVariety" + i.toString()); var goodVarietyX = "goodVariety" + i.toString(); - eval("detGood" + i) = { - _id: eG4no7zpSnthwwcv9, + detGood = { + _id: Random.id(), description: - CONSTANTS.DETECTORS["eating_alone"].description +goodVarietyX, + CONSTANTS.DETECTORS["eating_alone"].description +goodVarietyX, variables: newVars, rules: [ - "(" + - CONSTANTS.DETECTORS["eating_alone"].rules[0] + - " ) && " + goodVarietyX + " ;" + "(" + + CONSTANTS.DETECTORS["eating_alone"].rules[0] + + " ) && " + goodVarietyX + " ;" ] - }; //Good detectors + }; + //Good detectors + foodFightDetectors.push(detGood._id); Detectors.insert(detGood); //dynamically add variable names? } //write a createNeed function that takes in the stage number and creates needs automatically - let startStage2 = function(sub) { //this will add needs to the contributionTypes array - let needBad1 = createNewBadNeed(startStage2); - - let needGood1 = createNewGoodNeed(startStage2); + let needBad1 = createNewBadNeed("startStage2"); + let needGood1 = createNewGoodNeed("startStage2"); - addNeed(sub.eid, needBad1); - addNeed(sub.eid, needGood1); -} + addNeed(sub.eid, needBad1); + addNeed(sub.eid, needGood1); + } let startStage3 = function(sub) { //this will add needs to the contributionTypes array - let needBad2 = createNewBadNeed(startStage3); + let needBad2 = createNewBadNeed("startStage3"); + let needGood2 = createNewGoodNeed("startStage3"); - let needGood2 = createNewGoodNeed(startStage3); - -addNeed(sub.eid, needBad2); -addNeed(sub.eid, needGood2); + addNeed(sub.eid, needBad2); + addNeed(sub.eid, needGood2); } let startStage4 = function(sub) { //this will add needs to the contributionTypes array - let needBad3 = createNewBadNeed(startStage4); - - let needGood3 = createNewGoodNeed(startStage4); + let needBad3 = createNewBadNeed("startStage4"); + let needGood3 = createNewGoodNeed("startStage4"); -addNeed(sub.eid, needBad3); -addNeed(sub.eid, needGood3); + addNeed(sub.eid, needBad3); + addNeed(sub.eid, needGood3); } let startStage5 = function(sub) { //this will add needs to the contributionTypes array - let needBad4 = createNewBadNeed(startStage5); + let needBad4 = createNewBadNeed("startStage5"); + let needGood4 = createNewGoodNeed("startStage5"); - let needGood4 = createNewGoodNeed(startStage5); - -addNeed(sub.eid, needBad4); -addNeed(sub.eid, needGood4); + addNeed(sub.eid, needBad4); + addNeed(sub.eid, needGood4); } let startStage6 = function(sub) { //this will add needs to the contributionTypes array - let needBad5 = { - needName: "bad5", - situation: { detector: "eG4no7zpSnthwwcv8", number: "1" }, + let needBad6 = { + needName: "bad6", + situation: { detector: foodFightDetectors[10], number: "1" }, toPass: { instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" }, - numberNeeded: 1) + numberNeeded: 1 }; - let needGood5 = { - needName: "good5", - situation: { detector: "eG4no7zpSnthwwcv8", number: "1" }, + let needGood6 = { + needName: "good6", + situation: { detector: foodFightDetectors[11], number: "1" }, toPass: { instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" }, - numberNeeded: 1) + numberNeeded: 1 }; -addNeed(sub.eid, needBad5); -addNeed(sub.eid, needGood5); + addNeed(sub.eid, needBad6); + addNeed(sub.eid, needGood6); } - let exp = { - _id: Random.id(), - name: "Food Fight", - participateTemplate: "submitText", - resultsTemplate: "foodFightResult", +let exp = { + _id: Random.id(), + name: "Food Fight", + participateTemplate: "submitText", + resultsTemplate: "foodFightResult", contributionTypes: [ //array of different needs, one for each stage of the narrative - { - needName: "foodFightStage1", - situation: { detector: "eG4no7zpSnthwwcv6", number: "1" }, - toPass: { - instruction: "We're having a food fight! Share what you're eating." + { + needName: "foodFightStage1", + situation: { detector: "eG4no7zpSnthwwcv6", number: "1" }, + toPass: { + instruction: "We're having a food fight! Share what you're eating." - }, - numberNeeded: 10 - } + }, + numberNeeded: 10 + } ], description: "food fight", notificationText: "A food fight is starting!", callbacks: [ { - trigger: "cb.needFinished("foodFightStage1")", //cb referes to the callback manager, this calls back once stage 1 has begun + trigger: "cb.needFinished('foodFightStage1')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage2.toString() - } + }, { - trigger: "cb.needFinished("startStage2")", //cb referes to the callback manager, this calls back once stage 1 has begun + trigger: "cb.needFinished('startStage2')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage3.toString() - } + }, { - trigger: "cb.needFinished("startStage3")", //cb referes to the callback manager, this calls back once stage 1 has begun + trigger: "cb.needFinished('startStage3')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage4.toString() - } + }, { - trigger: "cb.needFinished("startStage4")", //cb referes to the callback manager, this calls back once stage 1 has begun + trigger: "cb.needFinished('startStage4')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage5.toString() - } + }, { - trigger: "cb.needFinished("startStage5")", //cb referes to the callback manager, this calls back once stage 1 has begun + trigger: "cb.needFinished('startStage5')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage6.toString() } ] - }; + }; - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); -} + Experiences.insert(exp); + let incident = createIncidentFromExperience(exp); + startRunningIncident(incident); + } diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index 0547b932..aada6bcb 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -194,7 +194,7 @@ let DETECTORS = { 'rules': [ '(food_court || dinning_hall)' ] - } + }, 'gas_station': { '_id': 'xZBgjwdPw8rtg86eo', 'description': 'gas_stations', From 414e9e5093d48a5e33f73408f5e919577f84a2a1 Mon Sep 17 00:00:00 2001 From: wangsanfeng1998 Date: Thu, 10 May 2018 21:52:55 -0500 Subject: [PATCH 004/132] wrote turf war --- imports/api/testing/createNewExperiences.js | 102 +++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js index 8b40c8f7..bf0fb710 100644 --- a/imports/api/testing/createNewExperiences.js +++ b/imports/api/testing/createNewExperiences.js @@ -33,6 +33,9 @@ Meteor.methods({ }, startFreshFoodFight() { createNewFoodFight(); + }, + startFreshTurfWar() { + createNewTurfWar(); } }); @@ -767,7 +770,7 @@ function createNewFoodFight() { CONSTANTS.DETECTORS["eating_alone"].rules[0] + " ) && " + badVarietyX + " ;" ] - }; + }; //Bad detectors foodFightDetectors.push(detBad._id); Detectors.insert(detBad); @@ -784,7 +787,7 @@ function createNewFoodFight() { CONSTANTS.DETECTORS["eating_alone"].rules[0] + " ) && " + goodVarietyX + " ;" ] - }; + }; //Good detectors foodFightDetectors.push(detGood._id); Detectors.insert(detGood); //dynamically add variable names? @@ -893,3 +896,98 @@ let exp = { let incident = createIncidentFromExperience(exp); startRunningIncident(incident); } + + function createNewTurfWar() { + let newVars = JSON.parse( + JSON.stringify(CONSTANTS.DETECTORS["on_transit"]["variables"]) + ); + let exp = { + _id: Random.id(), + name: "Turf War", + participateTemplate: "submitVote", + resultsTemplate: "TurfWarResult", + contributionTypes: [ //array of different needs, one for each stage of the narrative + { + needName: "TurfWarStage1", + situation: { detector: , number: "1" }, + toPass: { + instruction: "There is a turf war going on! Vote for who you want to win!" + + }, + numberNeeded: 100 + } + ], + description: "Turf War", + notificationText: "A turf war is starting!", + callbacks: [ + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 99)", + function: TurfWarCallback.toString() + }, + { + trigger: "cb.incidentFinished()", + function: turfWarEnd.toString() + } + ] + }; + + let TurfWarCallback = function(sub) { + Meteor.users.update( + { + _id: sub.uid + }, + { + $set: { + "profile.staticAffordances.participatedInTurfWar": true + } + } + ); + + let affordance = sub.content.affordance; + + let options = [ + ["Country A", "F8YqP3AEbyguQMJ9i"], + ["Country B", "F8YqP3AEbyguQMJ9i"] + ]; + + options = options.filter(function(x) { + return x[1] !== affordance; + }); + + let needName = "vote" + Random.id(3); + if (cb.numberOfSubmissions() === 100) { + needName = "voteFinal"; + } + let contribution = { + needName: needName, + situation: { detector: affordance, number: "1" }, + toPass: { + instruction: sub.content.sentence, + dropdownChoices: { name: "affordance", options: options } + }, + numberNeeded: 1 + }; + addContribution(sub.iid, contribution); + }; + + let turfWarEnd = function(sub) { + let otherSub = Submissions.findOne({ + uid: { $ne: sub.uid }, + iid: sub.iid, + needName: sub.needName + }); + + notify( + [sub.uid, otherSub.uid], + sub.iid, + "Check out which country is winning!", + "", + "/apicustomresults/" + sub.iid + "/" + sub.eid + ); + }; + + Experiences.insert(exp); + let incident = createIncidentFromExperience(exp); + startRunningIncident(incident); + } + } From 2f353d6b79301a79427b021aaeba1e21753da74d Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Fri, 11 May 2018 11:00:17 -0500 Subject: [PATCH 005/132] Implemented fantasy gym --- imports/api/testing/createNewExperiences.js | 279 +++++++++++++------- 1 file changed, 183 insertions(+), 96 deletions(-) diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js index bf0fb710..7c4dbdb2 100644 --- a/imports/api/testing/createNewExperiences.js +++ b/imports/api/testing/createNewExperiences.js @@ -12,7 +12,7 @@ import { Meteor } from "meteor/meteor"; Meteor.methods({ startFreshBumped() { - console.log("GOING IN BUMPED"); + console.log("GOING IN BUMPED"); createNewBumped(); }, @@ -36,6 +36,9 @@ Meteor.methods({ }, startFreshTurfWar() { createNewTurfWar(); + }, + startFreshFantasyGym() { + createNewFantasyGym(); } }); @@ -869,7 +872,7 @@ let exp = { description: "food fight", notificationText: "A food fight is starting!", callbacks: [ - { + { trigger: "cb.needFinished('foodFightStage1')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage2.toString() }, @@ -889,105 +892,189 @@ let exp = { trigger: "cb.needFinished('startStage5')", //cb referes to the callback manager, this calls back once stage 1 has begun function: startStage6.toString() } - ] - }; + ] + }; Experiences.insert(exp); let incident = createIncidentFromExperience(exp); startRunningIncident(incident); } - function createNewTurfWar() { +function createNewTurfWar() { + let newVars = JSON.parse( + JSON.stringify(CONSTANTS.DETECTORS["on_transit"]["variables"]) + ); + let exp = { + _id: Random.id(), + name: "Turf War", + participateTemplate: "submitVote", + resultsTemplate: "TurfWarResult", + contributionTypes: [ //array of different needs, one for each stage of the narrative + { + needName: "TurfWarStage1", + situation: { detector: , number: "1" }, + toPass: { + instruction: "There is a turf war going on! Vote for who you want to win!" + + }, + numberNeeded: 100 + } + ], + description: "Turf War", + notificationText: "A turf war is starting!", + callbacks: [ + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 99)", + function: TurfWarCallback.toString() + }, + { + trigger: "cb.incidentFinished()", + function: turfWarEnd.toString() + } + ] + }; + + let TurfWarCallback = function(sub) { + Meteor.users.update( + { + _id: sub.uid + }, + { + $set: { + "profile.staticAffordances.participatedInTurfWar": true + } + } + ); + + let affordance = sub.content.affordance; + + let options = [ + ["Country A", "F8YqP3AEbyguQMJ9i"], + ["Country B", "F8YqP3AEbyguQMJ9i"] + ]; + + options = options.filter(function(x) { + return x[1] !== affordance; + }); + + let needName = "vote" + Random.id(3); + if (cb.numberOfSubmissions() === 100) { + needName = "voteFinal"; + } + let contribution = { + needName: needName, + situation: { detector: affordance, number: "1" }, + toPass: { + instruction: sub.content.sentence, + dropdownChoices: { name: "affordance", options: options } + }, + numberNeeded: 1 + }; + addContribution(sub.iid, contribution); + }; + + let turfWarEnd = function(sub) { + let otherSub = Submissions.findOne({ + uid: { $ne: sub.uid }, + iid: sub.iid, + needName: sub.needName + }); + + notify( + [sub.uid, otherSub.uid], + sub.iid, + "Check out which country is winning!", + "", + "/apicustomresults/" + sub.iid + "/" + sub.eid + ); + }; + + Experiences.insert(exp); + let incident = createIncidentFromExperience(exp); + startRunningIncident(incident); +} + +createNewFantasyGym() { + let experience = { + name: "FanyasyGym", + participateTemplate: "gym", + resultsTemplate: "gymtemplate", + contributionTypes: [], + description: "Workout detected! Challenge someone else at this equipment.", + notificationText: "Workout detected! Challenge someone else at this equipment.", + callbacks: [] + }; + + let gymCallBack = function(sub) { + let otherSub = Submissions.findOne({ + uid: { $ne: sub.uid }, + iid: sub.iid, + needName: sub.needName + }); + + notify( + [sub.uid, otherSub.uid], + sub.iid, + "Compare your score with someone else's!", + "", + "/apicustomresults/" + sub.iid + "/" + sub.eid + ); + }; + + let equipments = [ + ["benchpress", "at the bench"], + ["shoulderpress", "at the bench"], + ["bicepcurl", "at the dumbell area"], + ["tricepcurl", "at the dumbell area"], + ["cardio", "at the running machine"], + ]; + + _.forEach(equipments, equipment => { let newVars = JSON.parse( - JSON.stringify(CONSTANTS.DETECTORS["on_transit"]["variables"]) + JSON.stringify(CONSTANTS.DETECTORS[equipment[0]]["variables"]) ); - let exp = { - _id: Random.id(), - name: "Turf War", - participateTemplate: "submitVote", - resultsTemplate: "TurfWarResult", - contributionTypes: [ //array of different needs, one for each stage of the narrative - { - needName: "TurfWarStage1", - situation: { detector: , number: "1" }, - toPass: { - instruction: "There is a turf war going on! Vote for who you want to win!" - - }, - numberNeeded: 100 - } - ], - description: "Turf War", - notificationText: "A turf war is starting!", - callbacks: [ - { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 99)", - function: TurfWarCallback.toString() - }, - { - trigger: "cb.incidentFinished()", - function: turfWarEnd.toString() - } - ] - }; - - let TurfWarCallback = function(sub) { - Meteor.users.update( - { - _id: sub.uid - }, - { - $set: { - "profile.staticAffordances.participatedInTurfWar": true - } - } - ); - - let affordance = sub.content.affordance; - - let options = [ - ["Country A", "F8YqP3AEbyguQMJ9i"], - ["Country B", "F8YqP3AEbyguQMJ9i"] - ]; - - options = options.filter(function(x) { - return x[1] !== affordance; - }); - - let needName = "vote" + Random.id(3); - if (cb.numberOfSubmissions() === 100) { - needName = "voteFinal"; - } - let contribution = { - needName: needName, - situation: { detector: affordance, number: "1" }, - toPass: { - instruction: sub.content.sentence, - dropdownChoices: { name: "affordance", options: options } - }, - numberNeeded: 1 - }; - addContribution(sub.iid, contribution); - }; - - let turfWarEnd = function(sub) { - let otherSub = Submissions.findOne({ - uid: { $ne: sub.uid }, - iid: sub.iid, - needName: sub.needName - }); - - notify( - [sub.uid, otherSub.uid], - sub.iid, - "Check out which country is winning!", - "", - "/apicustomresults/" + sub.iid + "/" + sub.eid - ); - }; - - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); - } - } + newVars.push("var lovesDTR;"); + newVars.push("var lovesDTRAlumni;"); + + let detector = { + _id: Random.id(), // need to save detectors for future use + description: + CONSTANTS.DETECTORS[equipment[0]].description, + variables: newVars, + rules: [ + "((" + + CONSTANTS.DETECTORS[equipment[0]].rules[0] + + ");" + ] + }; + CONSTANTS.DETECTORS[equipment[0]] = detector; + + Detectors.insert(detector); + + for (let i = 0; i < 10; i++) { + let need = { + needName: equipment[0] + i, + situation: { detector: detector._id, number: "2" }, + toPass: { + instruction: "You are " + equipment[1] + " at the same time as " + }, + numberNeeded: 2 + }; + let callback = { + trigger: + "cb.numberOfSubmissions('" + + equipment[0] + + i + + "') === 2", + function: gymCallBack.toString() + }; + + experience.contributionTypes.push(need); + experience.callbacks.push(callback); + } + }); + + Experiences.insert(experience); + let incident = createIncidentFromExperience(experience); + startRunningIncident(incident); +} From 28a6f4ad9c32fff349dd51b2cd80475cc4e0cf59 Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Wed, 30 May 2018 22:18:24 -0500 Subject: [PATCH 006/132] Compiler done --- imports/api/testing/createNewExperiences.js | 670 +++--- imports/api/testing/testingconstants.js | 2013 +++++++++++-------- imports/ui/pages/api_custom.html | 18 + imports/ui/pages/api_custom_results.html | 6 + 4 files changed, 1528 insertions(+), 1179 deletions(-) diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js index 7c4dbdb2..69ddc553 100644 --- a/imports/api/testing/createNewExperiences.js +++ b/imports/api/testing/createNewExperiences.js @@ -12,8 +12,6 @@ import { Meteor } from "meteor/meteor"; Meteor.methods({ startFreshBumped() { - console.log("GOING IN BUMPED"); - createNewBumped(); }, startFreshBumped2() { @@ -30,16 +28,16 @@ Meteor.methods({ }, startFreshSpookyNevilleStorytime() { createNewSpookyNevilleStorytime(); - }, - startFreshFoodFight() { - createNewFoodFight(); - }, - startFreshTurfWar() { - createNewTurfWar(); - }, - startFreshFantasyGym() { - createNewFantasyGym(); } + // startFreshFoodFight() { + // createNewFoodFight(); + // }, + // startFreshTurfWar() { + // createNewTurfWar(); + // }, + // startFreshFantasyGym() { + // createNewFantasyGym(); + // } }); function createNewBumped() { @@ -728,353 +726,353 @@ function createNewSpookyNevilleStorytime() { startRunningIncident(incident); } -var foodFightDetectors =[]; - -function createNewBadNeed(sub){ - let need = { - needName: "bad" + sub[10], - situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)], number: "1" }, - toPass: { - instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" - }, - numberNeeded: 2 - }; - return need; -} - -function createNewGoodNeed(sub){ - let need = { - needName: "good" + sub[10], - situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)+1], number: "1" }, - toPass: { - instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" - }, - numberNeeded: 2 * (6 - parseInt(sub[10])) - }; - return need; -} - -function createNewFoodFight() { - console.log("HERE"); - //this section "fakes" history through adding detectors with each step of the narrative - let newVars = JSON.parse( - JSON.stringify(CONSTANTS.DETECTORS["eating_alone"]["variables"]) - ); - for (var i = 1; i < 7; i++) { - var badVarietyX = "badVariety" + i.toString(); - newVars.push(badVarietyX); - detBad = { - _id: Random.id(), - description: - CONSTANTS.DETECTORS["eating_alone"].description +badVarietyX, - variables: newVars, - rules: [ - "(" + - CONSTANTS.DETECTORS["eating_alone"].rules[0] + - " ) && " + badVarietyX + " ;" - ] - }; - //Bad detectors - foodFightDetectors.push(detBad._id); - Detectors.insert(detBad); - - newVars.push("var goodVariety" + i.toString()); - var goodVarietyX = "goodVariety" + i.toString(); - detGood = { - _id: Random.id(), - description: - CONSTANTS.DETECTORS["eating_alone"].description +goodVarietyX, - variables: newVars, - rules: [ - "(" + - CONSTANTS.DETECTORS["eating_alone"].rules[0] + - " ) && " + goodVarietyX + " ;" - ] - }; - //Good detectors - foodFightDetectors.push(detGood._id); - Detectors.insert(detGood); //dynamically add variable names? - } - - //write a createNeed function that takes in the stage number and creates needs automatically - let startStage2 = function(sub) { //this will add needs to the contributionTypes array - let needBad1 = createNewBadNeed("startStage2"); - let needGood1 = createNewGoodNeed("startStage2"); - - addNeed(sub.eid, needBad1); - addNeed(sub.eid, needGood1); - } - -let startStage3 = function(sub) { //this will add needs to the contributionTypes array - let needBad2 = createNewBadNeed("startStage3"); - let needGood2 = createNewGoodNeed("startStage3"); - - addNeed(sub.eid, needBad2); - addNeed(sub.eid, needGood2); -} - -let startStage4 = function(sub) { //this will add needs to the contributionTypes array - let needBad3 = createNewBadNeed("startStage4"); - let needGood3 = createNewGoodNeed("startStage4"); +// var foodFightDetectors =[]; - addNeed(sub.eid, needBad3); - addNeed(sub.eid, needGood3); -} - -let startStage5 = function(sub) { //this will add needs to the contributionTypes array - let needBad4 = createNewBadNeed("startStage5"); - let needGood4 = createNewGoodNeed("startStage5"); - - addNeed(sub.eid, needBad4); - addNeed(sub.eid, needGood4); -} - -let startStage6 = function(sub) { //this will add needs to the contributionTypes array - let needBad6 = { - needName: "bad6", - situation: { detector: foodFightDetectors[10], number: "1" }, - toPass: { - instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" - }, - numberNeeded: 1 - }; - - let needGood6 = { - needName: "good6", - situation: { detector: foodFightDetectors[11], number: "1" }, - toPass: { - instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" - }, - numberNeeded: 1 - }; - - addNeed(sub.eid, needBad6); - addNeed(sub.eid, needGood6); -} +// function createNewBadNeed(sub){ +// let need = { +// needName: "bad" + sub[10], +// situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)], number: "1" }, +// toPass: { +// instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" +// }, +// numberNeeded: 2 +// }; +// return need; +// } +// function createNewGoodNeed(sub){ +// let need = { +// needName: "good" + sub[10], +// situation: { detector: foodFightDetectors[2*(parseInt(sub[10])-2)+1], number: "1" }, +// toPass: { +// instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" +// }, +// numberNeeded: 2 * (6 - parseInt(sub[10])) +// }; +// return need; +// } -let exp = { - _id: Random.id(), - name: "Food Fight", - participateTemplate: "submitText", - resultsTemplate: "foodFightResult", - contributionTypes: [ //array of different needs, one for each stage of the narrative - { - needName: "foodFightStage1", - situation: { detector: "eG4no7zpSnthwwcv6", number: "1" }, - toPass: { - instruction: "We're having a food fight! Share what you're eating." +// function createNewFoodFight() { +// console.log("HERE"); +// //this section "fakes" history through adding detectors with each step of the narrative +// let newVars = JSON.parse( +// JSON.stringify(CONSTANTS.DETECTORS["eating_alone"]["variables"]) +// ); +// for (var i = 1; i < 7; i++) { +// var badVarietyX = "badVariety" + i.toString(); +// newVars.push(badVarietyX); +// detBad = { +// _id: Random.id(), +// description: +// CONSTANTS.DETECTORS["eating_alone"].description +badVarietyX, +// variables: newVars, +// rules: [ +// "(" + +// CONSTANTS.DETECTORS["eating_alone"].rules[0] + +// " ) && " + badVarietyX + " ;" +// ] +// }; +// //Bad detectors +// foodFightDetectors.push(detBad._id); +// Detectors.insert(detBad); + +// newVars.push("var goodVariety" + i.toString()); +// var goodVarietyX = "goodVariety" + i.toString(); +// detGood = { +// _id: Random.id(), +// description: +// CONSTANTS.DETECTORS["eating_alone"].description +goodVarietyX, +// variables: newVars, +// rules: [ +// "(" + +// CONSTANTS.DETECTORS["eating_alone"].rules[0] + +// " ) && " + goodVarietyX + " ;" +// ] +// }; +// //Good detectors +// foodFightDetectors.push(detGood._id); +// Detectors.insert(detGood); //dynamically add variable names? +// } + +// //write a createNeed function that takes in the stage number and creates needs automatically +// let startStage2 = function(sub) { //this will add needs to the contributionTypes array +// let needBad1 = createNewBadNeed("startStage2"); +// let needGood1 = createNewGoodNeed("startStage2"); + +// addNeed(sub.eid, needBad1); +// addNeed(sub.eid, needGood1); +// } + +// let startStage3 = function(sub) { //this will add needs to the contributionTypes array +// let needBad2 = createNewBadNeed("startStage3"); +// let needGood2 = createNewGoodNeed("startStage3"); + +// addNeed(sub.eid, needBad2); +// addNeed(sub.eid, needGood2); +// } - }, - numberNeeded: 10 - } - ], - description: "food fight", - notificationText: "A food fight is starting!", - callbacks: [ - { - trigger: "cb.needFinished('foodFightStage1')", //cb referes to the callback manager, this calls back once stage 1 has begun - function: startStage2.toString() - }, - { - trigger: "cb.needFinished('startStage2')", //cb referes to the callback manager, this calls back once stage 1 has begun - function: startStage3.toString() - }, - { - trigger: "cb.needFinished('startStage3')", //cb referes to the callback manager, this calls back once stage 1 has begun - function: startStage4.toString() - }, - { - trigger: "cb.needFinished('startStage4')", //cb referes to the callback manager, this calls back once stage 1 has begun - function: startStage5.toString() - }, - { - trigger: "cb.needFinished('startStage5')", //cb referes to the callback manager, this calls back once stage 1 has begun - function: startStage6.toString() - } - ] - }; +// let startStage4 = function(sub) { //this will add needs to the contributionTypes array +// let needBad3 = createNewBadNeed("startStage4"); +// let needGood3 = createNewGoodNeed("startStage4"); - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); - } +// addNeed(sub.eid, needBad3); +// addNeed(sub.eid, needGood3); +// } -function createNewTurfWar() { - let newVars = JSON.parse( - JSON.stringify(CONSTANTS.DETECTORS["on_transit"]["variables"]) - ); - let exp = { - _id: Random.id(), - name: "Turf War", - participateTemplate: "submitVote", - resultsTemplate: "TurfWarResult", - contributionTypes: [ //array of different needs, one for each stage of the narrative - { - needName: "TurfWarStage1", - situation: { detector: , number: "1" }, - toPass: { - instruction: "There is a turf war going on! Vote for who you want to win!" +// let startStage5 = function(sub) { //this will add needs to the contributionTypes array +// let needBad4 = createNewBadNeed("startStage5"); +// let needGood4 = createNewGoodNeed("startStage5"); - }, - numberNeeded: 100 - } - ], - description: "Turf War", - notificationText: "A turf war is starting!", - callbacks: [ - { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 99)", - function: TurfWarCallback.toString() - }, - { - trigger: "cb.incidentFinished()", - function: turfWarEnd.toString() - } - ] - }; +// addNeed(sub.eid, needBad4); +// addNeed(sub.eid, needGood4); +// } - let TurfWarCallback = function(sub) { - Meteor.users.update( - { - _id: sub.uid - }, - { - $set: { - "profile.staticAffordances.participatedInTurfWar": true - } - } - ); +// let startStage6 = function(sub) { //this will add needs to the contributionTypes array +// let needBad6 = { +// needName: "bad6", +// situation: { detector: foodFightDetectors[10], number: "1" }, +// toPass: { +// instruction: "Your diet sucks! Get some more variety in your food so you don't run out of ammo" +// }, +// numberNeeded: 1 +// }; - let affordance = sub.content.affordance; +// let needGood6 = { +// needName: "good6", +// situation: { detector: foodFightDetectors[11], number: "1" }, +// toPass: { +// instruction: "You're doing great! Tell us what you're eating right now so we can restock your ammo" +// }, +// numberNeeded: 1 +// }; - let options = [ - ["Country A", "F8YqP3AEbyguQMJ9i"], - ["Country B", "F8YqP3AEbyguQMJ9i"] - ]; +// addNeed(sub.eid, needBad6); +// addNeed(sub.eid, needGood6); +// } - options = options.filter(function(x) { - return x[1] !== affordance; - }); - let needName = "vote" + Random.id(3); - if (cb.numberOfSubmissions() === 100) { - needName = "voteFinal"; - } - let contribution = { - needName: needName, - situation: { detector: affordance, number: "1" }, - toPass: { - instruction: sub.content.sentence, - dropdownChoices: { name: "affordance", options: options } - }, - numberNeeded: 1 - }; - addContribution(sub.iid, contribution); - }; +// let exp = { +// _id: Random.id(), +// name: "Food Fight", +// participateTemplate: "submitText", +// resultsTemplate: "foodFightResult", +// contributionTypes: [ //array of different needs, one for each stage of the narrative +// { +// needName: "foodFightStage1", +// situation: { detector: "eG4no7zpSnthwwcv6", number: "1" }, +// toPass: { +// instruction: "We're having a food fight! Share what you're eating." - let turfWarEnd = function(sub) { - let otherSub = Submissions.findOne({ - uid: { $ne: sub.uid }, - iid: sub.iid, - needName: sub.needName - }); +// }, +// numberNeeded: 10 +// } +// ], +// description: "food fight", +// notificationText: "A food fight is starting!", +// callbacks: [ +// { +// trigger: "cb.needFinished('foodFightStage1')", //cb referes to the callback manager, this calls back once stage 1 has begun +// function: startStage2.toString() +// }, +// { +// trigger: "cb.needFinished('startStage2')", //cb referes to the callback manager, this calls back once stage 1 has begun +// function: startStage3.toString() +// }, +// { +// trigger: "cb.needFinished('startStage3')", //cb referes to the callback manager, this calls back once stage 1 has begun +// function: startStage4.toString() +// }, +// { +// trigger: "cb.needFinished('startStage4')", //cb referes to the callback manager, this calls back once stage 1 has begun +// function: startStage5.toString() +// }, +// { +// trigger: "cb.needFinished('startStage5')", //cb referes to the callback manager, this calls back once stage 1 has begun +// function: startStage6.toString() +// } +// ] +// }; + +// Experiences.insert(exp); +// let incident = createIncidentFromExperience(exp); +// startRunningIncident(incident); +// } + +// function createNewTurfWar() { +// let newVars = JSON.parse( +// JSON.stringify(CONSTANTS.DETECTORS["on_transit"]["variables"]) +// ); +// let exp = { +// _id: Random.id(), +// name: "Turf War", +// participateTemplate: "submitVote", +// resultsTemplate: "TurfWarResult", +// contributionTypes: [ //array of different needs, one for each stage of the narrative +// { +// needName: "TurfWarStage1", +// situation: { detector: , number: "1" }, +// toPass: { +// instruction: "There is a turf war going on! Vote for who you want to win!" - notify( - [sub.uid, otherSub.uid], - sub.iid, - "Check out which country is winning!", - "", - "/apicustomresults/" + sub.iid + "/" + sub.eid - ); - }; +// }, +// numberNeeded: 100 +// } +// ], +// description: "Turf War", +// notificationText: "A turf war is starting!", +// callbacks: [ +// { +// trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= 99)", +// function: TurfWarCallback.toString() +// }, +// { +// trigger: "cb.incidentFinished()", +// function: turfWarEnd.toString() +// } +// ] +// }; - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); -} +// let TurfWarCallback = function(sub) { +// Meteor.users.update( +// { +// _id: sub.uid +// }, +// { +// $set: { +// "profile.staticAffordances.participatedInTurfWar": true +// } +// } +// ); -createNewFantasyGym() { - let experience = { - name: "FanyasyGym", - participateTemplate: "gym", - resultsTemplate: "gymtemplate", - contributionTypes: [], - description: "Workout detected! Challenge someone else at this equipment.", - notificationText: "Workout detected! Challenge someone else at this equipment.", - callbacks: [] - }; +// let affordance = sub.content.affordance; - let gymCallBack = function(sub) { - let otherSub = Submissions.findOne({ - uid: { $ne: sub.uid }, - iid: sub.iid, - needName: sub.needName - }); +// let options = [ +// ["Country A", "F8YqP3AEbyguQMJ9i"], +// ["Country B", "F8YqP3AEbyguQMJ9i"] +// ]; - notify( - [sub.uid, otherSub.uid], - sub.iid, - "Compare your score with someone else's!", - "", - "/apicustomresults/" + sub.iid + "/" + sub.eid - ); - }; +// options = options.filter(function(x) { +// return x[1] !== affordance; +// }); - let equipments = [ - ["benchpress", "at the bench"], - ["shoulderpress", "at the bench"], - ["bicepcurl", "at the dumbell area"], - ["tricepcurl", "at the dumbell area"], - ["cardio", "at the running machine"], - ]; +// let needName = "vote" + Random.id(3); +// if (cb.numberOfSubmissions() === 100) { +// needName = "voteFinal"; +// } +// let contribution = { +// needName: needName, +// situation: { detector: affordance, number: "1" }, +// toPass: { +// instruction: sub.content.sentence, +// dropdownChoices: { name: "affordance", options: options } +// }, +// numberNeeded: 1 +// }; +// addContribution(sub.iid, contribution); +// }; - _.forEach(equipments, equipment => { - let newVars = JSON.parse( - JSON.stringify(CONSTANTS.DETECTORS[equipment[0]]["variables"]) - ); - newVars.push("var lovesDTR;"); - newVars.push("var lovesDTRAlumni;"); +// let turfWarEnd = function(sub) { +// let otherSub = Submissions.findOne({ +// uid: { $ne: sub.uid }, +// iid: sub.iid, +// needName: sub.needName +// }); + +// notify( +// [sub.uid, otherSub.uid], +// sub.iid, +// "Check out which country is winning!", +// "", +// "/apicustomresults/" + sub.iid + "/" + sub.eid +// ); +// }; - let detector = { - _id: Random.id(), // need to save detectors for future use - description: - CONSTANTS.DETECTORS[equipment[0]].description, - variables: newVars, - rules: [ - "((" + - CONSTANTS.DETECTORS[equipment[0]].rules[0] + - ");" - ] - }; - CONSTANTS.DETECTORS[equipment[0]] = detector; +// Experiences.insert(exp); +// let incident = createIncidentFromExperience(exp); +// startRunningIncident(incident); +// } - Detectors.insert(detector); +// createNewFantasyGym() { +// let experience = { +// name: "FanyasyGym", +// participateTemplate: "gym", +// resultsTemplate: "gymtemplate", +// contributionTypes: [], +// description: "Workout detected! Challenge someone else at this equipment.", +// notificationText: "Workout detected! Challenge someone else at this equipment.", +// callbacks: [] +// }; - for (let i = 0; i < 10; i++) { - let need = { - needName: equipment[0] + i, - situation: { detector: detector._id, number: "2" }, - toPass: { - instruction: "You are " + equipment[1] + " at the same time as " - }, - numberNeeded: 2 - }; - let callback = { - trigger: - "cb.numberOfSubmissions('" + - equipment[0] + - i + - "') === 2", - function: gymCallBack.toString() - }; +// let gymCallBack = function(sub) { +// let otherSub = Submissions.findOne({ +// uid: { $ne: sub.uid }, +// iid: sub.iid, +// needName: sub.needName +// }); + +// notify( +// [sub.uid, otherSub.uid], +// sub.iid, +// "Compare your score with someone else's!", +// "", +// "/apicustomresults/" + sub.iid + "/" + sub.eid +// ); +// }; - experience.contributionTypes.push(need); - experience.callbacks.push(callback); - } - }); +// let equipments = [ +// ["benchpress", "at the bench"], +// ["shoulderpress", "at the bench"], +// ["bicepcurl", "at the dumbell area"], +// ["tricepcurl", "at the dumbell area"], +// ["cardio", "at the running machine"], +// ]; + +// _.forEach(equipments, equipment => { +// let newVars = JSON.parse( +// JSON.stringify(CONSTANTS.DETECTORS[equipment[0]]["variables"]) +// ); +// newVars.push("var lovesDTR;"); +// newVars.push("var lovesDTRAlumni;"); + +// let detector = { +// _id: Random.id(), // need to save detectors for future use +// description: +// CONSTANTS.DETECTORS[equipment[0]].description, +// variables: newVars, +// rules: [ +// "((" + +// CONSTANTS.DETECTORS[equipment[0]].rules[0] + +// ");" +// ] +// }; +// CONSTANTS.DETECTORS[equipment[0]] = detector; + +// Detectors.insert(detector); + +// for (let i = 0; i < 10; i++) { +// let need = { +// needName: equipment[0] + i, +// situation: { detector: detector._id, number: "2" }, +// toPass: { +// instruction: "You are " + equipment[1] + " at the same time as " +// }, +// numberNeeded: 2 +// }; +// let callback = { +// trigger: +// "cb.numberOfSubmissions('" + +// equipment[0] + +// i + +// "') === 2", +// function: gymCallBack.toString() +// }; + +// experience.contributionTypes.push(need); +// experience.callbacks.push(callback); +// } +// }); - Experiences.insert(experience); - let incident = createIncidentFromExperience(experience); - startRunningIncident(incident); -} +// Experiences.insert(experience); +// let incident = createIncidentFromExperience(experience); +// startRunningIncident(incident); +// } diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index aada6bcb..c3a5b6e8 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -1,843 +1,1170 @@ -import {addContribution} from '../incidents/methods'; -import {Submissions} from "../submissions/submissions"; -import {serverLog} from "../logs"; -import {Meteor} from "meteor/meteor"; - -let LOCATIONS = { - 'park': {lat: 42.056838, lng: -87.675940}, - 'lakefill': {lat: 42.054902, lng: -87.670197}, - 'burgers': {lat: 42.046131, lng: -87.681559}, - 'grocery': {lat: 42.047621, lng: -87.679488}, -}; - -let USERS = { - 'garrett': { - username: 'garrett', - password: 'password', - }, - 'garretts_brother': { - username: 'garretts_brother', - password: 'password', - }, - 'meg': { - username: 'meg', - password: 'password', - }, - 'megs_sister': { - username: 'megs_sister', - password: 'password', - }, - 'andrew': { - username: 'andrew', - password: 'password', - }, - 'josh': { - username: 'josh', - password: 'password', - } -}; - -let DETECTORS = { - 'field': { - '_id': 'rEbK6WMQnPPAGAXMX', - 'description': 'fields', - 'variables': [ - 'var baseball_fields;', - 'var stadiums___arenas;', - 'var soccer;', - 'var parks;' - ], - 'rules': [ - '(parks || soccer || baseball_fields || stadiums___arenas)' - ] - }, - 'niceish_day': { - '_id': 'x7EgLErQx3qmiemqt', - 'description': 'niceish_day', - 'variables': [ - 'var clouds;', - 'var clear;', - 'var daytime;' - ], - 'rules': [ - 'daytime && (clouds || clear)' - ] - }, - 'night': { - '_id': 'Wth3TB9Lcf6me6vgy', - 'description': 'places where it\'s nighttime,', - 'variables': [ - 'var nighttime;', - ], - 'rules': [ - '(nighttime)' - ] - }, - 'sunset': { - '_id': '44EXNzHS7oD2rbF68', - 'description': 'places where it\'s sunset,', - 'variables': [ - 'var sunset;', - 'var clear;', - ], - 'rules': [ - 'sunset && clear' - ] - }, - 'daytime': { - '_id': 'tyZMZvPKkkSPR4FpG', - 'description': 'places where it\'s daytime,', - 'variables': [ - 'var daytime;' - ], - 'rules': [ - 'daytime' - ] - }, - 'library': { - '_id': '5LqfPRajiQRe9BwBT', - 'description': ' libaries,', - 'variables': [ - 'var libraries;' - ], - 'rules': [ - ' libraries' - ] - }, - 'gym': { - '_id': '3XqHN8A4EpCZRpegS', - 'description': ' gym', - 'variables': [ - 'var gyms;' - ], - 'rules': [ - ' gyms' - ] - }, - 'produce': { - '_id': 'oHCMYfBBcaphXqQnT', - 'description': ' places where you can find fuits and veggies', - 'variables': [ - 'var grocery;', - 'var organic_stores;', - 'var fruits___veggies;', - 'var farmers_market;' - ], - 'rules': [ - '(grocery || organic_stores || fruits___veggies || farmers_market)' - ] - }, - 'rainbow': { - '_id': 'ksxGTXMaSpCFdmqqN', - 'description': 'rainbow flag', - 'variables': [ - 'var gay_bars;' - ], - 'rules': [ - 'gay_bars' - ] - }, - 'drugstore': { - '_id': 'k8KFfv3ATtbg2tnFB', - 'description': 'drugstores', - 'variables': [ - 'var drugstores;', - 'var pharmacy;' - ], - 'rules': [ - '(drugstores || pharmacy)' - ] - }, - 'costume_store': { - '_id': 'ECPk2mjuHJtrMotGg', - 'description': 'costume_store', - 'variables': [ - 'var costumes;', - 'var party_supplies;' - ], - 'rules': [ - '(party_supplies || costumes)' - ] - }, - 'irish': { - '_id': '5CJGGtjqyY89n55XP', - 'description': 'irish', - 'variables': [ - 'var irish_pub;', - 'var irish;' - ], - 'rules': [ - '(irish_pub || irish)' - ] - }, - 'hair_salon': { - '_id': 'eG4no7zpSnthwwcv5', - 'description': 'hairsalon', - 'variables': [ - 'var men_s_hair_salons;', - 'var hair_salons;', - 'var hair_stylists;', - 'var blow_dry_out_services;', - 'var barbers;' - ], - 'rules': [ - '(men_s_hair_salons || hair_salons || hair_stylists || blow_dry_out_services || barbers)' - ] - }, - 'eating_alone': { //collective narrative - '_id': 'eG4no7zpSnthwwcv6', - 'description': 'eating_alone', - 'variables': [ - 'var food_court;', - 'var dinning_hall;', - ], - 'rules': [ - '(food_court || dinning_hall)' - ] - }, - 'gas_station': { - '_id': 'xZBgjwdPw8rtg86eo', - 'description': 'gas_stations', - 'variables': [ - 'var gas_stations;' - ], - 'rules': [ - 'gas_stations' - ] - }, - 'coffee': { - '_id': '5DrGWRyMpu7WWFo7m', - 'description': 'coffee', - 'variables': [ - 'var coffee___tea;', - 'var cafes;', - 'var coffeeshops;' - ], - 'rules': [ - '(coffee___tea || cafes || coffeeshops)' - ] - }, - 'bank': { - '_id': 'qR9s4EtPngjZeEp9u', - 'description': 'banks', - 'variables': [ - 'var banks___credit_unions;' - ], - 'rules': [ - 'banks___credit_unions' - ] - }, - 'beer': { - '_id': 'i3yMtjdjTyJQRendD', - 'description': 'beer', - 'variables': [ - 'var beer_bar;', - 'var bars;', - 'var sports_bars;', - 'var dive_bars;', - 'var irish_pub;', - 'var pubs;', - 'var beer_tours;', - 'var beer_garden;', - 'var beer;', - 'var breweries;' - ], - 'rules': [ - '(bars || sports_bars || beer_bar || beer || dive_bars || irish_pub || pubs || beer_tours || beer_garden || breweries)' - ] - }, - 'train': { - '_id': 'mu8JcPRF7mEernyNQ', - 'description': 'trains', - 'variables': [ - 'var public_transportation;', - 'var trains;', - 'var train_stations;' - ], - 'rules': [ - '(trains || train_stations || public_transportation)' - ] - }, - 'forest': { - '_id': 'FfZnzP72ip4SLY4eR', - 'description': 'forests', - 'variables': [ - 'var campgrounds;', - 'var zoos;', - 'var parks;', - 'var botanical_gardens;', - 'var hiking;' - ], - 'rules': [ - '(campgrounds || botanical_gardens || hiking || zoos || parks)' - ] - }, - 'dinning_hall': { - '_id': 'sSK7rbbC9sHQBN94Y', - 'description': 'dinninghalls', - 'variables': [ - 'var diners;', - 'var restaurants;', - 'var cafeteria;', - 'var food_court;' - ], - 'rules': [ - '(diners || restaurants || cafeteria || food_court)' - ] - }, - 'castle': { - '_id': 'kMNownPaYRKxBXJfm', - 'description': 'castle', - 'variables': [ - 'var religious_schools;', - 'var churches;', - 'var landmarks___historical_buildings;', - 'var buddhist_temples;', - 'var hindu_temples;', - 'var synagogues;', - 'var mosques;' - ], - 'rules': [ - '(mosques || hindu_temples || buddhist_temples || synagogues || churches || religious_schools || landmarks___historical_buildings)' - ] - }, - 'bar': { - '_id': 'JLq2pGg8fizWGdZe2', - 'description': 'bars', - 'variables': [ - 'var dive_bars;', - 'var gay_bars;', - 'var country_dance_halls;', - 'var tapas_bars;', - 'var pool_halls;', - 'var champagne_bars;', - 'var club_crawl;', - 'var tiki_bars;', - 'var sports_bars;', - 'var island_pub;', - 'var karaoke;', - 'var piano_bars;', - 'var pop_up_restaurants;', - 'var irish_pub;', - 'var speakeasies;', - 'var lounges;', - 'var pubs;', - 'var whiskey_bars;', - 'var music_venues;', - 'var bar_crawl;', - 'var irish;', - 'var cocktail_bars;', - 'var bars;', - 'var nightlife;' - ], - 'rules': [ - '(dive_bars || gay_bars || tapas_bars || country_dance_halls || pool_halls || champagne_bars || club_crawl || tiki_bars || sports_bars || island_pub || karaoke || piano_bars || pop_up_restaurants || irish_pub || speakeasies || lounges || pubs || whiskey_bars || music_venues || bar_crawl || irish || bars || nightlife || cocktail_bars)' - ] - }, - 'grocery': { - '_id': 'jtCXkXBi4k6oJerxP', - 'description': 'grocery', - 'variables': [ - 'var ethnic_grocery;', - 'var international_grocery;', - 'var grocery;', - 'var fruits___veggies;', - 'var farmers_market;' - ], - 'rules': [ - '(farmers_market || international_grocery || ethnic_grocery || grocery || fruits___veggies)' - ] - }, - 'lake': { - '_id': '9iEpW4mb4ysHY5thP', - 'description': 'lake', - 'variables': [ - 'var lakes;' - ], - 'rules': [ - '(lakes)' - ] - }, - 'rainy': { - '_id': 'puLHKiGkLCJWpKc62', - 'description': 'rainy', - 'variables': [ - 'var rain;' - ], - 'rules': [ - '(rain)' - ] - }, - 'sunny': { - '_id': '6vyrBtdDAyRArMasj', - 'description': 'clear', - 'variables': [ - 'var clear;', - 'var daytime;', - ], - 'rules': [ - '(clear && daytime)' - ] - }, - 'cloudy': { - '_id': 'sorCvK53fyi5orAmj', - 'description': 'clouds', - 'variables': [ - 'var clouds;', - 'var daytime;', - ], - 'rules': [ - '(clouds && daytime)' - ] - }, - - 'restaurant': { - '_id': 'MzyBGuc6fLGR8Kjii', - 'description': 'restaurant', - 'variables': [ - 'var american__traditional_;', - 'var american__new_;', - 'var latin_american;', - 'var pizza;', - 'var pasta_shops;', - 'var burgers;', - 'var italian;', - 'var dominican;', - 'var trinidadian;', - 'var halal;', - 'var food_court;', - 'var arabian;', - 'var pakistani;', - 'var indian;', - 'var himalayan_nepalese;', - 'var afghan;', - 'var persian_iranian;', - 'var lebanese;', - 'var vegetarian;', - 'var middle_eastern;', - 'var kosher;', - 'var chinese;', - 'var mediterranean;', - 'var filipino;', - 'var puerto_rican;', - 'var ethnic_food;', - 'var african;', - 'var soul_food;', - 'var pub_food;', - 'var buffets;', - 'var mongolian;', - 'var brazilian;', - 'var hot_pot;', - 'var fast_food;', - 'var vegan;', - 'var sushi_bars;', - 'var salad;', - 'var japanese;', - 'var korean;', - 'var sandwiches;', - 'var imported_food;', - 'var restaurants;', - 'var diners;', - 'var barbeque;', - 'var soup;' - ], - 'rules': [ - '( american__traditional_ || american__new_ || latin_american || pizza || pasta_shops || burgers || italian || dominican || trinidadian || halal || food_court || arabian || pakistani || indian || himalayan_nepalese || afghan || persian_iranian || lebanese || vegetarian || middle_eastern || kosher || chinese || mediterranean || filipino || puerto_rican || ethnic_food || african || soul_food || pub_food || buffets || mongolian || brazilian || hot_pot || fast_food || vegan || sushi_bars || salad || japanese || korean || sandwiches || imported_food || restaurants || diners || barbeque || soup )' - ] - }, 'exercising': - { - "_id": "cTJmt5D6JGMNiJ3Yq", - "description": "exercising", - "variables": [ - 'var fitness___instruction;', - 'var climbing;', - 'var rock_climbing;', - 'var badminton;', - 'var parks;', - 'var golf;', - 'var bowling;', - 'var kickboxing;', - 'var circuit_training_gyms;', - 'var soccer;', - 'var professional_sports_teams;', - 'var boxing;', - 'var boot_camps;', - 'var amateur_sports_teams;', - 'var tennis;', - 'var cardio_classes;', - 'var interval_training_gyms;', - 'var pool_halls;', - 'var beach_volleyball;', - 'var fencing_clubs;', - 'var physical_therapy;', - 'var barre_classes;', - 'var trainers;', - 'var spin_classes;', - 'var cycling_classes;', - 'var gyms;', - 'var pilates;', - 'var squash;', - 'var martial_arts;', - 'var dance_studios;', - 'var surfing;', - 'var muay_thai;', - 'var weight_loss_centers;', - 'var sports_clubs;', - 'var aerial_fitness;', - 'var pole_dancing_classes;', - 'var brazilian_jiu_jitsu;', - 'var community_centers;' - ], - "rules": [ - "((((soccer || professional_sports_teams) || ((amateur_sports_teams || tennis) || ((pool_halls || beach_volleyball) || (fencing_clubs || physical_therapy)))) || ((kickboxing || circuit_training_gyms) || ((boxing || boot_camps) || ((cardio_classes || interval_training_gyms) || ((barre_classes || trainers) || ((spin_classes || cycling_classes) || ((gyms || fitness___instruction) || ((pilates || squash) || ((martial_arts || dance_studios) || ((surfing || muay_thai) || ((weight_loss_centers || sports_clubs) || ((aerial_fitness || pole_dancing_classes) || (brazilian_jiu_jitsu || community_centers))))))))))))) || (climbing || rock_climbing)) || (((badminton || parks) || (golf || bowling)) || fitness___instruction)" - ] - } - -}; - - -function createStorytime() { - - let storytimeCallback = function (sub) { - - Meteor.users.update({ - _id: sub.uid - }, { - $set: {'profile.staticAffordances.participatedInStorytime': true} - }); - - var affordance = sub.content.affordance; - - let options = [ - ['Drinking butterbeer', CONSTANTS.DETECTORS.beer_storytime._id], - ['Hogwarts Express at Platform 9 3/4', CONSTANTS.DETECTORS.train_storytime._id], - ['Forbidden Forest', CONSTANTS.DETECTORS.forest_storytime._id], - ['Dinner at the Great Hall', CONSTANTS.DETECTORS.dinning_hall_storytime._id], - ['Hogwarts Castle', CONSTANTS.DETECTORS.castle_storytime._id], - ['Quidditch Pitch', CONSTANTS.DETECTORS.field_storytime._id], - ['Training in the Room of Requirement ', CONSTANTS.DETECTORS.gym_storytime._id] - ]; - options = options.filter(function (x) { - return x[1] !== affordance; - }); - - let needName = 'page' + Random.id(3); - if (cb.numberOfSubmissions() == 7) { - needName = 'pageFinal' - } - let contribution = { - needName: needName, situation: {detector: affordance, number: '1'}, - toPass: { - instruction: sub.content.sentence, - dropdownChoices: {name: 'affordance', options: options} - }, numberNeeded: 1 - }; - addContribution(sub.iid, contribution); - }; - - let places = ["beer", "train", "forest", "dinning_hall", "castle", "field", "gym"]; - let detectorIds = ["N3uajhH3chDssFq3r", "Ly9vMvepymC4QNJqA", "52j9BfZ8DkZvSvhhf", "AKxSxuYBFqKP3auie", "LTnK6z94KQTJKTmZ8", "cDFgLqAAhtFWdmXkd", "H5P9ga8HHpCbxBza8", "M5SpmZQdc82GJ7xDj"]; - let i = 0; - _.forEach(places, (place) => { - - let newVars = JSON.parse(JSON.stringify(DETECTORS[place]['variables'])); - newVars.push('var participatedInStorytime;'); - - DETECTORS[place + "_storytime"] = { - '_id': detectorIds[i], - 'description': DETECTORS[place].description + "_storytime", - 'variables': newVars, - 'rules': ['(' + DETECTORS[place].rules[0] + ' ) && !participatedInStorytime;'] - }; - - i++; - }); - - let dropdownOptions = [ - ['Drinking butterbeer', DETECTORS.beer_storytime._id], - ['Hogwarts Express at Platform 9 3/4', DETECTORS.train_storytime._id], - ['Forbidden Forest', DETECTORS.forest_storytime._id], - ['Dinner at the Great Hall', DETECTORS.dinning_hall_storytime._id], - ['Hogwarts Castle', DETECTORS.castle_storytime._id], - ['Quidditch Pitch', DETECTORS.field_storytime._id], - ['Training in the Room of Requirement ', DETECTORS.gym_storytime._id] - ]; - - let firstSentence = 'Harry Potter looked up at the clouds swirling above him.'; - - let sendNotification = function (sub) { - let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { - return x.uid; - }); - notify(uids, sub.iid, 'Our story is finally complete. Click here to read it!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); - - }; - - - let experience = { - _id: "wGWTtQjmgEYSuRtrk", //Random.id(), - name: 'Storytime', - participateTemplate: 'storyPage', - resultsTemplate: 'storybook', - contributionTypes: [{ - needName: 'pageOne', situation: {detector: DETECTORS.niceish_day._id, number: '1'}, - toPass: { - instruction: firstSentence, - firstSentence: firstSentence, - dropdownChoices: { - name: 'affordance', options: dropdownOptions - } - }, - numberNeeded: 1 - }, - ], - description: 'We\'re writing a Harry Potter spin-off story', - notificationText: 'Help write a Harry Potter spin-off story!', - callbacks: [{ - trigger: 'cb.newSubmission() && (cb.numberOfSubmissions() <= 7)', - function: storytimeCallback.toString(), - }, { - trigger: 'cb.incidentFinished()', - function: sendNotification.toString() - }] - }; - return experience; -} - - -function createBumped() { - let experience = { - name: 'Bumped', - participateTemplate: 'bumped', - resultsTemplate: 'bumpedResults', - contributionTypes: [], - description: 'You just virtually bumped into someone!', - notificationText: 'You just virtually bumped into someone!', - callbacks: [] - }; - - let bumpedCallback = function (sub) { - let otherSub = Submissions.findOne({ - uid: {$ne: sub.uid}, - iid: sub.iid, - needName: sub.needName - }); - - notify([sub.uid, otherSub.uid], sub.iid, 'See a photo from who you virtually bumped into!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); - }; - - let relationships = ['lovesDTR', 'lovesGarrett', 'lovesMeg', 'lovesMaxine']; - let places = [['bar', 'bar'], ['coffee', 'coffee shop'], ['grocery', 'grocery store'], ['restaurant', "restaurant"]]; - _.forEach(relationships, (relationship) => { - _.forEach(places, (place) => { - - let newVars = JSON.parse(JSON.stringify(DETECTORS[place[0]]['variables'])); - newVars.push('var ' + relationship + ';'); - - let detector = { - '_id': Random.id(), - 'description': DETECTORS[place[0]].description + relationship, - 'variables': newVars, - 'rules': ['(' + DETECTORS[place[0]].rules[0] + ' && ' + relationship + ');'] - }; - DETECTORS[place[0] + relationship] = detector; - - for (let i = 0; i < 10; i++) { - let need = { - needName: place[0] + relationship + i, - situation: {detector: detector._id, number: '2'}, - toPass: {instruction: 'You are at a ' + place[1] + ' at the same time as '}, - numberNeeded: 2 - }; - let callback = { - trigger: 'cb.numberOfSubmissions(\'' + place[0] + relationship + i + '\') === 2', - function: bumpedCallback.toString(), - }; - - - experience.contributionTypes.push(need); - experience.callbacks.push(callback) - - } - - - }) - }); - - return experience; - -} - -let sendNotificationScavenger = function (sub) { - let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { - return x.uid; - }); - notify(uids, sub.iid, 'Wooh! All the scavenger hunt items were found. Click here to see all of them.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); - -}; -let sendNotificationSunset = function (sub) { - let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { - return x.uid; - }); - notify(uids, sub.iid, 'Our sunset timelapse is complete! Click here to see it.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); - -}; - - -let EXPERIENCES = { - 'bumped': createBumped(), - 'sunset': { - _id: Random.id(), - name: 'Sunset', - participateTemplate: 'uploadPhoto', - resultsTemplate: 'sunset', - contributionTypes: [{ - needName: 'sunset', situation: {detector: DETECTORS.sunset._id, number: '1'}, - toPass: {instruction: 'Take a photo of the sunset!'}, numberNeeded: 20 - }], - description: 'Create a timelapse of the sunset with others around the country', - notificationText: 'Take a photo of the sunset!', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationSunset.toString() - }] - }, - 'scavengerHunt': { - _id: Random.id(), - name: 'St. Patrick\'s Day Scavenger Hunt', - participateTemplate: 'scavengerHuntParticipate', - resultsTemplate: 'scavengerHunt', - contributionTypes: [{ - needName: 'beer', situation: {detector: DETECTORS.beer._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of beer?'}, numberNeeded: 1 - }, { - needName: 'greenProduce', situation: {detector: DETECTORS.produce._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of green vegetables? #leprechaunfood'}, numberNeeded: 1 - }, { - needName: 'coins', situation: {detector: DETECTORS.drugstore._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of chocolate gold coins on display?'}, numberNeeded: 1 - }, { - needName: 'leprechaun', situation: {detector: DETECTORS.costume_store._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a Leprechaun costume?'}, numberNeeded: 1 - }, { - needName: 'irishSign', situation: {detector: DETECTORS.irish._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of an Irish sign?'}, numberNeeded: 1 - }, { - needName: 'trimmings', situation: {detector: DETECTORS.hair_salon._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of some Leprechaun beard trimmings?'}, numberNeeded: 1 - }, { - needName: 'liquidGold', - situation: {detector: DETECTORS.gas_station._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of liquid gold that Leprechauns use to power their vehicles?'}, - numberNeeded: 1 - }, { - needName: 'potOfGold', - situation: {detector: DETECTORS.bank._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a bank where Leprechauns hide their pots of gold?'}, - numberNeeded: 1 - }, { - needName: 'rainbow', situation: {detector: DETECTORS.rainbow._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a rainbow flag?'}, numberNeeded: 1 - } - ], - description: 'Find an item for a scavenger hunt', - notificationText: 'Help us complete a St. Patrick\'s day scavenger hunt', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationScavenger.toString() - }] - }, - 'natureHunt': { - _id: Random.id(), - name: 'Nature Scavenger Hunt', - participateTemplate: 'scavengerHuntParticipate', - resultsTemplate: 'scavengerHunt', - contributionTypes: [{ - needName: 'tree', situation: {detector: DETECTORS.forest._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a tree?'}, numberNeeded: 1 - }, { - needName: 'leaf', situation: {detector: DETECTORS.forest._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a leaf?'}, numberNeeded: 1 - }, { - needName: 'grass', situation: {detector: DETECTORS.field._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the grass?'}, numberNeeded: 1 - }, { - needName: 'lake', situation: {detector: DETECTORS.lake._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the lake?'}, numberNeeded: 1 - }, { - needName: 'moon', situation: {detector: DETECTORS.night._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the moon?'}, numberNeeded: 1 - }, { - needName: 'sun', situation: {detector: DETECTORS.sunny._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the sun?'}, numberNeeded: 1 - }, { - needName: 'blueSky', situation: {detector: DETECTORS.sunny._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the blue sky?'}, numberNeeded: 1 - }, { - needName: 'clouds', situation: {detector: DETECTORS.cloudy._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the clouds?'}, numberNeeded: 1 - }, { - needName: 'puddle', situation: {detector: DETECTORS.rainy._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the puddle?'}, numberNeeded: 1 - }, - ], - description: 'Find an item for a scavenger hunt', - notificationText: 'Help us out with our nature scavenger hunt', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationScavenger.toString() - }] - }, - 'storyTime': createStorytime(), - -}; - - -export const CONSTANTS = { - 'LOCATIONS': LOCATIONS, - 'USERS': USERS, - 'EXPERIENCES': EXPERIENCES, - 'DETECTORS': DETECTORS - -}; - -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('b@gmail.com')._id, -// lat: 42.054902, //lakefill -// lng: -87.670197 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('c@gmail.com')._id, -// lat: 42.056975, //ford -// lng: -87.676575 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('d@gmail.com')._id, -// lat: 42.059273, //garage -// lng: -87.673794 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('e@gmail.com')._id, -// lat: 42.044314, //nevins -// lng: -87.682157 -// }); -// -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('g@gmail.com')._id, -// lat: 42.044314, //nevins -// lng: -87.682157 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('h@gmail.com')._id, -// lat: 42.045398, //pubs -// lng: -87.682431 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('i@gmail.com')._id, -// lat: 42.047621, //grocery, whole foods -// lng: -87.679488 -// }); -// Meteor.call('locations.updateUserLocationAndAffordances', { -// uid: Accounts.findUserByUsername('j@gmail.com')._id, -// lat: 42.042617, //beach -// lng: -87.671474 +import {addContribution} from '../incidents/methods'; +import {Submissions} from "../submissions/submissions"; +import {serverLog} from "../logs"; +import {Meteor} from "meteor/meteor"; + +let LOCATIONS = { + 'park': {lat: 42.056838, lng: -87.675940}, + 'lakefill': {lat: 42.054902, lng: -87.670197}, + 'burgers': {lat: 42.046131, lng: -87.681559}, + 'grocery': {lat: 42.047621, lng: -87.679488}, +}; + +let USERS = { + 'garrett': { + username: 'garrett', + password: 'password', + }, + 'garretts_brother': { + username: 'garretts_brother', + password: 'password', + }, + 'meg': { + username: 'meg', + password: 'password', + }, + 'megs_sister': { + username: 'megs_sister', + password: 'password', + }, + 'andrew': { + username: 'andrew', + password: 'password', + }, + 'josh': { + username: 'josh', + password: 'password', + } +}; + +let DETECTORS = { + 'field': { + '_id': 'rEbK6WMQnPPAGAXMX', + 'description': 'fields', + 'variables': [ + 'var baseball_fields;', + 'var stadiums___arenas;', + 'var soccer;', + 'var parks;' + ], + 'rules': [ + '(parks || soccer || baseball_fields || stadiums___arenas)' + ] + }, + 'niceish_day': { + '_id': 'x7EgLErQx3qmiemqt', + 'description': 'niceish_day', + 'variables': [ + 'var clouds;', + 'var clear;', + 'var daytime;' + ], + 'rules': [ + 'daytime && (clouds || clear)' + ] + }, + 'night': { + '_id': 'Wth3TB9Lcf6me6vgy', + 'description': 'places where it\'s nighttime,', + 'variables': [ + 'var nighttime;', + ], + 'rules': [ + '(nighttime)' + ] + }, + 'sunset': { + '_id': '44EXNzHS7oD2rbF68', + 'description': 'places where it\'s sunset,', + 'variables': [ + 'var sunset;', + 'var clear;', + ], + 'rules': [ + 'sunset && clear' + ] + }, + 'daytime': { + '_id': 'tyZMZvPKkkSPR4FpG', + 'description': 'places where it\'s daytime,', + 'variables': [ + 'var daytime;' + ], + 'rules': [ + 'daytime' + ] + }, + 'library': { + '_id': '5LqfPRajiQRe9BwBT', + 'description': ' libaries,', + 'variables': [ + 'var libraries;' + ], + 'rules': [ + ' libraries' + ] + }, + 'gym': { + '_id': '3XqHN8A4EpCZRpegS', + 'description': ' gym', + 'variables': [ + 'var gyms;' + ], + 'rules': [ + ' gyms' + ] + }, + 'produce': { + '_id': 'oHCMYfBBcaphXqQnT', + 'description': ' places where you can find fuits and veggies', + 'variables': [ + 'var grocery;', + 'var organic_stores;', + 'var fruits___veggies;', + 'var farmers_market;' + ], + 'rules': [ + '(grocery || organic_stores || fruits___veggies || farmers_market)' + ] + }, + 'rainbow': { + '_id': 'ksxGTXMaSpCFdmqqN', + 'description': 'rainbow flag', + 'variables': [ + 'var gay_bars;' + ], + 'rules': [ + 'gay_bars' + ] + }, + 'drugstore': { + '_id': 'k8KFfv3ATtbg2tnFB', + 'description': 'drugstores', + 'variables': [ + 'var drugstores;', + 'var pharmacy;' + ], + 'rules': [ + '(drugstores || pharmacy)' + ] + }, + 'costume_store': { + '_id': 'ECPk2mjuHJtrMotGg', + 'description': 'costume_store', + 'variables': [ + 'var costumes;', + 'var party_supplies;' + ], + 'rules': [ + '(party_supplies || costumes)' + ] + }, + 'irish': { + '_id': '5CJGGtjqyY89n55XP', + 'description': 'irish', + 'variables': [ + 'var irish_pub;', + 'var irish;' + ], + 'rules': [ + '(irish_pub || irish)' + ] + }, + 'hair_salon': { + '_id': 'eG4no7zpSnthwwcv5', + 'description': 'hairsalon', + 'variables': [ + 'var men_s_hair_salons;', + 'var hair_salons;', + 'var hair_stylists;', + 'var blow_dry_out_services;', + 'var barbers;' + ], + 'rules': [ + '(men_s_hair_salons || hair_salons || hair_stylists || blow_dry_out_services || barbers)' + ] + }, + 'eating_alone': { //collective narrative + '_id': 'eG4no7zpSnthwwcv6', + 'description': 'eating_alone', + 'variables': [ + 'var food_court;', + 'var dinning_hall;', + ], + 'rules': [ + '(food_court || dinning_hall)' + ] + }, + 'gas_station': { + '_id': 'xZBgjwdPw8rtg86eo', + 'description': 'gas_stations', + 'variables': [ + 'var gas_stations;' + ], + 'rules': [ + 'gas_stations' + ] + }, + 'coffee': { + '_id': '5DrGWRyMpu7WWFo7m', + 'description': 'coffee', + 'variables': [ + 'var coffee___tea;', + 'var cafes;', + 'var coffeeshops;' + ], + 'rules': [ + '(coffee___tea || cafes || coffeeshops)' + ] + }, + 'bank': { + '_id': 'qR9s4EtPngjZeEp9u', + 'description': 'banks', + 'variables': [ + 'var banks___credit_unions;' + ], + 'rules': [ + 'banks___credit_unions' + ] + }, + 'beer': { + '_id': 'i3yMtjdjTyJQRendD', + 'description': 'beer', + 'variables': [ + 'var beer_bar;', + 'var bars;', + 'var sports_bars;', + 'var dive_bars;', + 'var irish_pub;', + 'var pubs;', + 'var beer_tours;', + 'var beer_garden;', + 'var beer;', + 'var breweries;' + ], + 'rules': [ + '(bars || sports_bars || beer_bar || beer || dive_bars || irish_pub || pubs || beer_tours || beer_garden || breweries)' + ] + }, + 'train': { + '_id': 'mu8JcPRF7mEernyNQ', + 'description': 'trains', + 'variables': [ + 'var public_transportation;', + 'var trains;', + 'var train_stations;' + ], + 'rules': [ + '(trains || train_stations || public_transportation)' + ] + }, + 'forest': { + '_id': 'FfZnzP72ip4SLY4eR', + 'description': 'forests', + 'variables': [ + 'var campgrounds;', + 'var zoos;', + 'var parks;', + 'var botanical_gardens;', + 'var hiking;' + ], + 'rules': [ + '(campgrounds || botanical_gardens || hiking || zoos || parks)' + ] + }, + 'dinning_hall': { + '_id': 'sSK7rbbC9sHQBN94Y', + 'description': 'dinninghalls', + 'variables': [ + 'var diners;', + 'var restaurants;', + 'var cafeteria;', + 'var food_court;' + ], + 'rules': [ + '(diners || restaurants || cafeteria || food_court)' + ] + }, + 'castle': { + '_id': 'kMNownPaYRKxBXJfm', + 'description': 'castle', + 'variables': [ + 'var religious_schools;', + 'var churches;', + 'var landmarks___historical_buildings;', + 'var buddhist_temples;', + 'var hindu_temples;', + 'var synagogues;', + 'var mosques;' + ], + 'rules': [ + '(mosques || hindu_temples || buddhist_temples || synagogues || churches || religious_schools || landmarks___historical_buildings)' + ] + }, + 'bar': { + '_id': 'JLq2pGg8fizWGdZe2', + 'description': 'bars', + 'variables': [ + 'var dive_bars;', + 'var gay_bars;', + 'var country_dance_halls;', + 'var tapas_bars;', + 'var pool_halls;', + 'var champagne_bars;', + 'var club_crawl;', + 'var tiki_bars;', + 'var sports_bars;', + 'var island_pub;', + 'var karaoke;', + 'var piano_bars;', + 'var pop_up_restaurants;', + 'var irish_pub;', + 'var speakeasies;', + 'var lounges;', + 'var pubs;', + 'var whiskey_bars;', + 'var music_venues;', + 'var bar_crawl;', + 'var irish;', + 'var cocktail_bars;', + 'var bars;', + 'var nightlife;' + ], + 'rules': [ + '(dive_bars || gay_bars || tapas_bars || country_dance_halls || pool_halls || champagne_bars || club_crawl || tiki_bars || sports_bars || island_pub || karaoke || piano_bars || pop_up_restaurants || irish_pub || speakeasies || lounges || pubs || whiskey_bars || music_venues || bar_crawl || irish || bars || nightlife || cocktail_bars)' + ] + }, + 'grocery': { + '_id': 'jtCXkXBi4k6oJerxP', + 'description': 'grocery', + 'variables': [ + 'var ethnic_grocery;', + 'var international_grocery;', + 'var grocery;', + 'var fruits___veggies;', + 'var farmers_market;' + ], + 'rules': [ + '(farmers_market || international_grocery || ethnic_grocery || grocery || fruits___veggies)' + ] + }, + 'lake': { + '_id': '9iEpW4mb4ysHY5thP', + 'description': 'lake', + 'variables': [ + 'var lakes;' + ], + 'rules': [ + '(lakes)' + ] + }, + 'rainy': { + '_id': 'puLHKiGkLCJWpKc62', + 'description': 'rainy', + 'variables': [ + 'var rain;' + ], + 'rules': [ + '(rain)' + ] + }, + 'sunny': { + '_id': '6vyrBtdDAyRArMasj', + 'description': 'clear', + 'variables': [ + 'var clear;', + 'var daytime;', + ], + 'rules': [ + '(clear && daytime)' + ] + }, + 'cloudy': { + '_id': 'sorCvK53fyi5orAmj', + 'description': 'clouds', + 'variables': [ + 'var clouds;', + 'var daytime;', + ], + 'rules': [ + '(clouds && daytime)' + ] + }, + + 'restaurant': { + '_id': 'MzyBGuc6fLGR8Kjii', + 'description': 'restaurant', + 'variables': [ + 'var american__traditional_;', + 'var american__new_;', + 'var latin_american;', + 'var pizza;', + 'var pasta_shops;', + 'var burgers;', + 'var italian;', + 'var dominican;', + 'var trinidadian;', + 'var halal;', + 'var food_court;', + 'var arabian;', + 'var pakistani;', + 'var indian;', + 'var himalayan_nepalese;', + 'var afghan;', + 'var persian_iranian;', + 'var lebanese;', + 'var vegetarian;', + 'var middle_eastern;', + 'var kosher;', + 'var chinese;', + 'var mediterranean;', + 'var filipino;', + 'var puerto_rican;', + 'var ethnic_food;', + 'var african;', + 'var soul_food;', + 'var pub_food;', + 'var buffets;', + 'var mongolian;', + 'var brazilian;', + 'var hot_pot;', + 'var fast_food;', + 'var vegan;', + 'var sushi_bars;', + 'var salad;', + 'var japanese;', + 'var korean;', + 'var sandwiches;', + 'var imported_food;', + 'var restaurants;', + 'var diners;', + 'var barbeque;', + 'var soup;' + ], + 'rules': [ + '( american__traditional_ || american__new_ || latin_american || pizza || pasta_shops || burgers || italian || dominican || trinidadian || halal || food_court || arabian || pakistani || indian || himalayan_nepalese || afghan || persian_iranian || lebanese || vegetarian || middle_eastern || kosher || chinese || mediterranean || filipino || puerto_rican || ethnic_food || african || soul_food || pub_food || buffets || mongolian || brazilian || hot_pot || fast_food || vegan || sushi_bars || salad || japanese || korean || sandwiches || imported_food || restaurants || diners || barbeque || soup )' + ] + }, 'exercising': + { + "_id": "cTJmt5D6JGMNiJ3Yq", + "description": "exercising", + "variables": [ + 'var fitness___instruction;', + 'var climbing;', + 'var rock_climbing;', + 'var badminton;', + 'var parks;', + 'var golf;', + 'var bowling;', + 'var kickboxing;', + 'var circuit_training_gyms;', + 'var soccer;', + 'var professional_sports_teams;', + 'var boxing;', + 'var boot_camps;', + 'var amateur_sports_teams;', + 'var tennis;', + 'var cardio_classes;', + 'var interval_training_gyms;', + 'var pool_halls;', + 'var beach_volleyball;', + 'var fencing_clubs;', + 'var physical_therapy;', + 'var barre_classes;', + 'var trainers;', + 'var spin_classes;', + 'var cycling_classes;', + 'var gyms;', + 'var pilates;', + 'var squash;', + 'var martial_arts;', + 'var dance_studios;', + 'var surfing;', + 'var muay_thai;', + 'var weight_loss_centers;', + 'var sports_clubs;', + 'var aerial_fitness;', + 'var pole_dancing_classes;', + 'var brazilian_jiu_jitsu;', + 'var community_centers;' + ], + "rules": [ + "((((soccer || professional_sports_teams) || ((amateur_sports_teams || tennis) || ((pool_halls || beach_volleyball) || (fencing_clubs || physical_therapy)))) || ((kickboxing || circuit_training_gyms) || ((boxing || boot_camps) || ((cardio_classes || interval_training_gyms) || ((barre_classes || trainers) || ((spin_classes || cycling_classes) || ((gyms || fitness___instruction) || ((pilates || squash) || ((martial_arts || dance_studios) || ((surfing || muay_thai) || ((weight_loss_centers || sports_clubs) || ((aerial_fitness || pole_dancing_classes) || (brazilian_jiu_jitsu || community_centers))))))))))))) || (climbing || rock_climbing)) || (((badminton || parks) || (golf || bowling)) || fitness___instruction)" + ] + } + +}; + + +function createStorytime() { + + let storytimeCallback = function (sub) { + + Meteor.users.update({ + _id: sub.uid + }, { + $set: {'profile.staticAffordances.participatedInStorytime': true} + }); + + var affordance = sub.content.affordance; + + let options = [ + ['Drinking butterbeer', CONSTANTS.DETECTORS.beer_storytime._id], + ['Hogwarts Express at Platform 9 3/4', CONSTANTS.DETECTORS.train_storytime._id], + ['Forbidden Forest', CONSTANTS.DETECTORS.forest_storytime._id], + ['Dinner at the Great Hall', CONSTANTS.DETECTORS.dinning_hall_storytime._id], + ['Hogwarts Castle', CONSTANTS.DETECTORS.castle_storytime._id], + ['Quidditch Pitch', CONSTANTS.DETECTORS.field_storytime._id], + ['Training in the Room of Requirement ', CONSTANTS.DETECTORS.gym_storytime._id] + ]; + options = options.filter(function (x) { + return x[1] !== affordance; + }); + + let needName = 'page' + Random.id(3); + if (cb.numberOfSubmissions() == 7) { + needName = 'pageFinal' + } + let contribution = { + needName: needName, situation: {detector: affordance, number: '1'}, + toPass: { + instruction: sub.content.sentence, + dropdownChoices: {name: 'affordance', options: options} + }, numberNeeded: 1 + }; + addContribution(sub.iid, contribution); + }; + + let places = ["beer", "train", "forest", "dinning_hall", "castle", "field", "gym"]; + let detectorIds = ["N3uajhH3chDssFq3r", "Ly9vMvepymC4QNJqA", "52j9BfZ8DkZvSvhhf", "AKxSxuYBFqKP3auie", "LTnK6z94KQTJKTmZ8", "cDFgLqAAhtFWdmXkd", "H5P9ga8HHpCbxBza8", "M5SpmZQdc82GJ7xDj"]; + let i = 0; + _.forEach(places, (place) => { + + let newVars = JSON.parse(JSON.stringify(DETECTORS[place]['variables'])); + newVars.push('var participatedInStorytime;'); + + DETECTORS[place + "_storytime"] = { + '_id': detectorIds[i], + 'description': DETECTORS[place].description + "_storytime", + 'variables': newVars, + 'rules': ['(' + DETECTORS[place].rules[0] + ' ) && !participatedInStorytime;'] + }; + + i++; + }); + + let dropdownOptions = [ + ['Drinking butterbeer', DETECTORS.beer_storytime._id], + ['Hogwarts Express at Platform 9 3/4', DETECTORS.train_storytime._id], + ['Forbidden Forest', DETECTORS.forest_storytime._id], + ['Dinner at the Great Hall', DETECTORS.dinning_hall_storytime._id], + ['Hogwarts Castle', DETECTORS.castle_storytime._id], + ['Quidditch Pitch', DETECTORS.field_storytime._id], + ['Training in the Room of Requirement ', DETECTORS.gym_storytime._id] + ]; + + let firstSentence = 'Harry Potter looked up at the clouds swirling above him.'; + + let sendNotification = function (sub) { + let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { + return x.uid; + }); + notify(uids, sub.iid, 'Our story is finally complete. Click here to read it!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + + }; + + + let experience = { + _id: "wGWTtQjmgEYSuRtrk", //Random.id(), + name: 'Storytime', + participateTemplate: 'storyPage', + resultsTemplate: 'storybook', + contributionTypes: [{ + needName: 'pageOne', situation: {detector: DETECTORS.niceish_day._id, number: '1'}, + toPass: { + instruction: firstSentence, + firstSentence: firstSentence, + dropdownChoices: { + name: 'affordance', options: dropdownOptions + } + }, + numberNeeded: 1 + }, + ], + description: 'We\'re writing a Harry Potter spin-off story', + notificationText: 'Help write a Harry Potter spin-off story!', + callbacks: [{ + trigger: 'cb.newSubmission() && (cb.numberOfSubmissions() <= 7)', + function: storytimeCallback.toString(), + }, { + trigger: 'cb.incidentFinished()', + function: sendNotification.toString() + }] + }; + return experience; +} + + +function createBumped() { + let experience = { + name: 'Bumped', + participateTemplate: 'bumped', + resultsTemplate: 'bumpedResults', + contributionTypes: [], + description: 'You just virtually bumped into someone!', + notificationText: 'You just virtually bumped into someone!', + callbacks: [] + }; + + let bumpedCallback = function (sub) { + let otherSub = Submissions.findOne({ + uid: {$ne: sub.uid}, + iid: sub.iid, + needName: sub.needName + }); + + notify([sub.uid, otherSub.uid], sub.iid, 'See a photo from who you virtually bumped into!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + }; + + let relationships = ['lovesDTR', 'lovesGarrett', 'lovesMeg', 'lovesMaxine']; + let places = [['bar', 'bar'], ['coffee', 'coffee shop'], ['grocery', 'grocery store'], ['restaurant', "restaurant"]]; + _.forEach(relationships, (relationship) => { + _.forEach(places, (place) => { + + let newVars = JSON.parse(JSON.stringify(DETECTORS[place[0]]['variables'])); + newVars.push('var ' + relationship + ';'); + + let detector = { + '_id': Random.id(), + 'description': DETECTORS[place[0]].description + relationship, + 'variables': newVars, + 'rules': ['(' + DETECTORS[place[0]].rules[0] + ' && ' + relationship + ');'] + }; + DETECTORS[place[0] + relationship] = detector; + + for (let i = 0; i < 10; i++) { + let need = { + needName: place[0] + relationship + i, + situation: {detector: detector._id, number: '2'}, + toPass: {instruction: 'You are at a ' + place[1] + ' at the same time as '}, + numberNeeded: 2 + }; + let callback = { + trigger: 'cb.numberOfSubmissions(\'' + place[0] + relationship + i + '\') === 2', + function: bumpedCallback.toString(), + }; + + + experience.contributionTypes.push(need); + experience.callbacks.push(callback) + + } + + + }) + }); + + return experience; + +} + +let sendNotificationScavenger = function (sub) { + let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { + return x.uid; + }); + notify(uids, sub.iid, 'Wooh! All the scavenger hunt items were found. Click here to see all of them.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + +}; +let sendNotificationSunset = function (sub) { + let uids = Submissions.find({iid: sub.iid}).fetch().map(function (x) { + return x.uid; + }); + notify(uids, sub.iid, 'Our sunset timelapse is complete! Click here to see it.', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + +}; + +let update_chapter_map = function (chapter_title, next_chapter_title) { + chapter_map.chapter_title = next_chapter_title; +}; + +let chapter_map; + +function Setting(name, contexts) { + this.name = name; + this.contexts = contexts; +} + +function Chapter(title, setting, characters, objects) { + this.title = title; + this.setting = setting; + this.characters = characters; + this.objects = objects || []; + this.find_participants_for_character = find_participants_for_character; +} + +function Character(name, owner_chapters, owned_objects, actions, contexts, first_chapter_appearance) { + this.name = name; + this.owner_chapters = owner_chapters; + this.owned_objects = owned_objects || []; + this.actions = actions || {}; // map of arrays based on chapter + this.contexts = contexts || {}; // map of chapter_title to list of contexts + this.current_participant = null; + this.active_chapter = first_chapter_appearance; +} + + +function Action(description, change_character_and_object, priority) { + this.description = description; + this.change_character_and_object = change_character_and_object; + this.priority = priority; +} + +function Object(name, owner, first_chapter_appearance) { + this.name = name; + this.owner = owner || null; + this.active_chapter = first_chapter_appearance; +} + +let find_participants_for_chapter = function (setting) { + // query database +}; + +let find_participants_for_character = function (character, chapter) { + var setting = new Setting(character.context[chapter]); + // query database +}; + +let update_chapter_context = function (chapters) { + // add chapter-specific context + for (var i = 0; i < chapters.length; i++) { + chapter = chapters[i]; + for (var j = 0; j < chapter.characters.length; j++) { + chapter.characters[j].contexts[chapter.title] += chapter.setting.contexts; + } + } +} + +let update_character_context = function (character, chapter, contexts) { + var chapter_title = chapter.title; + // add character-specific context for this chapter + character.contexts[chapter_title] += " && " + contexts; +}; + +let add_action_to_character = function (character, chapter, action) { + character.actions[chapter.title].push(action); +}; + +function writeNarrative() { + // create setting + var Common_Room = new Setting("Common ROOM", "restaurant" /* "Common Room", "inside_a_building || in_a_school_building" */); + var Bedroom = new Setting("bedroom", "restaurant" /* "Bedroom", "inside_a_building || in_a_dorm" */); + + // create character + var bottle = new Object("bottle", "hermione", "1"); + var health = new Object("health", "ron", "2B"); + // name, chapters, objects, actions, contexts, first appearance + var harry = new Character("harry", ["1", "2A"], [], + {"1" : [], "2A" : [], "2B" : []}, {"1" : "", "2A" : "", "2B" : ""}, "1"); + var ron = new Character("ron", ["2B"], [], + {"1" : [], "2A" : [], "2B" : []}, {"1" : "", "2A" : "", "2B" : ""}, "2B"); + var hermione = new Character("hermione", ["1", "2B"], [], + {"1" : [], "2A" : [], "2B" : []}, {"1" : "", "2A" : "", "2B" : ""}, "1"); + + // create chapter + var chapter_one = new Chapter("1", Common_Room, [harry, hermione], [bottle]); + var chapter_twoA = new Chapter("2A", Bedroom, [harry], []); + var chapter_twoB = new Chapter("2B", Common_Room, [harry, ron, hermione], []); + + // update context + update_chapter_context([chapter_one, chapter_twoA, chapter_twoB]); + /* + update_character_context(harry, chapter_one, "sitting_by_table"); + update_character_context(harry, chapter_twoA, "sitting_by_bed"); + update_character_context(hermione, chapter_one, "has_a_bottle"); + update_character_context(ron, chapter_twoB, "sitting_by_table"); + */ + + // update action + let a1_give_harry_bottle = function () { + bottle.owner = "harry"; + harry.owned_objects.push(bottle); + }; + + let a1_take_potion_to_bed = function () { + update_chapter_map("1", "2"); + }; + + let a1_leave_potion_on_table = function () { + bottle.owner = null; + harry.owned_objects.remove(bottle); + update_chapter_map("1", "2B"); + }; + + let a2_leave_bedroom = function () { + bottle.owner = null; + harry.owned_objects.remove(bottle); + }; + + let a2B_drink_potion_bottle = function () { + health.owner = null; + ron.owned_objects.remove(health); + }; + + let a2B_rush_ron_outside = function () { + hermione.active_chapter = "3B"; + ron.active_chapter = "3B"; + }; + + let a2B_take_potion_to_class = function () { + bottle.owner = "harry"; + harry.owned_objects.push(bottle); + }; + + add_action_to_character(harry, chapter_one, new Action("Take potion bottle with him to bed", a1_take_potion_to_bed, 1)); + add_action_to_character(harry, chapter_one, new Action("Leave potion bottle on table", a1_leave_potion_on_table, 1)); + add_action_to_character(harry, chapter_twoA, new Action("Leave the bedroom", a2_leave_bedroom, 0)); + add_action_to_character(harry, chapter_twoB, new Action("Takes potion bottle with him to class", a2B_take_potion_to_class, 2)); + + add_action_to_character(ron, chapter_twoB, new Action("Drinks out of potion bottle", a2B_drink_potion_bottle, 0)); + + add_action_to_character(hermione, chapter_one, new Action("Give Harry a portion bottle", a1_give_harry_bottle, 0)); + add_action_to_character(hermione, chapter_twoB, new Action("Rush Ron outside", a2B_rush_ron_outside, 1)); + + return chapter_one; +} + +function convertChapterToExperience(chapter) { + // total list of actions that will be completed in the chapter + + console.log("DEBUG [creating chapter actions]"); + let chapterActions = []; + + for (let character of chapter.characters) { + for (let action of character.actions[chapter.title]) { + chapterActions.push(new Action(action.description, Random.id(), action.priority)); + console.log("DEBUG action description = " + action.description); + console.log("DEBUG action priority = " + action.priority); + } + } + // find the first action + let first_action = chapterActions[0]; + let max_priority_allowed = 0; + console.log("DEBUG first_action created = " + first_action.description); + + for (let action of chapterActions) { + if (action.priority < first_action.priority) { + first_action = action; + } + if (action.priority > max_priority_allowed) { + max_priority_allowed = action.priority; + } + } + console.log("DEBUG first_action updated = " + first_action.description); + console.log("DEBUG max_priority_allowed = " + max_priority_allowed); + + //need a way to keep number of already completed actions + let number_of_actions_done = 0; + + chapterActions = chapterActions.filter(function(x) { + return x.priority == number_of_actions_done; + }); + + console.log("DEBUG chapterAction size updated = " + chapterActions.length); + + console.log("DEBUG [creating send notification]"); + let sendNotification = function(sub) { + let uids = Submissions.find({ iid: sub.iid }).fetch().map(function(x) { + return x.uid; + }); + notify( + uids, + sub.iid, + "Chapter 1 is complete. Find out what happened here!", + "", + "/apicustomresults/" + sub.iid + "/" + sub.eid + ); + }; + + console.log("DEBUG [creating callback]"); + let hpStoryCallback = function(sub) { + var newSet = "profile.staticAffordances.participatedInPotterNarrative" + chapter.title; + Meteor.users.update( + {_id: sub.uid}, + {$set: {newSet : true}} + ); + // an action has now been performed + number_of_actions_done += 1; + //not sure if this is still needed + let affordance = sub.content.affordance; + // takes the list of actions within the chapter + let options = chapterActions; + // filters out all the actions that cannot be done at the moment + options = options.filter(function(x) { + return x[2] == number_of_actions_done; + }); + // which action in the chapter is being completed + let needName = "Action" + Random.id(3); + if (cb.numberOfSubmissions() === 2) { + needName = "pageFinal"; + } + let contribution = { + needName: needName, + situation: { detector: affordance, number: "1" }, + toPass: { + instruction: "You received a bottle from Hermione, what's next?", + dropdownChoices: { name: "affordance", options: options } + }, + numberNeeded: 1 + }; + addContribution(sub.iid, contribution); + }; + + console.log("DEBUG [creating character contexts]"); + var character_contexts = []; + + for (let character of chapter.characters) { + let character_context = [character.contexts[chapter.title], character.name]; + console.log("DEBUG character context = " + character_context[0]); + console.log("DEBUG character name = " + character_context[1]); + character_contexts.push(character_context); + } + console.log("DEBUG character contexts size = " + character_contexts.length); + + console.log("DEBUG [creating experience]"); + let experience = { + name: chapter.title, + participateTemplate: "Harry_Potter_Story", + resultsTemplate: "Harry_Potter_Story_Result", + contributionTypes: [], + description: chapter.title, + notificationText: "A new chapter [" + chapter.title + "] has begun!", + callbacks: [ + { + trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= " + max_priority_allowed + ")", + function: hpStoryCallback.toString() + }, + { + trigger: "cb.incidentFinished()", + function: sendNotification.toString() //start the next chapter + } + ] + }; + console.log("DEBUG experience name = " + experience.name); + console.log("DEBUG experience resultsTemplate = " + experience.resultsTemplate); + console.log("DEBUG experience contributionTypes size = " + experience.contributionTypes.length); + console.log("DEBUG experience description = " + experience.description); + console.log("DEBUG experience notificationText = " + experience.notificationText); + console.log("DEBUG experience callbacks size = " + experience.callbacks.length); + + // set up detectors + console.log("DEBUG [creating detectors]"); + let detectorIds = [ + Random.id(), + Random.id() + ]; + let i = 0; + _.forEach(character_contexts, character_context => { + let newVars = JSON.parse( + JSON.stringify(DETECTORS[character_context[0]]["variables"]) + ); + newVars.push("var participatedInPotterNarrative" + chapter.title + ";"); + + let detector = { + '_id': detectorIds[i], + 'description': DETECTORS[character_context[0]].description + "_PotterNarrative" + chapter.title, + 'variables': newVars, + 'rules': [ + "(" + DETECTORS[character_context[0]].rules[0] + + " ) && !participatedInPotterNarrative" + chapter.title + ";"] + }; + console.log("DEBUG detector [" + i + "]"); + console.log("DEBUG id = " + detector._id); + console.log("DEBUG description = " + detector.description); + console.log("DEBUG variables" + detector.variables); + console.log("DEBUG rules = " + detector.rules[0]); + + DETECTORS[character_context[0]] = detector; + + if (i == 0) { + // insert first need + let need = { + needName: first_action.description, //should be the title of the action + situation: {detector: DETECTORS[character_context[0]]._id, number: "1"}, + toPass: { + instruction: first_action.description, + firstSentence: chapter.title, + dropdownChoices: { + name: "affordance", + options: chapterActions + } + }, + numberNeeded: 1 + }; + experience.contributionTypes.push(need); + } + i++; + }); + // Experiences.insert(["HPStory": exp]); + //let incident = createIncidentFromExperience(exp); + //startRunningIncident(incident); + return experience; +} + +let chapterOne = writeNarrative(); + +let EXPERIENCES = { + 'hpstory': convertChapterToExperience(chapterOne), + 'bumped': createBumped(), + 'sunset': { + _id: Random.id(), + name: 'Sunset', + participateTemplate: 'uploadPhoto', + resultsTemplate: 'sunset', + contributionTypes: [{ + needName: 'sunset', situation: {detector: DETECTORS.sunset._id, number: '1'}, + toPass: {instruction: 'Take a photo of the sunset!'}, numberNeeded: 20 + }], + description: 'Create a timelapse of the sunset with others around the country', + notificationText: 'Take a photo of the sunset!', + callbacks: [{ + trigger: 'cb.incidentFinished()', + function: sendNotificationSunset.toString() + }] + }, + 'scavengerHunt': { + _id: Random.id(), + name: 'St. Patrick\'s Day Scavenger Hunt', + participateTemplate: 'scavengerHuntParticipate', + resultsTemplate: 'scavengerHunt', + contributionTypes: [{ + needName: 'beer', situation: {detector: DETECTORS.beer._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of beer?'}, numberNeeded: 1 + }, { + needName: 'greenProduce', situation: {detector: DETECTORS.produce._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of green vegetables? #leprechaunfood'}, numberNeeded: 1 + }, { + needName: 'coins', situation: {detector: DETECTORS.drugstore._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of chocolate gold coins on display?'}, numberNeeded: 1 + }, { + needName: 'leprechaun', situation: {detector: DETECTORS.costume_store._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of a Leprechaun costume?'}, numberNeeded: 1 + }, { + needName: 'irishSign', situation: {detector: DETECTORS.irish._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of an Irish sign?'}, numberNeeded: 1 + }, { + needName: 'trimmings', situation: {detector: DETECTORS.hair_salon._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of some Leprechaun beard trimmings?'}, numberNeeded: 1 + }, { + needName: 'liquidGold', + situation: {detector: DETECTORS.gas_station._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of liquid gold that Leprechauns use to power their vehicles?'}, + numberNeeded: 1 + }, { + needName: 'potOfGold', + situation: {detector: DETECTORS.bank._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of a bank where Leprechauns hide their pots of gold?'}, + numberNeeded: 1 + }, { + needName: 'rainbow', situation: {detector: DETECTORS.rainbow._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of a rainbow flag?'}, numberNeeded: 1 + } + ], + description: 'Find an item for a scavenger hunt', + notificationText: 'Help us complete a St. Patrick\'s day scavenger hunt', + callbacks: [{ + trigger: 'cb.incidentFinished()', + function: sendNotificationScavenger.toString() + }] + }, + 'natureHunt': { + _id: Random.id(), + name: 'Nature Scavenger Hunt', + participateTemplate: 'scavengerHuntParticipate', + resultsTemplate: 'scavengerHunt', + contributionTypes: [{ + needName: 'tree', situation: {detector: DETECTORS.forest._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of a tree?'}, numberNeeded: 1 + }, { + needName: 'leaf', situation: {detector: DETECTORS.forest._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of a leaf?'}, numberNeeded: 1 + }, { + needName: 'grass', situation: {detector: DETECTORS.field._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the grass?'}, numberNeeded: 1 + }, { + needName: 'lake', situation: {detector: DETECTORS.lake._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the lake?'}, numberNeeded: 1 + }, { + needName: 'moon', situation: {detector: DETECTORS.night._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the moon?'}, numberNeeded: 1 + }, { + needName: 'sun', situation: {detector: DETECTORS.sunny._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the sun?'}, numberNeeded: 1 + }, { + needName: 'blueSky', situation: {detector: DETECTORS.sunny._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the blue sky?'}, numberNeeded: 1 + }, { + needName: 'clouds', situation: {detector: DETECTORS.cloudy._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the clouds?'}, numberNeeded: 1 + }, { + needName: 'puddle', situation: {detector: DETECTORS.rainy._id, number: '1'}, + toPass: {instruction: 'Can you take a photo of the puddle?'}, numberNeeded: 1 + }, + ], + description: 'Find an item for a scavenger hunt', + notificationText: 'Help us out with our nature scavenger hunt', + callbacks: [{ + trigger: 'cb.incidentFinished()', + function: sendNotificationScavenger.toString() + }] + }, + 'storyTime': createStorytime(), +}; + + +export const CONSTANTS = { + 'LOCATIONS': LOCATIONS, + 'USERS': USERS, + 'EXPERIENCES': EXPERIENCES, + 'DETECTORS': DETECTORS +}; + +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('b@gmail.com')._id, +// lat: 42.054902, //lakefill +// lng: -87.670197 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('c@gmail.com')._id, +// lat: 42.056975, //ford +// lng: -87.676575 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('d@gmail.com')._id, +// lat: 42.059273, //garage +// lng: -87.673794 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('e@gmail.com')._id, +// lat: 42.044314, //nevins +// lng: -87.682157 +// }); +// +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('g@gmail.com')._id, +// lat: 42.044314, //nevins +// lng: -87.682157 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('h@gmail.com')._id, +// lat: 42.045398, //pubs +// lng: -87.682431 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('i@gmail.com')._id, +// lat: 42.047621, //grocery, whole foods +// lng: -87.679488 +// }); +// Meteor.call('locations.updateUserLocationAndAffordances', { +// uid: Accounts.findUserByUsername('j@gmail.com')._id, +// lat: 42.042617, //beach +// lng: -87.671474 \ No newline at end of file diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index f00a91e8..597a83f1 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -244,3 +244,21 @@

Let's celebrate Thanksgiving break

+ + diff --git a/imports/ui/pages/api_custom_results.html b/imports/ui/pages/api_custom_results.html index 1f52265e..a7d3d82a 100644 --- a/imports/ui/pages/api_custom_results.html +++ b/imports/ui/pages/api_custom_results.html @@ -322,6 +322,12 @@

Food Fight Results


+ + From 715953c5b9d5e487fc8427ab60a2b7d70dfeeb6e Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Fri, 1 Jun 2018 12:54:35 -0500 Subject: [PATCH 007/132] After meeting with CE, experience now runs --- imports/api/detectors/methods.js | 3 +- imports/api/testing/testingconstants.js | 228 +++++++++++++----------- 2 files changed, 121 insertions(+), 110 deletions(-) diff --git a/imports/api/detectors/methods.js b/imports/api/detectors/methods.js index 84385b79..3cbd01f8 100644 --- a/imports/api/detectors/methods.js +++ b/imports/api/detectors/methods.js @@ -29,7 +29,8 @@ export const getAffordancesFromLocation = function (lat, lng, callback) { export const matchAffordancesWithDetector = function (affordances, detectorId) { const detector = Detectors.findOne({ _id: detectorId }); - + console.log("dectectorId = " + detectorId); + console.log("dectector = " + detector); let doesUserMatchSituation = applyDetector(affordances, detector.variables, detector.rules); diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index c3a5b6e8..e3012cb2 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -2,6 +2,7 @@ import {addContribution} from '../incidents/methods'; import {Submissions} from "../submissions/submissions"; import {serverLog} from "../logs"; import {Meteor} from "meteor/meteor"; +import { Detectors } from "../detectors/detectors"; let LOCATIONS = { 'park': {lat: 42.056838, lng: -87.675940}, @@ -932,7 +933,7 @@ function convertChapterToExperience(chapter) { console.log("DEBUG [creating experience]"); let experience = { - name: chapter.title, + name: "Harry Potter story chapter " + chapter.title, participateTemplate: "Harry_Potter_Story", resultsTemplate: "Harry_Potter_Story_Result", contributionTypes: [], @@ -959,8 +960,8 @@ function convertChapterToExperience(chapter) { // set up detectors console.log("DEBUG [creating detectors]"); let detectorIds = [ - Random.id(), - Random.id() + "oFCWkpZ3MSdXXyKbu", + "oFCWkpZ3MSdXXyKbb" ]; let i = 0; _.forEach(character_contexts, character_context => { @@ -975,7 +976,7 @@ function convertChapterToExperience(chapter) { 'variables': newVars, 'rules': [ "(" + DETECTORS[character_context[0]].rules[0] + - " ) && !participatedInPotterNarrative" + chapter.title + ";"] + " ) && !participatedInPotterNarrative_" + chapter.title + ";"] }; console.log("DEBUG detector [" + i + "]"); console.log("DEBUG id = " + detector._id); @@ -983,8 +984,17 @@ function convertChapterToExperience(chapter) { console.log("DEBUG variables" + detector.variables); console.log("DEBUG rules = " + detector.rules[0]); - DETECTORS[character_context[0]] = detector; - + // DETECTORS[character_context[0]] = detector; + Detectors.insert(detector, (err, docs) => { + if (err) { + console.log("ERROR"); + console.log("error = " + err); + } else { + console.log("Detector addded -"); + console.log("docs = " + docs); + } + }); + if (i == 0) { // insert first need let need = { @@ -1015,109 +1025,109 @@ let chapterOne = writeNarrative(); let EXPERIENCES = { 'hpstory': convertChapterToExperience(chapterOne), 'bumped': createBumped(), - 'sunset': { - _id: Random.id(), - name: 'Sunset', - participateTemplate: 'uploadPhoto', - resultsTemplate: 'sunset', - contributionTypes: [{ - needName: 'sunset', situation: {detector: DETECTORS.sunset._id, number: '1'}, - toPass: {instruction: 'Take a photo of the sunset!'}, numberNeeded: 20 - }], - description: 'Create a timelapse of the sunset with others around the country', - notificationText: 'Take a photo of the sunset!', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationSunset.toString() - }] - }, - 'scavengerHunt': { - _id: Random.id(), - name: 'St. Patrick\'s Day Scavenger Hunt', - participateTemplate: 'scavengerHuntParticipate', - resultsTemplate: 'scavengerHunt', - contributionTypes: [{ - needName: 'beer', situation: {detector: DETECTORS.beer._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of beer?'}, numberNeeded: 1 - }, { - needName: 'greenProduce', situation: {detector: DETECTORS.produce._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of green vegetables? #leprechaunfood'}, numberNeeded: 1 - }, { - needName: 'coins', situation: {detector: DETECTORS.drugstore._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of chocolate gold coins on display?'}, numberNeeded: 1 - }, { - needName: 'leprechaun', situation: {detector: DETECTORS.costume_store._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a Leprechaun costume?'}, numberNeeded: 1 - }, { - needName: 'irishSign', situation: {detector: DETECTORS.irish._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of an Irish sign?'}, numberNeeded: 1 - }, { - needName: 'trimmings', situation: {detector: DETECTORS.hair_salon._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of some Leprechaun beard trimmings?'}, numberNeeded: 1 - }, { - needName: 'liquidGold', - situation: {detector: DETECTORS.gas_station._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of liquid gold that Leprechauns use to power their vehicles?'}, - numberNeeded: 1 - }, { - needName: 'potOfGold', - situation: {detector: DETECTORS.bank._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a bank where Leprechauns hide their pots of gold?'}, - numberNeeded: 1 - }, { - needName: 'rainbow', situation: {detector: DETECTORS.rainbow._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a rainbow flag?'}, numberNeeded: 1 - } - ], - description: 'Find an item for a scavenger hunt', - notificationText: 'Help us complete a St. Patrick\'s day scavenger hunt', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationScavenger.toString() - }] - }, - 'natureHunt': { - _id: Random.id(), - name: 'Nature Scavenger Hunt', - participateTemplate: 'scavengerHuntParticipate', - resultsTemplate: 'scavengerHunt', - contributionTypes: [{ - needName: 'tree', situation: {detector: DETECTORS.forest._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a tree?'}, numberNeeded: 1 - }, { - needName: 'leaf', situation: {detector: DETECTORS.forest._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of a leaf?'}, numberNeeded: 1 - }, { - needName: 'grass', situation: {detector: DETECTORS.field._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the grass?'}, numberNeeded: 1 - }, { - needName: 'lake', situation: {detector: DETECTORS.lake._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the lake?'}, numberNeeded: 1 - }, { - needName: 'moon', situation: {detector: DETECTORS.night._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the moon?'}, numberNeeded: 1 - }, { - needName: 'sun', situation: {detector: DETECTORS.sunny._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the sun?'}, numberNeeded: 1 - }, { - needName: 'blueSky', situation: {detector: DETECTORS.sunny._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the blue sky?'}, numberNeeded: 1 - }, { - needName: 'clouds', situation: {detector: DETECTORS.cloudy._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the clouds?'}, numberNeeded: 1 - }, { - needName: 'puddle', situation: {detector: DETECTORS.rainy._id, number: '1'}, - toPass: {instruction: 'Can you take a photo of the puddle?'}, numberNeeded: 1 - }, - ], - description: 'Find an item for a scavenger hunt', - notificationText: 'Help us out with our nature scavenger hunt', - callbacks: [{ - trigger: 'cb.incidentFinished()', - function: sendNotificationScavenger.toString() - }] - }, - 'storyTime': createStorytime(), + // 'sunset': { + // _id: Random.id(), + // name: 'Sunset', + // participateTemplate: 'uploadPhoto', + // resultsTemplate: 'sunset', + // contributionTypes: [{ + // needName: 'sunset', situation: {detector: DETECTORS.sunset._id, number: '1'}, + // toPass: {instruction: 'Take a photo of the sunset!'}, numberNeeded: 20 + // }], + // description: 'Create a timelapse of the sunset with others around the country', + // notificationText: 'Take a photo of the sunset!', + // callbacks: [{ + // trigger: 'cb.incidentFinished()', + // function: sendNotificationSunset.toString() + // }] + // }, + // 'scavengerHunt': { + // _id: Random.id(), + // name: 'St. Patrick\'s Day Scavenger Hunt', + // participateTemplate: 'scavengerHuntParticipate', + // resultsTemplate: 'scavengerHunt', + // contributionTypes: [{ + // needName: 'beer', situation: {detector: DETECTORS.beer._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of beer?'}, numberNeeded: 1 + // }, { + // needName: 'greenProduce', situation: {detector: DETECTORS.produce._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of green vegetables? #leprechaunfood'}, numberNeeded: 1 + // }, { + // needName: 'coins', situation: {detector: DETECTORS.drugstore._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of chocolate gold coins on display?'}, numberNeeded: 1 + // }, { + // needName: 'leprechaun', situation: {detector: DETECTORS.costume_store._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of a Leprechaun costume?'}, numberNeeded: 1 + // }, { + // needName: 'irishSign', situation: {detector: DETECTORS.irish._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of an Irish sign?'}, numberNeeded: 1 + // }, { + // needName: 'trimmings', situation: {detector: DETECTORS.hair_salon._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of some Leprechaun beard trimmings?'}, numberNeeded: 1 + // }, { + // needName: 'liquidGold', + // situation: {detector: DETECTORS.gas_station._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of liquid gold that Leprechauns use to power their vehicles?'}, + // numberNeeded: 1 + // }, { + // needName: 'potOfGold', + // situation: {detector: DETECTORS.bank._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of a bank where Leprechauns hide their pots of gold?'}, + // numberNeeded: 1 + // }, { + // needName: 'rainbow', situation: {detector: DETECTORS.rainbow._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of a rainbow flag?'}, numberNeeded: 1 + // } + // ], + // description: 'Find an item for a scavenger hunt', + // notificationText: 'Help us complete a St. Patrick\'s day scavenger hunt', + // callbacks: [{ + // trigger: 'cb.incidentFinished()', + // function: sendNotificationScavenger.toString() + // }] + // }, + // 'natureHunt': { + // _id: Random.id(), + // name: 'Nature Scavenger Hunt', + // participateTemplate: 'scavengerHuntParticipate', + // resultsTemplate: 'scavengerHunt', + // contributionTypes: [{ + // needName: 'tree', situation: {detector: DETECTORS.forest._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of a tree?'}, numberNeeded: 1 + // }, { + // needName: 'leaf', situation: {detector: DETECTORS.forest._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of a leaf?'}, numberNeeded: 1 + // }, { + // needName: 'grass', situation: {detector: DETECTORS.field._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the grass?'}, numberNeeded: 1 + // }, { + // needName: 'lake', situation: {detector: DETECTORS.lake._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the lake?'}, numberNeeded: 1 + // }, { + // needName: 'moon', situation: {detector: DETECTORS.night._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the moon?'}, numberNeeded: 1 + // }, { + // needName: 'sun', situation: {detector: DETECTORS.sunny._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the sun?'}, numberNeeded: 1 + // }, { + // needName: 'blueSky', situation: {detector: DETECTORS.sunny._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the blue sky?'}, numberNeeded: 1 + // }, { + // needName: 'clouds', situation: {detector: DETECTORS.cloudy._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the clouds?'}, numberNeeded: 1 + // }, { + // needName: 'puddle', situation: {detector: DETECTORS.rainy._id, number: '1'}, + // toPass: {instruction: 'Can you take a photo of the puddle?'}, numberNeeded: 1 + // }, + // ], + // description: 'Find an item for a scavenger hunt', + // notificationText: 'Help us out with our nature scavenger hunt', + // callbacks: [{ + // trigger: 'cb.incidentFinished()', + // function: sendNotificationScavenger.toString() + // }] + // }, + // 'storyTime': createStorytime(), }; From dfff17ca68c788cb6a9acad7716aab39e317e476 Mon Sep 17 00:00:00 2001 From: wangsanfeng1998 Date: Mon, 4 Jun 2018 17:23:15 -0500 Subject: [PATCH 008/132] changed instruction to be the previous need --- imports/api/testing/testingconstants.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index e3012cb2..f11f569e 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -894,17 +894,17 @@ function convertChapterToExperience(chapter) { {$set: {newSet : true}} ); // an action has now been performed - number_of_actions_done += 1; + number_of_actions_done += 1; //not sure if this is still needed let affordance = sub.content.affordance; // takes the list of actions within the chapter - let options = chapterActions; + let options = chapterActions; // filters out all the actions that cannot be done at the moment - options = options.filter(function(x) { + options = options.filter(function(x) { return x[2] == number_of_actions_done; }); // which action in the chapter is being completed - let needName = "Action" + Random.id(3); + let needName = "Action" + Random.id(3); if (cb.numberOfSubmissions() === 2) { needName = "pageFinal"; } @@ -912,7 +912,7 @@ function convertChapterToExperience(chapter) { needName: needName, situation: { detector: affordance, number: "1" }, toPass: { - instruction: "You received a bottle from Hermione, what's next?", + instruction: sub.needName, dropdownChoices: { name: "affordance", options: options } }, numberNeeded: 1 @@ -957,7 +957,7 @@ function convertChapterToExperience(chapter) { console.log("DEBUG experience notificationText = " + experience.notificationText); console.log("DEBUG experience callbacks size = " + experience.callbacks.length); - // set up detectors + // set up detectors console.log("DEBUG [creating detectors]"); let detectorIds = [ "oFCWkpZ3MSdXXyKbu", @@ -996,7 +996,7 @@ function convertChapterToExperience(chapter) { }); if (i == 0) { - // insert first need + // insert first need let need = { needName: first_action.description, //should be the title of the action situation: {detector: DETECTORS[character_context[0]]._id, number: "1"}, @@ -1177,4 +1177,4 @@ export const CONSTANTS = { // Meteor.call('locations.updateUserLocationAndAffordances', { // uid: Accounts.findUserByUsername('j@gmail.com')._id, // lat: 42.042617, //beach -// lng: -87.671474 \ No newline at end of file +// lng: -87.671474 From 9b03548fa9d8aaef80d96247b5fc839d84de35cb Mon Sep 17 00:00:00 2001 From: wangsanfeng1998 Date: Wed, 6 Jun 2018 20:32:47 -0500 Subject: [PATCH 009/132] testing HTML character --- imports/api/testing/testingconstants.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index f11f569e..afd64a67 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -908,11 +908,21 @@ function convertChapterToExperience(chapter) { if (cb.numberOfSubmissions() === 2) { needName = "pageFinal"; } + //finding the character of the action + let next_action = options[0]; + for (let character of chapter.characters) { + for (let action of character.actions[chapter.title]) { + if (action == next_action) { + let next_character = character; + } + } + } let contribution = { needName: needName, situation: { detector: affordance, number: "1" }, toPass: { - instruction: sub.needName, + characterName: next_character; + instruction: sub.needName, dropdownChoices: { name: "affordance", options: options } }, numberNeeded: 1 @@ -1001,7 +1011,8 @@ function convertChapterToExperience(chapter) { needName: first_action.description, //should be the title of the action situation: {detector: DETECTORS[character_context[0]]._id, number: "1"}, toPass: { - instruction: first_action.description, + characterName: character_context[1], + instruction: "Please choose from the following list of actions", firstSentence: chapter.title, dropdownChoices: { name: "affordance", From e3d5ff6c2e4d59ed59ba4f072bdc6fe78284366c Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Wed, 6 Jun 2018 20:40:59 -0500 Subject: [PATCH 010/132] Changes to template --- imports/api/testing/testingconstants.js | 20 +++++++++++++++----- imports/ui/pages/api_custom.html | 7 +++++-- imports/ui/pages/api_custom.js | 1 + 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index afd64a67..7e1a7125 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -910,10 +910,11 @@ function convertChapterToExperience(chapter) { } //finding the character of the action let next_action = options[0]; + let next_character; for (let character of chapter.characters) { for (let action of character.actions[chapter.title]) { - if (action == next_action) { - let next_character = character; + if (action.description == next_action.description) { + next_character = character; } } } @@ -921,7 +922,7 @@ function convertChapterToExperience(chapter) { needName: needName, situation: { detector: affordance, number: "1" }, toPass: { - characterName: next_character; + characterName: next_character.name, instruction: sub.needName, dropdownChoices: { name: "affordance", options: options } }, @@ -1005,18 +1006,27 @@ function convertChapterToExperience(chapter) { } }); + let first_character; + for (let character of chapter.characters) { + for (let action of character.actions[chapter.title]) { + if (action.description == first_action.description) { + first_character = character; + } + } + } + if (i == 0) { // insert first need let need = { needName: first_action.description, //should be the title of the action situation: {detector: DETECTORS[character_context[0]]._id, number: "1"}, toPass: { - characterName: character_context[1], + characterName: first_character.name, instruction: "Please choose from the following list of actions", firstSentence: chapter.title, dropdownChoices: { name: "affordance", - options: chapterActions + options: [["a"], ["b"], ["c"]] } }, numberNeeded: 1 diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index 597a83f1..1ca49a3e 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -252,13 +252,16 @@

Let's celebrate Thanksgiving break

padding-top: 4%; padding-bottom: 4%; } - .camera-upload { padding-bottom: 1%; }
- {{this.toPass.instruction}} +

Welcome to Harry Potter story. You are {{this.toPass.characterName}}!

+

Your current action -

+

{{this.toPass.instruction}}

+

{{> Template.dynamic template="dropdown" data=(dropdownData)}}

+
diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index 4c994621..76826315 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -41,6 +41,7 @@ Template.api_custom.helpers({ } }, }); + Template.storyPage.helpers({ dropdownData() { From 3b71952d13b2a430d73319a015114361639c4109 Mon Sep 17 00:00:00 2001 From: Gino Wang Date: Wed, 6 Jun 2018 21:48:07 -0500 Subject: [PATCH 011/132] Working on submission --- imports/api/detectors/methods.js | 4 +-- imports/api/testing/testingconstants.js | 23 ++++++++----- imports/ui/pages/api_custom.html | 46 ++++++++++++++----------- imports/ui/pages/api_custom.js | 7 +++- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/imports/api/detectors/methods.js b/imports/api/detectors/methods.js index 3cbd01f8..2140894f 100644 --- a/imports/api/detectors/methods.js +++ b/imports/api/detectors/methods.js @@ -29,8 +29,8 @@ export const getAffordancesFromLocation = function (lat, lng, callback) { export const matchAffordancesWithDetector = function (affordances, detectorId) { const detector = Detectors.findOne({ _id: detectorId }); - console.log("dectectorId = " + detectorId); - console.log("dectector = " + detector); + // console.log("dectectorId = " + detectorId); + // console.log("dectector = " + detector); let doesUserMatchSituation = applyDetector(affordances, detector.variables, detector.rules); diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index 7e1a7125..7ed98d3d 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -828,7 +828,7 @@ function writeNarrative() { add_action_to_character(ron, chapter_twoB, new Action("Drinks out of potion bottle", a2B_drink_potion_bottle, 0)); - add_action_to_character(hermione, chapter_one, new Action("Give Harry a portion bottle", a1_give_harry_bottle, 0)); + add_action_to_character(hermione, chapter_one, new Action("Give Harry a potion bottle", a1_give_harry_bottle, 0)); add_action_to_character(hermione, chapter_twoB, new Action("Rush Ron outside", a2B_rush_ron_outside, 1)); return chapter_one; @@ -888,6 +888,7 @@ function convertChapterToExperience(chapter) { console.log("DEBUG [creating callback]"); let hpStoryCallback = function(sub) { + console.log("DEBUG in callback"); var newSet = "profile.staticAffordances.participatedInPotterNarrative" + chapter.title; Meteor.users.update( {_id: sub.uid}, @@ -924,7 +925,10 @@ function convertChapterToExperience(chapter) { toPass: { characterName: next_character.name, instruction: sub.needName, - dropdownChoices: { name: "affordance", options: options } + dropdownChoices: { + name: "affordance", + options: [chapterActions[0], DETECTORS.restaurant._id] + } }, numberNeeded: 1 }; @@ -948,11 +952,12 @@ function convertChapterToExperience(chapter) { participateTemplate: "Harry_Potter_Story", resultsTemplate: "Harry_Potter_Story_Result", contributionTypes: [], - description: chapter.title, - notificationText: "A new chapter [" + chapter.title + "] has begun!", + description: "You are invited to participate in Harry Potter story", + notificationText: "You are invited to participate in Harry Potter story", callbacks: [ { - trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= " + max_priority_allowed + ")", + //trigger: "cb.newSubmission() && (cb.numberOfSubmissions() <= " + max_priority_allowed + ")", + trigger: "cb.newSubmission()", function: hpStoryCallback.toString() }, { @@ -1001,7 +1006,7 @@ function convertChapterToExperience(chapter) { console.log("ERROR"); console.log("error = " + err); } else { - console.log("Detector addded -"); + console.log("Detector added -"); console.log("docs = " + docs); } }); @@ -1017,7 +1022,7 @@ function convertChapterToExperience(chapter) { if (i == 0) { // insert first need - let need = { + let need = { needName: first_action.description, //should be the title of the action situation: {detector: DETECTORS[character_context[0]]._id, number: "1"}, toPass: { @@ -1026,7 +1031,7 @@ function convertChapterToExperience(chapter) { firstSentence: chapter.title, dropdownChoices: { name: "affordance", - options: [["a"], ["b"], ["c"]] + options: [[first_action.description, DETECTORS.restaurant._id]] } }, numberNeeded: 1 @@ -1148,7 +1153,7 @@ let EXPERIENCES = { // function: sendNotificationScavenger.toString() // }] // }, - // 'storyTime': createStorytime(), + 'storyTime': createStorytime(), }; diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index 1ca49a3e..83e0dd94 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -145,7 +145,7 @@ {{this.toPass.instruction}}
- {{> Template.dynamic template="text" data="food"}} + {{> Template.dynamic template="text" data="food"}}
+ + From 20adb4aa1fab2d8613d5910816b5c40fafcc157a Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Thu, 6 Dec 2018 15:12:07 -0600 Subject: [PATCH 027/132] creates messages collection --- imports/api/messages/messages.js | 3 ++ imports/api/messages/server/methods.js | 45 +++++++++++++++++++++ imports/api/messages/server/publications.js | 6 +++ imports/ui/components/contributions.js | 2 +- imports/ui/pages/api_custom.js | 1 + server/main.js | 1 + 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 imports/api/messages/messages.js create mode 100644 imports/api/messages/server/methods.js create mode 100644 imports/api/messages/server/publications.js diff --git a/imports/api/messages/messages.js b/imports/api/messages/messages.js new file mode 100644 index 00000000..4f37b6c6 --- /dev/null +++ b/imports/api/messages/messages.js @@ -0,0 +1,3 @@ +import { Mongo } from 'meteor/mongo'; + +export const Messages = new Mongo.Collection('messages'); \ No newline at end of file diff --git a/imports/api/messages/server/methods.js b/imports/api/messages/server/methods.js new file mode 100644 index 00000000..10bf57d9 --- /dev/null +++ b/imports/api/messages/server/methods.js @@ -0,0 +1,45 @@ +import { Meteor } from 'meteor/meteor'; +import { check, Match } from 'meteor/check'; + +import { Messages } from '../messages.js'; + +Meteor.methods({ + + 'sendMessage'(data) { + + check(data, { + message: String, //the message to send + name: Match.Optional(String) //if the user already has a name + }); + + if (data.message=="") { + throw new Meteor.Error("message-empty", "Your message is empty"); + } + + let userName = (data.name && data.name!="") ? data.name : "Anonymous"; + + const matchName = data.message.match(/^My name is (.*)/i); + + if (matchName && matchName[1]!="") { + userName = matchName[1]; + Messages.insert({ + name: "Chat Bot", + message: "Hey everyone, " + userName + " is here!", + createdAt: new Date(), + announcement: true + }); + } else { + Messages.insert({ + name: userName, + message: data.message, + createdAt: new Date() + }); + } + + return { + name: userName + }; + + } + +}); \ No newline at end of file diff --git a/imports/api/messages/server/publications.js b/imports/api/messages/server/publications.js new file mode 100644 index 00000000..09a8c992 --- /dev/null +++ b/imports/api/messages/server/publications.js @@ -0,0 +1,6 @@ +import { Meteor } from 'meteor/meteor'; +import { Messages } from '../messages.js'; + +Meteor.publish("messages", function() { + return Messages.find({}, { fields: { name: 1, message: 1, createdAt: 1, announcement: 1 }, limit: 100, sort: { createdAt: -1 } }); //we want the 100 most recent messages +}); \ No newline at end of file diff --git a/imports/ui/components/contributions.js b/imports/ui/components/contributions.js index 8552fda0..ac9b20fd 100644 --- a/imports/ui/components/contributions.js +++ b/imports/ui/components/contributions.js @@ -1,6 +1,6 @@ import { Template } from 'meteor/templating'; import { Cookies } from 'meteor/mrt:cookies'; -import { Messages } from '../api/collections.js'; +import { Messages } from '../api/messages/messages.js'; import moment from 'moment'; import './contributions.html'; import './loader.html'; diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index ffe56a6e..181fdd0a 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -15,6 +15,7 @@ import { Users } from '../../api/UserMonitor/users/users.js'; import { Locations } from '../../api/UserMonitor/locations/locations.js'; import { Submissions } from '../../api/OCEManager/currentNeeds.js'; import { Images } from '../../api/ImageUpload/images.js'; +import { Messages } from '../../api/messages/messages.js'; import { photoInput } from './photoUploadHelpers.js' import { photoUpload } from './photoUploadHelpers.js' diff --git a/server/main.js b/server/main.js index 497d20e5..fa428bf6 100644 --- a/server/main.js +++ b/server/main.js @@ -1,6 +1,7 @@ import '/imports/startup/server'; Schema = require('../imports/api/schema.js').Schema; +Messages = require('../imports/api/messages/messages.js').Messages; Experiences = require('../imports/api/OCEManager/OCEs/experiences.js').Experiences; Images = require('../imports/api/ImageUpload/images.js').Images; Avatars = require('../imports/api/ImageUpload/images.js').Avatars; From 620b38aa72ac76b857f63d66ad4ab15f82d29bab Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Thu, 6 Dec 2018 20:26:29 -0600 Subject: [PATCH 028/132] displaying messages + setup messages collection --- .meteor/packages | 1 + .meteor/versions | 1 + client/main.js | 1 + imports/api/messages/server/methods.js | 3 +- imports/startup/client/router.js | 1 + imports/startup/server/fixtures.js | 1 + imports/startup/server/register-api.js | 2 + imports/ui/components/contributions.html | 16 +- imports/ui/components/contributions.js | 17 +- imports/ui/pages/api_custom.html | 2 +- imports/ui/pages/api_custom.js | 6 +- package-lock.json | 494 ++++++++++++----------- package.json | 1 + 13 files changed, 279 insertions(+), 267 deletions(-) diff --git a/.meteor/packages b/.meteor/packages index 07ba38b6..e1a8e107 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -56,3 +56,4 @@ useraccounts:iron-routing aldeed:template-extension useraccounts:bootstrap underscore@1.0.10 +mrt:cookies@=0.3.0 diff --git a/.meteor/versions b/.meteor/versions index 1881b86b..dd087ada 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -100,6 +100,7 @@ mongo@1.4.7 mongo-dev-server@1.1.0 mongo-id@1.0.7 mongo-livedata@1.0.12 +mrt:cookies@0.3.0 mrt:moment@2.8.1 mrt:moment-timezone@0.2.1 msavin:mongol@2.0.1 diff --git a/client/main.js b/client/main.js index 674910a0..4e0251da 100644 --- a/client/main.js +++ b/client/main.js @@ -3,6 +3,7 @@ import '/imports/startup/client'; //TODO: what's all this? Do we need to add availabliity? if (Meteor.isDevelopment) { Schema = require('../imports/api/schema.js').Schema; + Messages = require('../imports/api/messages/messages.js').Messages; Experiences = require('../imports/api/OCEManager/OCEs/experiences.js').Experiences; Images = require('../imports/api/ImageUpload/images.js').Images; Incidents = require('../imports/api/OCEManager/OCEs/experiences.js').Incidents; diff --git a/imports/api/messages/server/methods.js b/imports/api/messages/server/methods.js index 10bf57d9..d8e10360 100644 --- a/imports/api/messages/server/methods.js +++ b/imports/api/messages/server/methods.js @@ -5,7 +5,8 @@ import { Messages } from '../messages.js'; Meteor.methods({ - 'sendMessage'(data) { + sendMessage: function (data) { + console.log("sending Message") check(data, { message: String, //the message to send diff --git a/imports/startup/client/router.js b/imports/startup/client/router.js index 1642998f..a2cf5f63 100644 --- a/imports/startup/client/router.js +++ b/imports/startup/client/router.js @@ -27,6 +27,7 @@ import '../../ui/pages/participate_backdoor.js'; import { Experiences, Incidents } from "../../api/OCEManager/OCEs/experiences"; import { Locations } from "../../api/UserMonitor/locations/locations"; import {Avatars, Images} from "../../api/ImageUpload/images"; +import { Messages } from "../../api/messages/messages.js"; import { Submissions } from "../../api/OCEManager/currentNeeds"; import { Meteor } from "meteor/meteor"; import {Assignments, Availability} from "../../api/OpportunisticCoordinator/databaseHelpers"; diff --git a/imports/startup/server/fixtures.js b/imports/startup/server/fixtures.js index fa354d99..2efc2198 100644 --- a/imports/startup/server/fixtures.js +++ b/imports/startup/server/fixtures.js @@ -6,6 +6,7 @@ import { CONFIG } from '../../api/config.js'; import { Experiences, Incidents } from '../../api/OCEManager/OCEs/experiences.js'; import { Locations } from '../../api/UserMonitor/locations/locations.js'; import { Submissions } from "../../api/OCEManager/currentNeeds"; +import { Messages } from "../../api/messages/messages.js"; import { Assignments, Availability } from "../../api/OpportunisticCoordinator/databaseHelpers"; import { Images, Avatars } from '../../api/ImageUpload/images.js'; import { log } from '../../api/logs.js'; diff --git a/imports/startup/server/register-api.js b/imports/startup/server/register-api.js index fd9dd3ee..d4f0541d 100644 --- a/imports/startup/server/register-api.js +++ b/imports/startup/server/register-api.js @@ -2,6 +2,8 @@ import '../../api/OCEManager/OCEs/server/publications.js'; import '../../api/OCEManager/OCEs/methods.js'; import '../../api/ImageUpload/server/publications.js'; import '../../api/ImageUpload/methods.js'; +import '../../api/messages/server/methods.js'; +import '../../api/messages/server/publications.js'; import '../../api/UserMonitor/users/methods.js'; import '../../api/UserMonitor/users/server/publications.js'; import '../../api/UserMonitor/locations/server/publications.js'; diff --git a/imports/ui/components/contributions.html b/imports/ui/components/contributions.html index c959512e..7027439e 100644 --- a/imports/ui/components/contributions.html +++ b/imports/ui/components/contributions.html @@ -49,27 +49,19 @@
{{name}} {{timestamp}}
-
{{message}}{{#if announcement}} tada{{/if}}
+
{{message}}
@@ -62,7 +59,7 @@
-
+
{{#if Template.subscriptionsReady}} {{#each messages}} {{> message}} diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index c85fd939..a3dcf3ab 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -442,7 +442,6 @@ Template.api_custom.onCreated(() => { Template.api_custom.events({ 'submit #participate'(event, instance) { - debugger; event.preventDefault(); console.log("form was submitted"); @@ -456,17 +455,31 @@ Template.api_custom.events({ const iid = Router.current().params.iid; const needName = Router.current().params.needName; const uid = Meteor.userId(); + const user = Meteor.username(); const timestamp = Date.now() const submissions = {}; const resultsUrl = '/apicustomresults/' + iid + '/' + experience._id; + var dialogue = []; + var action = ""; + var chapterID = 0; const dropDowns = event.target.getElementsByClassName('dropdown'); _.forEach(dropDowns, (dropDown) => { const index = dropDown.selectedIndex; + action = dropDown[index].text; + console.log("dropDown value" + dropDown[index].text) + chapterID = dropDown[index].value; + console.log("chapter name" + dropDown[index].value) submissions[dropDown.id] = dropDown[index].value }); + const messages = event.target.getElementsByClassName('messages'); + _.forEach(messages, (message) => { + console.log("message value" + message) + dialogue.push(message.textContent); + }); + const textBoxes = event.target.getElementsByClassName('textinput'); _.forEach(textBoxes, (textBox) => { submissions[textBox.id] = textBox.value; @@ -478,6 +491,10 @@ Template.api_custom.events({ Router.go(resultsUrl); } + dialogue.push(user + " commited this action: " + action) + submissions[chapterID] = dialogue; + console.log("finished making dialogue" + dialogue[0]); + //otherwise, we do have ImageUpload to upload so need to hang around for that _.forEach(images, (image, index) => { let picture; @@ -548,6 +565,8 @@ Template.api_custom.events({ lng: location.lng }; + console.log("submitted submission"); + Meteor.call('updateSubmission', submissionObject); }, From 9c9636dffb756b6660bef3d404a3158a284f84ee Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Fri, 7 Dec 2018 14:17:44 -0600 Subject: [PATCH 030/132] results now display what happened last chapter; messages collection keeps track of what chapter the line was said --- imports/api/Testing/testingconstants.js | 24 +++++++++++++++--------- imports/api/messages/server/methods.js | 16 +++++++++++++--- imports/api/testing/testingconstants.js | 24 +++++++++++++++--------- imports/ui/components/contributions.html | 8 +++++--- imports/ui/components/contributions.js | 14 +++++++++----- imports/ui/pages/api_custom.html | 2 +- imports/ui/pages/api_custom.js | 2 +- imports/ui/pages/api_custom_results.html | 5 +++++ imports/ui/pages/api_custom_results.js | 17 +++++++++++++++++ 9 files changed, 81 insertions(+), 31 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index e40dedf8..7161d1e8 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1793,8 +1793,8 @@ let update_character_context = function (character, chapter, contexts) { character.contexts[chapter_title] += " && " + contexts; }; -let add_action_to_character = function (character, chapter, action) { - character.actions[chapter.title].push(action); +let add_action_to_character = function (character, chapterID, action) { + character.actions[chapterID].push(action); }; function writeNarrative() { @@ -1863,8 +1863,8 @@ function writeNarrative() { harry.owned_objects.push(bottle); }; - add_action_to_character(harry, chapter_one, new Action("Take potion bottle with him to bed", a1_take_potion_to_bed, 1)); - add_action_to_character(harry, chapter_one, new Action("Leave potion bottle on table", a1_leave_potion_on_table, 1)); + add_action_to_character(harry, chapter_one, new Action("Take potion bottle with him to bed", '1', 1)); + add_action_to_character(harry, chapter_one, new Action("Leave potion bottle on table", '1', 1)); //add_action_to_character(harry, chapter_twoA, new Action("Leave the bedroom", a2_leave_bedroom, 0)); //add_action_to_character(harry, chapter_twoB, new Action("Takes potion bottle with him to class", a2B_take_potion_to_class, 2)); @@ -2142,20 +2142,26 @@ function convertChapterToExperience(chapter) { } } - if (i == 0) { + for (let i = 0; i < chapter.characters.length; i++) { // insert first need + let actions = []; + for (let action in chapter.characters[i].actions[i]) { + actions.push([action.description, DETECTORS.grocery._id]); + console.log("Action is " + action.description); + } + let need = { chapterName: chapter.title, - needName: first_action.description, //should be the title of the action - situation: {detector: DETECTORS[character_context]._id, number: "2"}, + needName: first_action.description + i, //should be the title of the action + situation: {detector: DETECTORS[character_context]._id, number: "1"}, toPass: { chapterName: chapter.title, - characterName: first_character.name, + characterName: chapter.characters[i].name, instruction: "Please choose from the following list of actions", firstSentence: chapter.title, dropdownChoices: { name: chapter.title, - options: [[first_action.description, DETECTORS.grocery._id]] + options: actions } }, numberNeeded: 1 diff --git a/imports/api/messages/server/methods.js b/imports/api/messages/server/methods.js index d8e10360..14bce345 100644 --- a/imports/api/messages/server/methods.js +++ b/imports/api/messages/server/methods.js @@ -10,14 +10,20 @@ Meteor.methods({ check(data, { message: String, //the message to send - name: Match.Optional(String) //if the user already has a name + name: Match.Optional(String), //if the user already has a name + chapterID: String }); if (data.message=="") { throw new Meteor.Error("message-empty", "Your message is empty"); } + + // if (data.name=="") { + // throw new Meteor.Error("no name"); + // } let userName = (data.name && data.name!="") ? data.name : "Anonymous"; + let chapterID = data.chapterID; const matchName = data.message.match(/^My name is (.*)/i); @@ -27,13 +33,17 @@ Meteor.methods({ name: "Chat Bot", message: "Hey everyone, " + userName + " is here!", createdAt: new Date(), - announcement: true + announcement: true, + chapterID: chapterID }); } else { Messages.insert({ name: userName, message: data.message, - createdAt: new Date() + createdAt: new Date(), + announcement: false, + chapterID: chapterID + //chapter: "1" }); } diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index e40dedf8..7161d1e8 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -1793,8 +1793,8 @@ let update_character_context = function (character, chapter, contexts) { character.contexts[chapter_title] += " && " + contexts; }; -let add_action_to_character = function (character, chapter, action) { - character.actions[chapter.title].push(action); +let add_action_to_character = function (character, chapterID, action) { + character.actions[chapterID].push(action); }; function writeNarrative() { @@ -1863,8 +1863,8 @@ function writeNarrative() { harry.owned_objects.push(bottle); }; - add_action_to_character(harry, chapter_one, new Action("Take potion bottle with him to bed", a1_take_potion_to_bed, 1)); - add_action_to_character(harry, chapter_one, new Action("Leave potion bottle on table", a1_leave_potion_on_table, 1)); + add_action_to_character(harry, chapter_one, new Action("Take potion bottle with him to bed", '1', 1)); + add_action_to_character(harry, chapter_one, new Action("Leave potion bottle on table", '1', 1)); //add_action_to_character(harry, chapter_twoA, new Action("Leave the bedroom", a2_leave_bedroom, 0)); //add_action_to_character(harry, chapter_twoB, new Action("Takes potion bottle with him to class", a2B_take_potion_to_class, 2)); @@ -2142,20 +2142,26 @@ function convertChapterToExperience(chapter) { } } - if (i == 0) { + for (let i = 0; i < chapter.characters.length; i++) { // insert first need + let actions = []; + for (let action in chapter.characters[i].actions[i]) { + actions.push([action.description, DETECTORS.grocery._id]); + console.log("Action is " + action.description); + } + let need = { chapterName: chapter.title, - needName: first_action.description, //should be the title of the action - situation: {detector: DETECTORS[character_context]._id, number: "2"}, + needName: first_action.description + i, //should be the title of the action + situation: {detector: DETECTORS[character_context]._id, number: "1"}, toPass: { chapterName: chapter.title, - characterName: first_character.name, + characterName: chapter.characters[i].name, instruction: "Please choose from the following list of actions", firstSentence: chapter.title, dropdownChoices: { name: chapter.title, - options: [[first_action.description, DETECTORS.grocery._id]] + options: actions } }, numberNeeded: 1 diff --git a/imports/ui/components/contributions.html b/imports/ui/components/contributions.html index 6622fd64..9cb08ede 100644 --- a/imports/ui/components/contributions.html +++ b/imports/ui/components/contributions.html @@ -40,7 +40,7 @@ @@ -59,7 +59,7 @@
-
+
{{#if Template.subscriptionsReady}} {{#each messages}} {{> message}} @@ -75,6 +75,8 @@
+

{{this.toPass.characterName}}

+

{{this.toPass.chapterName}}

@@ -83,7 +85,7 @@
-

{{> Template.dynamic template="dropdown" data=this}}

+

{{> Template.dynamic template="dropdown" data=this.toPass.dropdownChoices}}

diff --git a/imports/ui/pages/home.js b/imports/ui/pages/home.js index c6680bac..960295c1 100644 --- a/imports/ui/pages/home.js +++ b/imports/ui/pages/home.js @@ -9,6 +9,8 @@ import { Experiences, Incidents } from '../../api/OCEManager/OCEs/experiences'; import { Assignments } from '../../api/OpportunisticCoordinator/databaseHelpers'; import '../components/active_experience.js'; +import {needAggregator} from "../../api/OpportunisticCoordinator/strategizer"; +import {setIntersection} from "../../api/custom/arrayHelpers"; Template.home.onCreated(function () { this.state = new ReactiveDict(); @@ -32,28 +34,47 @@ Template.home.events({ Template.home.helpers({ activeUserAssigment() { - // create [{iid: incident_id, eid: experience_id, needName: assigned_need_name}] - let activeAssignments = Assignments.find().fetch(); - let output = []; + if (Template.instance().subscriptionsReady()) { + // create [{iid: incident_id, experience: experience, detectorId: detector_id}] + let activeAssignments = Assignments.find().fetch(); + let output = []; - _.forEach(activeAssignments, (assignment) => { - _.forEach(assignment.needUserMaps, (currNeedUserMap) => { - if (currNeedUserMap.users.find(user => user.uid === Meteor.userId())) { + _.forEach(activeAssignments, (assignment) => { + + let iid = assignment._id; + let incident = Incidents.findOne(iid); + let needNamesBinnedByDetector = needAggregator(incident); + + // gah I wish the assignments had its own interface + let assignedNeedNames = assignment.needUserMaps.map(currNeedUserMap => { + if (currNeedUserMap.users.find(user => user.uid === Meteor.userId())) { + return currNeedUserMap.needName; + } + }); + + _.forEach(needNamesBinnedByDetector, (needNamesForDetector, detectorId) => { + let assignedNeedNamesForDetector = setIntersection(assignedNeedNames, needNamesForDetector); + if (assignedNeedNamesForDetector.length === 0) { + // user not assigned to any needs for this detector + return; + } // get experience - let experience = Experiences.findOne(Incidents.findOne(assignment._id).eid); + let experience = Experiences.findOne(Incidents.findOne(iid).eid); + + // instead of knowing the specific need at the home screen, + // we only know the associated iid/detector; dynamic routing to the need will happen output.push({ 'iid': assignment._id, 'experience': experience, - 'needName': currNeedUserMap.needName + 'detectorId': detectorId }); - // user can only be assigned to one need in each assignment object - return false; - } + }); + }); - }); - return output; + return output; + } }, noActiveIncidents() { let currActiveIncidents = Meteor.users.findOne(Meteor.userId()).profile.activeIncidents; From ba5514ad8c22598a88d3ec7ee3cc8c9869d42adb Mon Sep 17 00:00:00 2001 From: youralien Date: Tue, 7 May 2019 19:36:32 -0500 Subject: [PATCH 052/132] Attempt at fixing notifications... Summary: - Issue: When checking for a needObject, we were checking in the experience, rather than the incident; this is problematic because the incident is what stores the updating contributionTypes. - Fix: Point to the incident, and for some reason if it can't find the newest needObject, still try to send experience level notifications. --- .../server/executor.js | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/server/executor.js b/imports/api/OpportunisticCoordinator/server/executor.js index b40c6382..2f6eb8a3 100644 --- a/imports/api/OpportunisticCoordinator/server/executor.js +++ b/imports/api/OpportunisticCoordinator/server/executor.js @@ -79,31 +79,31 @@ export const runNeedsWithThresholdMet = (incidentsWithUsersToRun) => { let uidsNotNotifiedRecently = newUsersMeta.map(usermeta => usermeta.uid); let route = "/"; - let needObject = experience.contributionTypes.find((need) => need.needName === needName); - - if (needObject) { - log.cerebro(JSON.stringify(needObject)); - if (needObject.notificationSubject && needObject.notificationText) { - notifyForParticipating(uidsNotNotifiedRecently, iid, needObject.notificationSubject, - needObject.notificationText, route); - } - else if (experience.name && experience.notificationText) { - notifyForParticipating(uidsNotNotifiedRecently, iid, `Participate in "${experience.name}"!`, - experience.notificationText, route); - } else { - log.error('notification information cannot be found in the need or experience level'); - return; - } - - _.forEach(newUsersMeta, usermeta => { - Notification_log.insert({ - uid: usermeta.uid, - iid: iid, - needName: needName, - timestamp: Date.now() - }); - }); + // Try to notify, based on if the current need has need-specific notification info + let needObject = incident.contributionTypes.find((need) => need.needName === needName); + if (needObject && needObject.notificationSubject && needObject.notificationText) { + notifyForParticipating(uidsNotNotifiedRecently, iid, needObject.notificationSubject, + needObject.notificationText, route); + } + // Try to notify, based on experience-level notification info + else if (experience.name && experience.notificationText) { + notifyForParticipating(uidsNotNotifiedRecently, iid, `Participate in "${experience.name}"!`, + experience.notificationText, route); + } + // Fail to notify, because these parameters are not defined + else { + log.error('notification information cannot be found in the need or experience level'); + return; } + + _.forEach(newUsersMeta, usermeta => { + Notification_log.insert({ + uid: usermeta.uid, + iid: iid, + needName: needName, + timestamp: Date.now() + }); + }); }); // FIXME(rlouie): old, used to go through every need From f942aebcfecb731f4b25bd6ee7082cfcbf6a5ca9 Mon Sep 17 00:00:00 2001 From: youralien Date: Thu, 9 May 2019 20:45:56 -0500 Subject: [PATCH 053/132] WIP: Dynamic Routing Algorithm based on participateSempahore --- imports/api/OCEManager/OCEs/experiences.js | 8 ++ imports/api/OCEManager/OCEs/methods.js | 26 +++- .../databaseHelpers.js | 34 ++++- .../server/executor.js | 6 +- .../server/publications.js | 6 +- .../server/strategizer.js | 8 +- .../OpportunisticCoordinator/strategizer.js | 119 +++++++++++++++++- imports/api/Testing/testingconstants.js | 27 ++-- imports/startup/client/router.js | 26 ++-- imports/ui/pages/api_custom.js | 60 +++------ imports/ui/pages/dynamic_participate.js | 51 +++----- imports/ui/pages/home.js | 1 + 12 files changed, 257 insertions(+), 115 deletions(-) diff --git a/imports/api/OCEManager/OCEs/experiences.js b/imports/api/OCEManager/OCEs/experiences.js index 2fd81891..652e25d2 100644 --- a/imports/api/OCEManager/OCEs/experiences.js +++ b/imports/api/OCEManager/OCEs/experiences.js @@ -26,6 +26,7 @@ Schema.SituationDescription = new SimpleSchema({ detector: { type: String }, + // sameTimeNumber (2 would mean that the situation requires 2 people to be in the situation at the same time) number: { type: Number, } @@ -57,6 +58,13 @@ Schema.NeedType = new SimpleSchema({ numberNeeded: { type: Number, }, + // this is like a semaphore/mutex mechanism for participate routes as a resource a participant can latch onto + numberAllowedToParticipateAtSameTime: { + // if = 1, then this operates like a mutex + type: Number, + optional: true + // defaults to numberNeeded, like a semaphore. + }, notificationDelay: { type: Number, optional: true, diff --git a/imports/api/OCEManager/OCEs/methods.js b/imports/api/OCEManager/OCEs/methods.js index 503395ca..d8b95b43 100644 --- a/imports/api/OCEManager/OCEs/methods.js +++ b/imports/api/OCEManager/OCEs/methods.js @@ -13,7 +13,7 @@ import { } from "../../UserMonitor/detectors/methods"; import {Incidents} from './experiences'; -import {Assignments, Availability} from '../../OpportunisticCoordinator/databaseHelpers'; +import {Assignments, Availability, ParticipatingNow} from '../../OpportunisticCoordinator/databaseHelpers'; import {Submissions} from '../../OCEManager/currentNeeds'; import {serverLog} from "../../logs"; import {setIntersection} from "../../custom/arrayHelpers"; @@ -284,6 +284,12 @@ export const addContribution = (iid, contribution) =>{ },{ $push: {needUserMaps: {needName: contribution.needName, users: []}} }); + + ParticipatingNow.update({ + _id: iid + },{ + $push: {needUserMaps: {needName: contribution.needName, users: []}} + }); }; export const addEmptySubmissionsForNeed = (iid, eid, need) => { @@ -346,6 +352,11 @@ export const startRunningIncident = (incident) => { _id: incident._id, needUserMaps: needUserMaps }); + + ParticipatingNow.insert({ + _id: incident._id, + needUserMaps: needUserMaps + }); }; /** @@ -388,7 +399,7 @@ export const updateRunningIncident = (incident) => { needUserMaps: needUserMaps } } - ) + ); Assignments.update( { @@ -398,8 +409,17 @@ export const updateRunningIncident = (incident) => { needUserMaps: needUserMaps } } - ) + ); + ParticipatingNow.update( + { + _id: incident._id, + }, { + $set: { + needUserMaps: needUserMaps + } + } + ) }; diff --git a/imports/api/OpportunisticCoordinator/databaseHelpers.js b/imports/api/OpportunisticCoordinator/databaseHelpers.js index 57613a90..d27c8a41 100644 --- a/imports/api/OpportunisticCoordinator/databaseHelpers.js +++ b/imports/api/OpportunisticCoordinator/databaseHelpers.js @@ -1,6 +1,7 @@ import { Mongo } from "meteor/mongo"; import { SimpleSchema } from 'meteor/aldeed:simple-schema'; import { Schema } from '../schema.js'; +import {Location_log} from "../Logging/location_log"; Schema.Assignment = new SimpleSchema({ _id: { @@ -23,7 +24,9 @@ Schema.UserNeedMapping = new SimpleSchema({ type: String }, users: { - type: [Object], // {uid: String, place: String, distance: Number} + // For Availability and Assignments, this object can look like {"uid": uid, "place": place, "distance": distance} + // For ParticipatingNow, this object looks like {"uid": uid} + type: [Object], defaultValue: [], blackbox: true }, @@ -47,3 +50,32 @@ Schema.Availability = new SimpleSchema({ export const Availability = new Mongo.Collection('availability'); Availability.attachSchema(Schema.Availability); + +Schema.ParticipatingNow = new SimpleSchema({ + _id: { + type: String, + optional: true, + regEx: SimpleSchema.RegEx.Id, + }, + needUserMaps: { + type: [Schema.UserNeedMapping], + blackbox: true, + //TODO: this shouldn't be blackbox true, figure out why it's not doing its thang + }, + +}); + +export const ParticipatingNow = new Mongo.Collection('participating_now'); +ParticipatingNow.attachSchema(Schema.ParticipatingNow); + +ParticipatingNow.allow({ + insert: function () { + return true; + }, + update: function () { + return true; + }, + remove: function () { + return true; + } +}); \ No newline at end of file diff --git a/imports/api/OpportunisticCoordinator/server/executor.js b/imports/api/OpportunisticCoordinator/server/executor.js index 2f6eb8a3..bc43d82d 100644 --- a/imports/api/OpportunisticCoordinator/server/executor.js +++ b/imports/api/OpportunisticCoordinator/server/executor.js @@ -38,7 +38,7 @@ export const runNeedsWithThresholdMet = (incidentsWithUsersToRun) => { let incident = Incidents.findOne(iid); let experience = Experiences.findOne(incident.eid); - // { [detectorId]: [...needs], ...} + // { [detectorId]: [need1, ...], ...} let needNamesBinnedByDetector = needAggregator(incident); let assignedNeedNames = Object.keys(needUserMapping); @@ -105,10 +105,6 @@ export const runNeedsWithThresholdMet = (incidentsWithUsersToRun) => { }); }); }); - - // FIXME(rlouie): old, used to go through every need - // _.forEach(needUserMapping, (usersMeta, needName) => { - // }); }); }; diff --git a/imports/api/OpportunisticCoordinator/server/publications.js b/imports/api/OpportunisticCoordinator/server/publications.js index 85c6761b..80285289 100644 --- a/imports/api/OpportunisticCoordinator/server/publications.js +++ b/imports/api/OpportunisticCoordinator/server/publications.js @@ -1,5 +1,5 @@ import { Meteor } from 'meteor/meteor'; -import { Assignments } from "../databaseHelpers"; +import {Assignments, ParticipatingNow} from "../databaseHelpers"; import {Availability} from "../databaseHelpers"; import {Notification_log} from "../../Logging/notification_log"; @@ -35,3 +35,7 @@ Meteor.publish('assignments.activeUser', function () { Meteor.publish('notification_log.activeIncident', function (iid) { return Notification_log.find({iid: iid}); }); + +Meteor.publish('participating.now.activeIncident', function (iid) { + return ParticipatingNow.find({_id: iid}); +}); \ No newline at end of file diff --git a/imports/api/OpportunisticCoordinator/server/strategizer.js b/imports/api/OpportunisticCoordinator/server/strategizer.js index 5f452113..258d37a9 100644 --- a/imports/api/OpportunisticCoordinator/server/strategizer.js +++ b/imports/api/OpportunisticCoordinator/server/strategizer.js @@ -8,7 +8,7 @@ import {Experiences} from "../../OCEManager/OCEs/experiences"; import {createIncidentFromExperience, startRunningIncident} from "../../OCEManager/OCEs/methods"; import {CONSTANTS} from "../../Testing/testingconstants"; import {Meteor} from "meteor/meteor"; -import {usersAlreadyAssignedToNeed, usersAlreadySubmittedToNeed} from "../strategizer"; +import {numberSubmissionsRemaining, usersAlreadyAssignedToNeed, usersAlreadySubmittedToNeed} from "../strategizer"; const util = require('util'); @@ -100,11 +100,7 @@ export const checkIfThreshold = updatedIncidentsAndNeeds => { /** my mutex, but not dynamic on page load, but does it during the first assignment (for notification) **/ const chooseUsers = (availableUserMetas, iid, needUserMap) => { - let numberPeopleNeeded = Submissions.find({ - iid: iid, - needName: needUserMap.needName, - uid: null - }).count(); + let numberPeopleNeeded = numberSubmissionsRemaining(iid, needUserMap.needName); let usersWeAlreadyHave = needUserMap.users; diff --git a/imports/api/OpportunisticCoordinator/strategizer.js b/imports/api/OpportunisticCoordinator/strategizer.js index c315a700..f8636348 100644 --- a/imports/api/OpportunisticCoordinator/strategizer.js +++ b/imports/api/OpportunisticCoordinator/strategizer.js @@ -1,7 +1,7 @@ /** * strategizer -- server+client side */ -import {Assignments} from "./databaseHelpers"; +import {Assignments, Availability, ParticipatingNow} from "./databaseHelpers"; import {Submissions} from "../OCEManager/currentNeeds"; @@ -23,6 +23,7 @@ export const usersAlreadyAssignedToNeed = (iid, needName) => { /** + * usersAlreadySubmittedToNeed * * @param iid * @param needName @@ -39,6 +40,8 @@ export const usersAlreadySubmittedToNeed = (iid, needName) => { /** + * needAggregator + * * Helper for Dynamic Loading of Exact Participate Need * Looks at the need.situation.detectors of an incident, * returns the needNames should be aggregated @@ -57,4 +60,116 @@ export const needAggregator = (incident) => { } }); return res; -}; \ No newline at end of file +}; + +/** + * needA will be given a higher value in the sorting, if needA has fewer submissions left + * (e.g., a photo that needs 1 more person to complete it will be ranked higher) + * TODO(rlouie): What about the case where some needs have 1 out of 2 more needed, + * while others just need 1 out of 1 submission to complete? + * @param needA + * @param needB + * @return {number} + */ +export const prioritizeHalfCompletedNeeds = (needA, needB) => { + return numberSubmissionsRemaining(this.iid, needB) - numberSubmissionsRemaining(this.iid, needA); +}; + +/** + * numberSubmissionsNeeded + * + * @param iid + * @param needName + * @return {any | * | IDBRequest | void} + */ +export const numberSubmissionsRemaining = (iid, needName) => { + let numSubsNeeded = Submissions.find({ + iid: iid, + needName: needName, + uid: null + }).count(); + if (!Number.isInteger(numSubsNeeded)) { + throw `Error in numSubmissionsNeeded: numSubsNeeded is not an Integer`; + } + return numSubsNeeded; +}; + +export const needIsAvailableToParticipateNow = (incident, needName) => { + if (!incident.contributionTypes) { + throw `Error in needAggregator: incident does not have contribution types\n ${JSON.stringify(incident)}`; + } + const needObject = incident.contributionTypes.find(need => need.needName == needName); + const numberNeeded = numberSubmissionsRemaining(incident._id, needName); + const semaphoreMax = needObject.numberAllowedToParticipateAtSameTime ? + needObject.numberAllowedToParticipateAtSameTime : numberNeeded; + const resourcesStillAvailable = numberUsersParticipatingNow(incident._id, needName) < semaphoreMax; + return resourcesStillAvailable; +}; + +/** + * numberUsersParticipatingNow + * + * @param iid + * @param needName + */ +export const numberUsersParticipatingNow = (iid, needName) => { + let participatingNowInIncident = ParticipatingNow.findOne({_id: iid}); + if (!Array.isArray(participatingNowInIncident.needUserMaps)) { + console.error(`Error in numberUsersParticipatingNow: participatingNowInIncident.needUserMaps is not an array`); + return; + } + let needMap = participatingNowInIncident.needUserMaps.find(needUserMap => needUserMap.needName == needName); + if (!Array.isArray(needMap.users)) { + console.error(`Error in numUsersParticipatingNow: needMap.users is not an array`); + return; + } + return needMap.users.length; +}; + +/** + * pushUserIntoParticipatingNowNeedUserMaps + * + * The list of users in each needUserMap is a counter for who has the participate route open + * This function increments this "semaphore" like counter, or adds users + * @param iid + * @param needName + * @param uid + * @param place + * @param distance + */ +export const pushUserIntoParticipatingNowNeedUserMaps = (iid, needName, uid) => { + ParticipatingNow.update( + { + _id: iid + }, + { + $push: { + // note: this object is different than {"uid": uid, "place": place, "distance": distance} + "needUserMaps.$.users": { + "uid": uid + } + } + } + ); +}; + + +/** + * pullUserFromParticipatingNowNeedUserMaps + * + * The list of users in each needUserMap is a counter for who has the participate route open + * This function decrements this "semaphore" like counter, or removes users + * @param iid + * @param needName + * @param uid + */ +export const pullUserFromParticipatingNowNeedUserMaps = (iid, needName, uid) => { + ParticipatingNow.update( + { + _id: iid + }, + { + $pull: { "needUserMaps.$.users": {"uid" : uid } } + } + ); +}; diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 456b1c45..ffe49c6e 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -729,6 +729,7 @@ let DETECTORS = { export const getDetectorId = (detector) => { let db_detector = Detectors.findOne({description: detector.description}); if (db_detector) { + console.log(JSON.stringify(db_detector)); return db_detector._id; } else { return detector._id; @@ -1615,15 +1616,20 @@ const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { */ const addStaticAffordanceToNeeds = function(staticAffordance, contributionTypes) { return _.map(contributionTypes, (need) => { - let detectorKey; - _.forEach(_.keys(DETECTORS), (key) => { - if (DETECTORS[key]._id === need.situation.detector) { - detectorKey = key; - } - }); - // WILL THROW ERROR if we don't find the matching detector id - let newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); - need.situation.detector = getDetectorId(DETECTORS[newDetectorKey]); + const detectorKey = _.keys(DETECTORS).find(key => DETECTORS[key]._id === need.situation.detector); + const oldDetectorId = getDetectorId(DETECTORS[detectorKey]); + if (!detectorKey) { + throw `Exception in addStaticAffordanceToNeeds: could not find corresponding detector for ${JSON.stringify(need)}` + } + const newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); + if (detectorKey == newDetectorKey) { + throw "detectorKey == newDetectorKey" + } + const newDetectorId = getDetectorId(DETECTORS[newDetectorKey]); + if (oldDetectorId == newDetectorKey) { + throw "old and new" + } + need.situation.detector = newDetectorKey; return need; }); }; @@ -1857,6 +1863,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-hands-in-front.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 1, }]), description: 'Use the sun to make a silhouette of your hand', @@ -1885,6 +1892,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-fruit-in-hand.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90, }]), description: 'While shopping for groceries, create a half half photo.', @@ -2702,6 +2710,7 @@ let EXPERIENCES = { toPass: { instruction: 'Can you take a photo of green vegetables? #leprechaunfood' }, + numberAllowedToParticipateAtSameTime: 1, numberNeeded: 5, notificationDelay: 20, // 20 seconds for debugging }, { diff --git a/imports/startup/client/router.js b/imports/startup/client/router.js index 0b236fed..1ac52799 100644 --- a/imports/startup/client/router.js +++ b/imports/startup/client/router.js @@ -83,25 +83,27 @@ Router.route('api.custom', { path: '/apicustom/:iid/:eid/:needName', template: 'api_custom', before: function () { - if (Meteor.userId()) { - let dic = { - uid: Meteor.userId(), - timestamp: Date.now(), - route: "customparticipate", - params: { - iid: this.params.iid, - eid: this.params.eid, - needName: this.params.needName - } - }; - Meteor.call('insertLog', dic); + if (!Meteor.userId()) { + Router.go('home'); } + let dic = { + uid: Meteor.userId(), + timestamp: Date.now(), + route: "customparticipate", + params: { + iid: this.params.iid, + eid: this.params.eid, + needName: this.params.needName + } + }; + Meteor.call('insertLog', dic); this.subscribe('experiences.single', this.params.eid).wait(); this.subscribe('incidents.single', this.params.iid).wait(); this.subscribe('locations.activeUser').wait(); this.subscribe('images.activeIncident', this.params.iid).wait(); this.subscribe('notification_log.activeIncident', this.params.iid).wait(); + this.subscribe('participating.now.activeIncident', this.params.iid).wait(); // TODO(rlouie): create subscribers which only get certain fields like, username which would be useful for templates this.subscribe('users.all').wait(); this.subscribe('avatars.all').wait(); diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index 64bcf15a..144eb2bc 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -10,15 +10,16 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Template } from 'meteor/templating'; import { Router } from 'meteor/iron:router'; -import { Experiences, Incidents } from '../../api/OCEManager/OCEs/experiences.js'; import { Users } from '../../api/UserMonitor/users/users.js'; -import { Locations } from '../../api/UserMonitor/locations/locations.js'; -import { Submissions } from '../../api/OCEManager/currentNeeds.js'; import { Images } from '../../api/ImageUpload/images.js'; import { photoInput } from './photoUploadHelpers.js' import { photoUpload } from './photoUploadHelpers.js' import {Meteor} from "meteor/meteor"; +import { + pullUserFromParticipatingNowNeedUserMaps, + pushUserIntoParticipatingNowNeedUserMaps +} from "../../api/OpportunisticCoordinator/strategizer"; // HELPER FUNCTIONS FOR LOADING CUSTOM EXPERIENCES @@ -385,48 +386,21 @@ const b64CropLikeCordova = function(base64PictureData, rect_width, rect_height, }; }; -// data2pass() { - // //TODO: clean up this hot mess of a function - // const instance = Template.instance(); - // const incident = instance.state.get('incident'); - // const subs = Submissions.find({ incidentId: incident._id }).fetch(); - // const hasSubs = subs.length > 0; - // - // // TODO: fix, dont want to get by experience - // const exp = instance.state.get('experience'); - // incident.situationNeeds.forEach((sitNeed) => { - // if (sitNeed.notifiedUsers.includes(Meteor.userId())) { - // situationNeedName = sitNeed.name; - // contributionTemplateName = sitNeed.contributionTemplate; - // affordance = sitNeed.affordance - // } - // }); - // let contributionTemplate; - // exp.contributionGroups.forEach((group) => { - // group.contributionTemplates.forEach((template) => { - // if (template.name === contributionTemplateName) { - // contributionTemplate = template - // } - // }); - // }); - // - // instance.state.set('situationNeedName', situationNeedName); - // instance.state.set('contributionTemplate', contributionTemplate); - // - // return { - // 'incident': incident, - // 'situationNeedName': situationNeedName, - // 'contributionTemplate': contributionTemplate, - // 'submissions': subs - // } - // }, - // template_name() { - // const instance = Template.instance(); - // return instance.state.get('experience').participateTemplate; - // } - Template.api_custom.onCreated(() => { + if (!Meteor.userId()) { + Router.go('home'); + } + else { + const params = Router.current().params; + pushUserIntoParticipatingNowNeedUserMaps(params.iid, params.needName, Meteor.userId()); + } +}); +Template.api_custom.onDestroyed(() => { + if (Meteor.userId()) { + const params = Router.current().params; + pullUserFromParticipatingNowNeedUserMaps(params.iid, params.needName, Meteor.userId()); + } }); Template.api_custom.events({ diff --git a/imports/ui/pages/dynamic_participate.js b/imports/ui/pages/dynamic_participate.js index e192c4a4..63e132cf 100644 --- a/imports/ui/pages/dynamic_participate.js +++ b/imports/ui/pages/dynamic_participate.js @@ -3,18 +3,25 @@ import { Meteor } from 'meteor/meteor'; import { Template } from 'meteor/templating'; import { Router } from 'meteor/iron:router'; import {Incidents} from "../../api/OCEManager/OCEs/experiences"; -import {Submissions} from "../../api/OCEManager/currentNeeds"; -import {needAggregator} from "../../api/OpportunisticCoordinator/strategizer"; +import { + needAggregator, needIsAvailableToParticipateNow, prioritizeHalfCompletedNeeds +} from "../../api/OpportunisticCoordinator/strategizer"; import {Assignments} from "../../api/OpportunisticCoordinator/databaseHelpers"; Template.dynamicParticipate.onCreated(function() { this.uid = Meteor.userId(); + + if (!this.uid) { + Router.go('home'); + return; + } this.iid = Router.current().params.iid; this.detectorId = Router.current().params.detectorId; const handles = [ this.subscribe('incidents.single', this.iid), this.subscribe('assignments.single', this.iid), this.subscribe('submissions.activeIncident', this.iid), + this.subscribe('participating.now.activeIncident', this.iid) ]; this.autorun(() => { @@ -34,44 +41,22 @@ Template.dynamicParticipate.onCreated(function() { this.assignment = Assignments.findOne(); let needNamesBinnedByDetector = needAggregator(this.incident); - let potentialNeeds = needNamesBinnedByDetector[this.detectorId]; + let potentialNeedNames = needNamesBinnedByDetector[this.detectorId]; // TODO: filter additionally by only the needs in which this user is assigned to - const numberSubmissionsRemaining = (iid, needName) => { - let numberPeopleNeeded = Submissions.find({ - iid: iid, - needName: needName, - uid: null - }).count(); - return numberPeopleNeeded; - }; - - /** - * needA will be given a higher value in the sorting, if needA has more submission left - * (e.g., a photo that needs 1 more person to complete it will - * @param needA - * @param needB - * @return {number} - */ - const prioritizeHalfCompletedNeeds = (needA, needB) => { - return numberSubmissionsRemaining(this.iid, needB) - numberSubmissionsRemaining(this.iid, needA); - }; - - potentialNeeds.sort(prioritizeHalfCompletedNeeds); // mutates + potentialNeedNames.sort(prioritizeHalfCompletedNeeds); // mutates + potentialNeedNames = potentialNeedNames.filter(needName => needIsAvailableToParticipateNow(this.incident, needName)); - // at this point, assignments are already filtered by user has already participated in this need - - // TODO: create collection that monitors the participateSemaphore; then filter the potentials + if (!potentialNeedNames.length) { + // tell user that somehow they were too late and there are no needs available for them + // or redirect them away from this page -- don't go route them to a participate screen. + return; + } // choose the top-1, then dynamically redirect to that participate - const chosenNeedName = potentialNeeds[0]; + const chosenNeedName = potentialNeedNames[0]; Router.go(`/apicustom/${this.iid}/${this.incident.eid}/${chosenNeedName}`); - - // _.forEach(needNamesBinnedByDetector, (commonDetectorNeedNames, detectorId) => { - // relevantAssignments - // }); - // this.assignment } }); }); diff --git a/imports/ui/pages/home.js b/imports/ui/pages/home.js index 960295c1..4f215b0f 100644 --- a/imports/ui/pages/home.js +++ b/imports/ui/pages/home.js @@ -43,6 +43,7 @@ Template.home.helpers({ let iid = assignment._id; let incident = Incidents.findOne(iid); + // TODO(rlouie): errors on refresh? try the competing resource test garret/barrett let needNamesBinnedByDetector = needAggregator(incident); // gah I wish the assignments had its own interface From 018f38054790b16d5201c10cb0ae4e5617b365c2 Mon Sep 17 00:00:00 2001 From: youralien Date: Fri, 10 May 2019 02:08:56 -0500 Subject: [PATCH 054/132] added ParticipatingNow semaphore model --- .../databaseHelpers.js | 12 --- .../server/identifier.js | 76 ++++++++++++++++++- .../OpportunisticCoordinator/strategizer.js | 47 ------------ imports/ui/pages/api_custom.js | 46 ++++++++--- 4 files changed, 112 insertions(+), 69 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/databaseHelpers.js b/imports/api/OpportunisticCoordinator/databaseHelpers.js index d27c8a41..3c288bf8 100644 --- a/imports/api/OpportunisticCoordinator/databaseHelpers.js +++ b/imports/api/OpportunisticCoordinator/databaseHelpers.js @@ -67,15 +67,3 @@ Schema.ParticipatingNow = new SimpleSchema({ export const ParticipatingNow = new Mongo.Collection('participating_now'); ParticipatingNow.attachSchema(Schema.ParticipatingNow); - -ParticipatingNow.allow({ - insert: function () { - return true; - }, - update: function () { - return true; - }, - remove: function () { - return true; - } -}); \ No newline at end of file diff --git a/imports/api/OpportunisticCoordinator/server/identifier.js b/imports/api/OpportunisticCoordinator/server/identifier.js index 7c7c0d83..7b685cea 100644 --- a/imports/api/OpportunisticCoordinator/server/identifier.js +++ b/imports/api/OpportunisticCoordinator/server/identifier.js @@ -1,7 +1,7 @@ import {ValidatedMethod} from "meteor/mdg:validated-method"; import {SimpleSchema} from "meteor/aldeed:simple-schema"; -import {Assignments, Availability} from "../databaseHelpers"; +import {Assignments, Availability, ParticipatingNow} from "../databaseHelpers"; import {Incidents} from "../../OCEManager/OCEs/experiences.js"; import {Submissions} from "../../OCEManager/currentNeeds.js"; import {Locations} from "../../UserMonitor/locations/locations"; @@ -19,6 +19,7 @@ import {flattenAffordanceDict} from "../../UserMonitor/detectors/methods"; import {Decommission_log} from "../../Logging/decommission_log"; import {AddedToIncident_log} from "../../Logging/added_to_incident_log"; + export const getNeedObject = (iid, needName) => { let incident = Incidents.findOne(iid); if (incident) { @@ -395,6 +396,79 @@ export const getNeedUserMapForNeed = (iid, needName) => { } }; + +Meteor.methods({ + pushUserIntoParticipatingNow({iid, needName, uid}) { + new SimpleSchema({ + iid: { type: String }, + needName: { type: String }, + uid: { type: String } + }).validate({iid, needName, uid}); + + pushUserIntoParticipatingNow(iid, needName, uid); + }, + pullUserFromParticipatingNow({iid, needName, uid}) { + new SimpleSchema({ + iid: { type: String }, + needName: { type: String }, + uid: { type: String } + }).validate({iid, needName, uid}); + + pullUserFromParticipatingNow(iid, needName, uid); + }, +}); + +/** + * pushUserIntoParticipatingNow + * + * The list of users in each needUserMap is a counter for who has the participate route open + * This function increments this "semaphore" like counter, or adds users + * @param iid + * @param needName + * @param uid + * @param place + * @param distance + */ +export const pushUserIntoParticipatingNow = (iid, needName, uid) => { + ParticipatingNow.update( + { + _id: iid, + "needUserMaps.needName": needName + }, + { + $push: { + // note: this object is different than {"uid": uid, "place": place, "distance": distance} + "needUserMaps.$.users": { + "uid": uid + } + } + } + ); +}; + + +/** + * pullUserFromParticipatingNow + * + * The list of users in each needUserMap is a counter for who has the participate route open + * This function decrements this "semaphore" like counter, or removes users + * @param iid + * @param needName + * @param uid + */ +export const pullUserFromParticipatingNow = (iid, needName, uid) => { + ParticipatingNow.update( + { + _id: iid, + "needUserMaps.needName": needName + }, + { + $pull: { "needUserMaps.$.users": {"uid" : uid } } + } + ); +}; + + // const locationCursor = Locations.find(); // // /** diff --git a/imports/api/OpportunisticCoordinator/strategizer.js b/imports/api/OpportunisticCoordinator/strategizer.js index f8636348..69bde890 100644 --- a/imports/api/OpportunisticCoordinator/strategizer.js +++ b/imports/api/OpportunisticCoordinator/strategizer.js @@ -126,50 +126,3 @@ export const numberUsersParticipatingNow = (iid, needName) => { return needMap.users.length; }; -/** - * pushUserIntoParticipatingNowNeedUserMaps - * - * The list of users in each needUserMap is a counter for who has the participate route open - * This function increments this "semaphore" like counter, or adds users - * @param iid - * @param needName - * @param uid - * @param place - * @param distance - */ -export const pushUserIntoParticipatingNowNeedUserMaps = (iid, needName, uid) => { - ParticipatingNow.update( - { - _id: iid - }, - { - $push: { - // note: this object is different than {"uid": uid, "place": place, "distance": distance} - "needUserMaps.$.users": { - "uid": uid - } - } - } - ); -}; - - -/** - * pullUserFromParticipatingNowNeedUserMaps - * - * The list of users in each needUserMap is a counter for who has the participate route open - * This function decrements this "semaphore" like counter, or removes users - * @param iid - * @param needName - * @param uid - */ -export const pullUserFromParticipatingNowNeedUserMaps = (iid, needName, uid) => { - ParticipatingNow.update( - { - _id: iid - }, - { - $pull: { "needUserMaps.$.users": {"uid" : uid } } - } - ); -}; diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index 144eb2bc..94c1efb9 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -16,10 +16,7 @@ import { Images } from '../../api/ImageUpload/images.js'; import { photoInput } from './photoUploadHelpers.js' import { photoUpload } from './photoUploadHelpers.js' import {Meteor} from "meteor/meteor"; -import { - pullUserFromParticipatingNowNeedUserMaps, - pushUserIntoParticipatingNowNeedUserMaps -} from "../../api/OpportunisticCoordinator/strategizer"; +import {needIsAvailableToParticipateNow} from "../../api/OpportunisticCoordinator/strategizer"; // HELPER FUNCTIONS FOR LOADING CUSTOM EXPERIENCES @@ -387,22 +384,53 @@ const b64CropLikeCordova = function(base64PictureData, rect_width, rect_height, }; Template.api_custom.onCreated(() => { + this.state = new ReactiveDict(); + if (!Meteor.userId()) { Router.go('home'); + return; } - else { - const params = Router.current().params; - pushUserIntoParticipatingNowNeedUserMaps(params.iid, params.needName, Meteor.userId()); + + const params = Router.current().params; + this.state.set('iid', params.iid); + this.state.set('needName', params.needName); + + const incident = Incidents.findOne({_id: params.iid}); + if (!needIsAvailableToParticipateNow(incident, params.needName)) { + // TODO: redirect to an apology page + Router.go('home'); + return; } + + Meteor.call('pushUserIntoParticipatingNow', { + iid: params.iid, needName: params.needName, uid: Meteor.userId() + }); }); Template.api_custom.onDestroyed(() => { + // Called when loading another route, and the template is gracefully destroyed if (Meteor.userId()) { - const params = Router.current().params; - pullUserFromParticipatingNowNeedUserMaps(params.iid, params.needName, Meteor.userId()); + Meteor.call('pullUserFromParticipatingNow', { + iid: this.state.get('iid'), + needName: this.state.get('needName'), + uid: Meteor.userId() + }); } + this.state.destroy(); }); +window.onbeforeunload = function() { + // Called when user closes the browser window; onDestroyed is not called in this instance + if (Meteor.userId()) { + Meteor.call('pullUserFromParticipatingNow', { + iid: this.state.get('iid'), + needName: this.state.get('needName'), + uid: Meteor.userId() + }); + } + this.state.destroy(); +}; + Template.api_custom.events({ 'submit form'(event, instance) { event.preventDefault(); From 3f014810292186cf9d924b84b0d9b09afaad279e Mon Sep 17 00:00:00 2001 From: youralien Date: Fri, 10 May 2019 08:43:25 -0500 Subject: [PATCH 055/132] Added input contracts to reduce client-side errors --- imports/api/OpportunisticCoordinator/strategizer.js | 4 ++++ imports/ui/pages/api_custom.js | 8 ++++---- imports/ui/pages/home.js | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/strategizer.js b/imports/api/OpportunisticCoordinator/strategizer.js index 69bde890..2b039b47 100644 --- a/imports/api/OpportunisticCoordinator/strategizer.js +++ b/imports/api/OpportunisticCoordinator/strategizer.js @@ -51,6 +51,10 @@ export const needAggregator = (incident) => { // keys: detectors // values: needs let res = {}; + if (!incident) { + console.log('needAggregator: incident is null'); + return res; + } _.forEach(incident.contributionTypes, (need) => { if (res[need.situation.detector]) { res[need.situation.detector].push(need.needName); diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index 94c1efb9..fd66b346 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -409,26 +409,26 @@ Template.api_custom.onCreated(() => { Template.api_custom.onDestroyed(() => { // Called when loading another route, and the template is gracefully destroyed - if (Meteor.userId()) { + if (Meteor.userId() && this.state) { Meteor.call('pullUserFromParticipatingNow', { iid: this.state.get('iid'), needName: this.state.get('needName'), uid: Meteor.userId() }); + this.state.destroy(); } - this.state.destroy(); }); window.onbeforeunload = function() { // Called when user closes the browser window; onDestroyed is not called in this instance - if (Meteor.userId()) { + if (Meteor.userId() && this.state) { Meteor.call('pullUserFromParticipatingNow', { iid: this.state.get('iid'), needName: this.state.get('needName'), uid: Meteor.userId() }); + this.state.destroy(); } - this.state.destroy(); }; Template.api_custom.events({ diff --git a/imports/ui/pages/home.js b/imports/ui/pages/home.js index 4f215b0f..5942ebad 100644 --- a/imports/ui/pages/home.js +++ b/imports/ui/pages/home.js @@ -43,6 +43,7 @@ Template.home.helpers({ let iid = assignment._id; let incident = Incidents.findOne(iid); + // TODO(rlouie): errors on refresh? try the competing resource test garret/barrett let needNamesBinnedByDetector = needAggregator(incident); From fa9f2dd65373dbe2a78810e6a138b0775c0c6da6 Mon Sep 17 00:00:00 2001 From: youralien Date: Fri, 10 May 2019 08:50:58 -0500 Subject: [PATCH 056/132] numberAllowedToParticipateAtSameTime: 1 for test experiences --- imports/api/Testing/testingconstants.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index ffe49c6e..4a935e29 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1949,6 +1949,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-cheers.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90 }]), description: 'While enjoying your drink, create a half half photo.', @@ -2003,6 +2004,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-religious-building.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90, }]), description: 'While visiting a place of worship, create a half half photo.', @@ -2031,6 +2033,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-sunset-heart.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 1, }]), description: 'While looking up at the sunset, create a half half photo.', @@ -2059,6 +2062,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-holding-chopsticks.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90 }]), description: 'While eating asian food, create a half half photo.', @@ -2087,6 +2091,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-book-face.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90, }]), description: 'While reading a book, create a half half photo.', @@ -2167,6 +2172,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-leaf-face.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90 }]), description: 'While in the park, create a half half photo.', @@ -2195,6 +2201,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-puddle-feet.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 1, }]), description: 'With the puddles on a rainy day, create a half half photo.', @@ -2300,6 +2307,7 @@ let EXPERIENCES = { exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-embodied-mimicry-big-bite.jpg' }, numberNeeded: 2, + numberAllowedToParticipateAtSameTime: 1, notificationDelay: 90, // https://www.quora.com/Whats-the-average-time-that-customers-wait-between-entering-a-restaurant-and-getting-served }]), description: 'While eating some non-trivially sized food, create a half half photo.', @@ -2710,8 +2718,8 @@ let EXPERIENCES = { toPass: { instruction: 'Can you take a photo of green vegetables? #leprechaunfood' }, - numberAllowedToParticipateAtSameTime: 1, - numberNeeded: 5, + numberAllowedToParticipateAtSameTime: 1, // for testing + numberNeeded: 5, // for testing notificationDelay: 20, // 20 seconds for debugging }, { needName: 'coins', From 72d0fa7d07782b29b7dd4818ded27150c3d964dd Mon Sep 17 00:00:00 2001 From: youralien Date: Fri, 10 May 2019 09:37:04 -0500 Subject: [PATCH 057/132] fixed a bug introduced in my refactor of addStaticAffordanceToNeed --- imports/api/Testing/testingconstants.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 4a935e29..84f46cd5 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1617,19 +1617,11 @@ const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { const addStaticAffordanceToNeeds = function(staticAffordance, contributionTypes) { return _.map(contributionTypes, (need) => { const detectorKey = _.keys(DETECTORS).find(key => DETECTORS[key]._id === need.situation.detector); - const oldDetectorId = getDetectorId(DETECTORS[detectorKey]); if (!detectorKey) { throw `Exception in addStaticAffordanceToNeeds: could not find corresponding detector for ${JSON.stringify(need)}` } const newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); - if (detectorKey == newDetectorKey) { - throw "detectorKey == newDetectorKey" - } - const newDetectorId = getDetectorId(DETECTORS[newDetectorKey]); - if (oldDetectorId == newDetectorKey) { - throw "old and new" - } - need.situation.detector = newDetectorKey; + need.situation.detector = getDetectorId(DETECTORS[newDetectorKey]); return need; }); }; From a4fd0d0d886d71ca7a5d8a22da294ecf10cc616e Mon Sep 17 00:00:00 2001 From: youralien Date: Fri, 10 May 2019 10:22:14 -0500 Subject: [PATCH 058/132] Made interface descriptions of the different states of the app more visually friendly and informative --- .../OpportunisticCoordinator/strategizer.js | 7 ++++- imports/ui/pages/dynamic_participate.html | 31 ++++++++++++++++++- imports/ui/pages/dynamic_participate.js | 15 +++++++++ imports/ui/pages/home.html | 6 +++- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/strategizer.js b/imports/api/OpportunisticCoordinator/strategizer.js index 2b039b47..84e263a4 100644 --- a/imports/api/OpportunisticCoordinator/strategizer.js +++ b/imports/api/OpportunisticCoordinator/strategizer.js @@ -99,8 +99,13 @@ export const numberSubmissionsRemaining = (iid, needName) => { }; export const needIsAvailableToParticipateNow = (incident, needName) => { + if (!incident) { + console.log(`Error in needAggregator: incident is null\n ${JSON.stringify(incident)}`); + return; + } if (!incident.contributionTypes) { - throw `Error in needAggregator: incident does not have contribution types\n ${JSON.stringify(incident)}`; + console.log(`Error in needAggregator: incident does not have contribution types\n ${JSON.stringify(incident)}`); + return; } const needObject = incident.contributionTypes.find(need => need.needName == needName); const numberNeeded = numberSubmissionsRemaining(incident._id, needName); diff --git a/imports/ui/pages/dynamic_participate.html b/imports/ui/pages/dynamic_participate.html index d85751d5..c7ea44b7 100644 --- a/imports/ui/pages/dynamic_participate.html +++ b/imports/ui/pages/dynamic_participate.html @@ -1,3 +1,32 @@ \ No newline at end of file diff --git a/imports/ui/pages/dynamic_participate.js b/imports/ui/pages/dynamic_participate.js index 63e132cf..aa45d2d2 100644 --- a/imports/ui/pages/dynamic_participate.js +++ b/imports/ui/pages/dynamic_participate.js @@ -1,5 +1,6 @@ import './dynamic_participate.html'; import { Meteor } from 'meteor/meteor'; +import { ReactiveDict } from 'meteor/reactive-dict'; import { Template } from 'meteor/templating'; import { Router } from 'meteor/iron:router'; import {Incidents} from "../../api/OCEManager/OCEs/experiences"; @@ -9,6 +10,8 @@ import { import {Assignments} from "../../api/OpportunisticCoordinator/databaseHelpers"; Template.dynamicParticipate.onCreated(function() { + this.state = new ReactiveDict(); + this.state.set('renderWaiting', false); this.uid = Meteor.userId(); if (!this.uid) { @@ -51,6 +54,7 @@ Template.dynamicParticipate.onCreated(function() { if (!potentialNeedNames.length) { // tell user that somehow they were too late and there are no needs available for them // or redirect them away from this page -- don't go route them to a participate screen. + this.state.set('renderWaiting', true); return; } @@ -61,3 +65,14 @@ Template.dynamicParticipate.onCreated(function() { }); }); +Template.dynamicParticipate.helpers({ + renderWaiting() { + return Template.instance().state.get('renderWaiting') + } +}); + +Template.dynamicParticipate.onDestroyed(() => { + if (this.state) { + this.state.destroy(); + } +}); diff --git a/imports/ui/pages/home.html b/imports/ui/pages/home.html index c2d77ca0..92753dfa 100644 --- a/imports/ui/pages/home.html +++ b/imports/ui/pages/home.html @@ -47,7 +47,11 @@

Available experiences

{{/if}} {{else}} - Loading... +
+

🤞📲

+

Trying to fetch data...

+

You may have to close and restart the app if this takes too long 😭

+
{{/if}} {{else}}

Welcome to CE 👋

From d842c1924266a44ea444b3ce7aa15f42f5d7bc2e Mon Sep 17 00:00:00 2001 From: youralien Date: Mon, 13 May 2019 18:53:56 -0500 Subject: [PATCH 059/132] Fix error with null need in getNeedFromIncidentId --- imports/api/OCEManager/OCEs/methods.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/imports/api/OCEManager/OCEs/methods.js b/imports/api/OCEManager/OCEs/methods.js index d8b95b43..b82fdb96 100644 --- a/imports/api/OCEManager/OCEs/methods.js +++ b/imports/api/OCEManager/OCEs/methods.js @@ -497,6 +497,11 @@ export const getNeedFromIncidentId = (iid, needName) => { let incident = Incidents.findOne(iid); let output = undefined; + if (!incident) { + console.error(`Error in getNeedFromIncidentId: Could not find incident of iid = ${iid}`) + return false; + } + _.forEach(incident.contributionTypes, (need) => { if (need.needName === needName) { output = need; From f71bb87da3448fe4f9424ac7e28a5829280fdabf Mon Sep 17 00:00:00 2001 From: youralien Date: Tue, 14 May 2019 19:23:37 -0500 Subject: [PATCH 060/132] Fixing functions to more gracefully error upon undefined input contract violations --- .../OpportunisticCoordinator/strategizer.js | 29 ++++++++++--------- imports/ui/pages/dynamic_participate.js | 15 ++++++++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/strategizer.js b/imports/api/OpportunisticCoordinator/strategizer.js index 84e263a4..50ab5d66 100644 --- a/imports/api/OpportunisticCoordinator/strategizer.js +++ b/imports/api/OpportunisticCoordinator/strategizer.js @@ -66,18 +66,6 @@ export const needAggregator = (incident) => { return res; }; -/** - * needA will be given a higher value in the sorting, if needA has fewer submissions left - * (e.g., a photo that needs 1 more person to complete it will be ranked higher) - * TODO(rlouie): What about the case where some needs have 1 out of 2 more needed, - * while others just need 1 out of 1 submission to complete? - * @param needA - * @param needB - * @return {number} - */ -export const prioritizeHalfCompletedNeeds = (needA, needB) => { - return numberSubmissionsRemaining(this.iid, needB) - numberSubmissionsRemaining(this.iid, needA); -}; /** * numberSubmissionsNeeded @@ -87,13 +75,20 @@ export const prioritizeHalfCompletedNeeds = (needA, needB) => { * @return {any | * | IDBRequest | void} */ export const numberSubmissionsRemaining = (iid, needName) => { + if (!iid) { + console.error(`Error in numberSubmissionsRemaining: param iid undefined`) + } + if (!needName) { + console.error(`Error in numberSubmissionsRemaining: param needName undefined`) + } let numSubsNeeded = Submissions.find({ iid: iid, needName: needName, uid: null }).count(); if (!Number.isInteger(numSubsNeeded)) { - throw `Error in numSubmissionsNeeded: numSubsNeeded is not an Integer`; + console.error(`Error in numSubmissionsNeeded: numSubsNeeded is not an Integer`); + return; } return numSubsNeeded; }; @@ -103,6 +98,10 @@ export const needIsAvailableToParticipateNow = (incident, needName) => { console.log(`Error in needAggregator: incident is null\n ${JSON.stringify(incident)}`); return; } + if (!needName) { + console.log(`Error in needAggregator: needName is null`); + return; + } if (!incident.contributionTypes) { console.log(`Error in needAggregator: incident does not have contribution types\n ${JSON.stringify(incident)}`); return; @@ -123,6 +122,10 @@ export const needIsAvailableToParticipateNow = (incident, needName) => { */ export const numberUsersParticipatingNow = (iid, needName) => { let participatingNowInIncident = ParticipatingNow.findOne({_id: iid}); + if (!participatingNowInIncident) { + console.error(`Error in numberUsersParticipatingNow: no doc in ParticipatingNow found for iid ${iid}`); + return; + } if (!Array.isArray(participatingNowInIncident.needUserMaps)) { console.error(`Error in numberUsersParticipatingNow: participatingNowInIncident.needUserMaps is not an array`); return; diff --git a/imports/ui/pages/dynamic_participate.js b/imports/ui/pages/dynamic_participate.js index aa45d2d2..36bb606a 100644 --- a/imports/ui/pages/dynamic_participate.js +++ b/imports/ui/pages/dynamic_participate.js @@ -5,7 +5,7 @@ import { Template } from 'meteor/templating'; import { Router } from 'meteor/iron:router'; import {Incidents} from "../../api/OCEManager/OCEs/experiences"; import { - needAggregator, needIsAvailableToParticipateNow, prioritizeHalfCompletedNeeds + needAggregator, needIsAvailableToParticipateNow, numberSubmissionsRemaining, prioritizeHalfCompletedNeeds } from "../../api/OpportunisticCoordinator/strategizer"; import {Assignments} from "../../api/OpportunisticCoordinator/databaseHelpers"; @@ -47,7 +47,18 @@ Template.dynamicParticipate.onCreated(function() { let potentialNeedNames = needNamesBinnedByDetector[this.detectorId]; // TODO: filter additionally by only the needs in which this user is assigned to - + /** + * needA will be given a higher value in the sorting, if needA has fewer submissions left + * (e.g., a photo that needs 1 more person to complete it will be ranked higher) + * TODO(rlouie): What about the case where some needs have 1 out of 2 more needed, + * while others just need 1 out of 1 submission to complete? + * @param needA + * @param needB + * @return {number} + */ + const prioritizeHalfCompletedNeeds = (needA, needB) => { + return numberSubmissionsRemaining(this.iid, needB) - numberSubmissionsRemaining(this.iid, needA); + }; potentialNeedNames.sort(prioritizeHalfCompletedNeeds); // mutates potentialNeedNames = potentialNeedNames.filter(needName => needIsAvailableToParticipateNow(this.incident, needName)); From bcae6310eca52975edc4176e2d9cfad2fa6eaecf Mon Sep 17 00:00:00 2001 From: youralien Date: Tue, 14 May 2019 20:12:24 -0500 Subject: [PATCH 061/132] Fix imports in api_custom.js for new onCreated function --- imports/ui/pages/api_custom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/imports/ui/pages/api_custom.js b/imports/ui/pages/api_custom.js index fd66b346..fc19478e 100644 --- a/imports/ui/pages/api_custom.js +++ b/imports/ui/pages/api_custom.js @@ -12,6 +12,7 @@ import { Router } from 'meteor/iron:router'; import { Users } from '../../api/UserMonitor/users/users.js'; import { Images } from '../../api/ImageUpload/images.js'; +import { Incidents } from "../../api/OCEManager/OCEs/experiences"; import { photoInput } from './photoUploadHelpers.js' import { photoUpload } from './photoUploadHelpers.js' From f4ab8402efd8a1a7484553efe2102c572cbf442b Mon Sep 17 00:00:00 2001 From: youralien Date: Tue, 14 May 2019 23:11:39 -0500 Subject: [PATCH 062/132] updated simulate locations for local/prod/staging testing --- simulatelocations.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/simulatelocations.py b/simulatelocations.py index 03839c05..e38e4274 100644 --- a/simulatelocations.py +++ b/simulatelocations.py @@ -4,7 +4,7 @@ path1 = [(43, -87), (32, -120)] -park = (42.056838, -87.675940) +park = (42.056838, -87.675940) burgers = (42.046131, -87.681559) grocery = (42.047621, -87.679488) grocery2 = (42.039818,-87.680088) @@ -13,7 +13,7 @@ sydney = (-33, 151) train = (42.053872,-87.683748) brisbane = (-37.822464, 144.966146) - +library = (42.058141, -87.674490) def followPath(path, uid): for stop in path: @@ -21,7 +21,10 @@ def followPath(path, uid): time.sleep(1) def setLocation(location, uid): - r = requests.post("http://localhost:3000/api/geolocation", json={ + host = "http://localhost:3000" + # host = "https://ce-platform.herokuapp.com" + # host = "https://staging-ce-platform.herokuapp.com" + r = requests.post(host + "/api/geolocation", json={ "userId": uid, "location": { "coords": { @@ -294,7 +297,9 @@ def allUsersGrocery(): print("all users at grocery") if __name__ == "__main__": - allUsersGrocery() + # single user movement + # setLocation(burgers, sys.argv[1]) + # allUsersGrocery() #garrettAndMegBump() #time.sleep(2) #onlyGarretAtLake() From a9eb381add2bf014d5ea8fef1b95e465c53f414d Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Thu, 16 May 2019 18:27:44 -0500 Subject: [PATCH 063/132] Halfway through static affordance bumpedThree implementation --- imports/api/Testing/testingconstants.js | 130 ++++++++++++++---------- simulatelocations.py | 13 +-- 2 files changed, 83 insertions(+), 60 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 7b388606..11a8537d 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1164,16 +1164,6 @@ const createHalfHalf = function( }; const createBumpedThree = function() { - let experience = { - name: 'Group Bumped', - participateTemplate: 'bumpedThree', - resultsTemplate: 'bumpedThreeResults', - contributionTypes: [], - description: 'Share your experience with your friend and their friend!', - notificationText: 'Share your experience with your friend and their friend!', - callbacks: [] - }; - const bumpedThreeCallback = function (sub) { console.log("A bumpedThree experience completed!"); @@ -1188,55 +1178,91 @@ const createBumpedThree = function() { } - let triads = ['triadOne', 'triadTwo', 'triadThree']; - let places = [ + let experience = { + name: 'Group Bumped', + participateTemplate: 'bumpedThree', + resultsTemplate: 'bumpedThreeResults', + contributionTypes: [], + description: 'Share your experience with your friend and their friend!', + notificationText: 'Share your experience with your friend and their friend!', + callbacks: [{ + trigger: `cb.numberOfSubmissions() === 3`, // ? + function: bumpedThreeCallback.toString(), + }] + }; + + + const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; + const places = [ ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], - //weather/sky and other bumped three experiences can be added here ]; - _.forEach(triads, (triad) => { - _.forEach(places, (place) => { - const [detectorName, situationDescription, instruction] = place; - let newVars = JSON.parse(JSON.stringify(DETECTORS[detectorName]['variables'])); - newVars.push(`var ${triad};`); - - let newRules = JSON.parse(JSON.stringify(DETECTORS[detectorName]['rules'])); - let lastRule = newRules.pop(); - let lastRuleNoSemicolon = lastRule.split(';')[0]; - lastRule = `(${triad} && (${lastRuleNoSemicolon}));`; - newRules.push(lastRule); - - - let detector = { - '_id': Random.id(), - 'description': DETECTORS[detectorName].description + triad, - 'variables': newVars, - 'rules': newRules - }; - DETECTORS[detectorName + triad] = detector; - const need = { - needName: `bumped three: ${detectorName} ${triad}`, - situation: { - detector: detector._id, + const needs = places.map(place => { + const [detectorName, situationDescription, instruction] = place; + return { + needName: `Bumped Three`, + situation: { + detector: getDetectorId(DETECTORS[detectorName]), number: '1' - }, - toPass: { - situationDescription: `Having a good time ${situationDescription}?`, - instruction: `${instruction}` - }, - numberNeeded: 3, - // notificationDelay: 90 uncomment for testing - }; - let callback = { - trigger: `cb.numberOfSubmissions("${need.needName}") === 3`, - function: bumpedThreeCallback.toString(), - }; - experience.contributionTypes.push(need); - experience.callbacks.push(callback) - }); + }, + toPass: { + situationDescription: `Having a good time ${situationDescription}?`, + instruction: `${instruction}` + }, + numberNeeded: 3, + // notificationDelay: 90 uncomment for testing + } + }); + + staticAffordances.forEach(triad => { + experience.contributionTypes.push(addStaticAffordanceToNeeds(triad, needs)); }); + // _.forEach(triads, (triad) => { + // _.forEach(places, (place) => { + // const [detectorName, situationDescription, instruction] = place; + // let newVars = JSON.parse(JSON.stringify(DETECTORS[detectorName]['variables'])); + // newVars.push(`var ${triad};`); + + // let newRules = JSON.parse(JSON.stringify(DETECTORS[detectorName]['rules'])); + // let lastRule = newRules.pop(); + // let lastRuleNoSemicolon = lastRule.split(';')[0]; + // lastRule = `(${triad} && (${lastRuleNoSemicolon}));`; + // newRules.push(lastRule); + + + // let detector = { + // '_id': Random.id(), // use addStaticAffordanceToNeed function and getdetectorid instead of random , use halfhalfsunny as an example + // 'description': DETECTORS[detectorName].description + triad, + // 'variables': newVars, + // 'rules': newRules + // }; + // DETECTORS[detectorName + triad] = detector; + + // const need = { + // needName: `bumped three: ${detectorName} ${triad}`, + // situation: { + // detector: detector._id, + // number: '1' + // }, + // toPass: { + // situationDescription: `Having a good time ${situationDescription}?`, + // instruction: `${instruction}` + // }, + // numberNeeded: 3, + // // notificationDelay: 90 uncomment for testing + // }; + // let callback = { + // trigger: `cb.numberOfSubmissions("${need.needName}") === 3`, + // function: bumpedThreeCallback.toString(), + // }; + // experience.contributionTypes.push(need); + // experience.callbacks.push(callback) + // }); + // }); + console.log(experience); + return experience; } diff --git a/simulatelocations.py b/simulatelocations.py index 764e6eab..97181d02 100644 --- a/simulatelocations.py +++ b/simulatelocations.py @@ -21,7 +21,8 @@ def followPath(path, uid): time.sleep(1) def setLocation(location, uid): - r = requests.post("http://localhost:3000/api/geolocation", json={ + # r = requests.post("https://staging-ce-platform.herokuapp.com/api/geolocation", json={ + r = requests.post("http://localhost:3000//api/geolocation", json={ "userId": uid, "location": { "coords": { @@ -294,13 +295,9 @@ def allUsersGrocery(): print("all users at grocery") def allUsersCoffee(): - setLocation(coffee, sys.argv[1]) - setLocation(coffee, sys.argv[2]) - setLocation(coffee, sys.argv[3]) - setLocation(coffee, sys.argv[4]) - setLocation(coffee, sys.argv[5]) - - print("all users at coffee") + for i in sys.argv: + setLocation(coffee, i) + print("all users at coffee") if __name__ == "__main__": # allUsersGrocery() From 3789ccb59276967c47ad7e01b7bee65dd8c9ed80 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Thu, 16 May 2019 19:10:52 -0500 Subject: [PATCH 064/132] hardcode needs in bumpedthree static affordances --- imports/api/Testing/testingconstants.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 7385dfaf..67067e36 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1195,7 +1195,7 @@ const createBumpedThree = function() { const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; const places = [ - ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], + // ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], ]; @@ -1216,8 +1216,23 @@ const createBumpedThree = function() { } }); + console.log(needs); + + staticAffordances.forEach(triad => { - experience.contributionTypes.push(addStaticAffordanceToNeeds(triad, needs)); + // experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, needs)]; + addStaticAffordanceToNeeds(triad, [ { needName: 'Bumped Three', + situation: { detector: getDetectorId(DETECTORS['coffee']), number: '1' }, + toPass: + { situationDescription: 'Having a good time at a coffee shop?', + instruction: 'Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)' }, + numberNeeded: 3 }, + { needName: 'Bumped Three', + situation: { detector: getDetectorId(DETECTORS['daytime']), number: '1' }, + toPass: + { situationDescription: 'Having a good time today?', + instruction: 'Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel.' }, + numberNeeded: 3 } ]) }); // _.forEach(triads, (triad) => { @@ -1794,6 +1809,7 @@ const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { */ const addStaticAffordanceToNeeds = function(staticAffordance, contributionTypes) { return _.map(contributionTypes, (need) => { + console.log(need.situation.detector); const detectorKey = _.keys(DETECTORS).find(key => DETECTORS[key]._id === need.situation.detector); if (!detectorKey) { throw `Exception in addStaticAffordanceToNeeds: could not find corresponding detector for ${JSON.stringify(need)}` @@ -1911,7 +1927,7 @@ const sendNotificationTwoHalvesCompleted = function(sub) { let EXPERIENCES = { bumped: createBumped(), - bumpedThree: createBumpedThree(), + // bumpedThree: createBumpedThree(), storyTime: createStorytime(0), storyTime1: createStorytime(1), storyTime2: createStorytime(2), From e5720db8085a75ca03127603439b7b951d13ae14 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Thu, 16 May 2019 19:14:56 -0500 Subject: [PATCH 065/132] uncommment create bumpedthree --- imports/api/Testing/testingconstants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 67067e36..2dd566c4 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1927,7 +1927,7 @@ const sendNotificationTwoHalvesCompleted = function(sub) { let EXPERIENCES = { bumped: createBumped(), - // bumpedThree: createBumpedThree(), + bumpedThree: createBumpedThree(), storyTime: createStorytime(0), storyTime1: createStorytime(1), storyTime2: createStorytime(2), From 603185c1b7cb628711e77dc7ba695aaf32dc5f58 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Fri, 17 May 2019 16:59:29 -0500 Subject: [PATCH 066/132] add function to grab availabilities based on user id --- imports/api/OpportunisticCoordinator/identifier.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 imports/api/OpportunisticCoordinator/identifier.js diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js new file mode 100644 index 00000000..8b4afa1c --- /dev/null +++ b/imports/api/OpportunisticCoordinator/identifier.js @@ -0,0 +1,5 @@ +import { Availability } from "./databaseHelpers"; + +export const getUserAvailabilities = (uid) => { + Availability.find({"needUserMaps.users.uid": uid}); +} From 92986d11353c5834898e55145482be8d5682e647 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Fri, 17 May 2019 17:02:17 -0500 Subject: [PATCH 067/132] add return --- imports/api/OpportunisticCoordinator/identifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js index 8b4afa1c..c0e9e305 100644 --- a/imports/api/OpportunisticCoordinator/identifier.js +++ b/imports/api/OpportunisticCoordinator/identifier.js @@ -1,5 +1,5 @@ import { Availability } from "./databaseHelpers"; export const getUserAvailabilities = (uid) => { - Availability.find({"needUserMaps.users.uid": uid}); + return Availability.find({"needUserMaps.users.uid": uid}); } From aaef6991d22095cdd159aadae07ada9bc817c004 Mon Sep 17 00:00:00 2001 From: youralien Date: Tue, 14 May 2019 23:43:29 -0500 Subject: [PATCH 068/132] Host fontawesome files so theres never issues with getting the wrong icons from CDN? --- .meteor/packages | 1 + .meteor/versions | 1 + client/head.html | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.meteor/packages b/.meteor/packages index 6922c999..e68b0b20 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -56,3 +56,4 @@ useraccounts:iron-routing aldeed:template-extension useraccounts:bootstrap underscore +fortawesome:fontawesome diff --git a/.meteor/versions b/.meteor/versions index 810ebfaf..d131e974 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -64,6 +64,7 @@ es5-shim@4.8.0 fetch@0.1.1 force-ssl@1.1.0 force-ssl-common@1.1.0 +fortawesome:fontawesome@4.7.0 fourseven:scss@3.13.0 geojson-utils@1.0.10 hot-code-push@1.0.4 diff --git a/client/head.html b/client/head.html index 5a320bba..d62af2d6 100644 --- a/client/head.html +++ b/client/head.html @@ -25,7 +25,6 @@ - From a0214d9ce05920670f5f4c7b0370fa094d69201c Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Fri, 17 May 2019 16:59:29 -0500 Subject: [PATCH 069/132] add function to grab availabilities based on user id --- imports/api/OpportunisticCoordinator/identifier.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 imports/api/OpportunisticCoordinator/identifier.js diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js new file mode 100644 index 00000000..8b4afa1c --- /dev/null +++ b/imports/api/OpportunisticCoordinator/identifier.js @@ -0,0 +1,5 @@ +import { Availability } from "./databaseHelpers"; + +export const getUserAvailabilities = (uid) => { + Availability.find({"needUserMaps.users.uid": uid}); +} From 9ab706df1d9671813cc509aab5fecd7affb05c49 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Fri, 17 May 2019 17:02:17 -0500 Subject: [PATCH 070/132] add return --- imports/api/OpportunisticCoordinator/identifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js index 8b4afa1c..c0e9e305 100644 --- a/imports/api/OpportunisticCoordinator/identifier.js +++ b/imports/api/OpportunisticCoordinator/identifier.js @@ -1,5 +1,5 @@ import { Availability } from "./databaseHelpers"; export const getUserAvailabilities = (uid) => { - Availability.find({"needUserMaps.users.uid": uid}); + return Availability.find({"needUserMaps.users.uid": uid}); } From 197b4c7cdd3e78a4ed448001eae253163009ad5a Mon Sep 17 00:00:00 2001 From: youralien Date: Sat, 18 May 2019 00:39:12 -0500 Subject: [PATCH 071/132] Created meteor method for getUserAvailabilities --- .../OpportunisticCoordinator/identifier.js | 22 +++++++++++++++++-- imports/startup/server/register-api.js | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js index c0e9e305..664ffdf4 100644 --- a/imports/api/OpportunisticCoordinator/identifier.js +++ b/imports/api/OpportunisticCoordinator/identifier.js @@ -1,5 +1,23 @@ +import { Meteor } from "meteor/meteor"; import { Availability } from "./databaseHelpers"; + +Meteor.methods({ + getUserAvailabilities({uid}) { + new SimpleSchema({ + uid: { type: String } + }).validate({uid}); + + const user = Meteor.users.findOne(uid); + if (!user) { + throw new Meteor.Error('getUserAvailabilities.userNotFound', + `User not found with uid = ${uid}`); + } + return getUserAvailabilities(uid); + } + +}); + export const getUserAvailabilities = (uid) => { - return Availability.find({"needUserMaps.users.uid": uid}); -} + return Availability.find({"needUserMaps.users.uid": uid}); +}; diff --git a/imports/startup/server/register-api.js b/imports/startup/server/register-api.js index a8b9398b..409803d1 100644 --- a/imports/startup/server/register-api.js +++ b/imports/startup/server/register-api.js @@ -9,6 +9,7 @@ import '../../api/UserMonitor/locations/methods.js'; import '../../api/UserMonitor/detectors/server/publications.js'; import '../../api/UserMonitor/detectors/methods.js'; import '../../api/OpportunisticCoordinator/server/identifier.js'; +import '../../api/OpportunisticCoordinator/identifier.js'; import '../../api/OpportunisticCoordinator/server/publications.js'; import '../../api/OCEManager/progressorHelper.js'; import '../../api/OCEManager/server/publications.js'; From 85589ef5be50fd0389b5cbc520884af0ce2cf6c8 Mon Sep 17 00:00:00 2001 From: youralien Date: Sat, 18 May 2019 01:56:47 -0500 Subject: [PATCH 072/132] activeIncidents user collection helper method Summary: - Moved responsibility of activeIncdents to the UserMonitor/methods.js file - Added users.tests.js to test the helper methods for activeIncident computation - added meteor package dburles:collection-helpers - Deleted identifier.js visible to client/server in OpportunisticCoordinator --- .meteor/packages | 1 + .meteor/versions | 1 + .../OpportunisticCoordinator/identifier.js | 23 ----- imports/api/UserMonitor/users/methods.js | 23 +++++ imports/api/UserMonitor/users/users.tests.js | 87 +++++++++++++++++++ imports/startup/server/register-api.js | 1 - 6 files changed, 112 insertions(+), 24 deletions(-) delete mode 100644 imports/api/OpportunisticCoordinator/identifier.js create mode 100644 imports/api/UserMonitor/users/users.tests.js diff --git a/.meteor/packages b/.meteor/packages index e68b0b20..1592903a 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -57,3 +57,4 @@ aldeed:template-extension useraccounts:bootstrap underscore fortawesome:fontawesome +dburles:collection-helpers diff --git a/.meteor/versions b/.meteor/versions index d131e974..bc5e0144 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -44,6 +44,7 @@ cfs:upload-http@0.0.20 cfs:worker@0.1.5 check@1.3.1 coffeescript@1.0.17 +dburles:collection-helpers@1.1.0 dburles:google-maps@1.1.5 ddp@1.4.0 ddp-client@2.3.3 diff --git a/imports/api/OpportunisticCoordinator/identifier.js b/imports/api/OpportunisticCoordinator/identifier.js deleted file mode 100644 index 664ffdf4..00000000 --- a/imports/api/OpportunisticCoordinator/identifier.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Meteor } from "meteor/meteor"; -import { Availability } from "./databaseHelpers"; - - -Meteor.methods({ - getUserAvailabilities({uid}) { - new SimpleSchema({ - uid: { type: String } - }).validate({uid}); - - const user = Meteor.users.findOne(uid); - if (!user) { - throw new Meteor.Error('getUserAvailabilities.userNotFound', - `User not found with uid = ${uid}`); - } - return getUserAvailabilities(uid); - } - -}); - -export const getUserAvailabilities = (uid) => { - return Availability.find({"needUserMaps.users.uid": uid}); -}; diff --git a/imports/api/UserMonitor/users/methods.js b/imports/api/UserMonitor/users/methods.js index 01f3f0cb..9566f3cd 100644 --- a/imports/api/UserMonitor/users/methods.js +++ b/imports/api/UserMonitor/users/methods.js @@ -1,6 +1,29 @@ import { Meteor } from 'meteor/meteor'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { SimpleSchema } from 'meteor/aldeed:simple-schema'; +import { Assignments } from "../../OpportunisticCoordinator/databaseHelpers"; + +// via dburles:collection-helpers +Meteor.users.helpers({ + /** + * usage: Meteor.users.findOne().activeIncidents() + * + * @returns: activeIncidents {Array} array of incident iids e.g. [iid1, iid2] + */ + activeIncidents() { + return getUserActiveIncidents(this._id); + } +}); + +/** + * activeIncidents are the ones in which a user is assigned. + * + * @param uid + * @return activeIncidents {Array} array of incident iids e.g. [iid1, iid2] + */ +export const getUserActiveIncidents = (uid) => { + return Assignments.find({"needUserMaps.users.uid": uid}, {fields: {_id: 1}}).map(doc => doc._id); +}; export const findUserByUsername = function (username) { return Meteor.users.findOne({ 'username': username }); diff --git a/imports/api/UserMonitor/users/users.tests.js b/imports/api/UserMonitor/users/users.tests.js new file mode 100644 index 00000000..f40bedf2 --- /dev/null +++ b/imports/api/UserMonitor/users/users.tests.js @@ -0,0 +1,87 @@ +import {Meteor} from 'meteor/meteor'; +import {resetDatabase} from 'meteor/xolvio:cleaner'; +import {Assignments} from "../../OpportunisticCoordinator/databaseHelpers"; +import {findUserByUsername, getUserActiveIncidents} from "./methods"; +import {insertTestUser} from "../../OpportunisticCoordinator/populateDatabase"; + +describe('users activeIncidents computed from Assignments', () => { + const iid1 = Random.id(); + const iid2 = Random.id(); + const usernameA = 'garrett'; + const usernameB = 'garretts_brother'; + const needName1 = 'Rainy 1'; + const needName2 = 'Rainy 2'; + let userA; + let userB; + + beforeEach(() => { + resetDatabase(); + + insertTestUser(usernameA); + insertTestUser(usernameB); + const testUserA = findUserByUsername(usernameA); + const testUserB = findUserByUsername(usernameB); + userA = testUserA._id; + userB = testUserB._id; + Assignments.insert({ + _id: iid1, + needUserMaps: [ + { + needName: needName1, + users: [ + {uid: userA} + ] + }, + ], + }); + Assignments.insert({ + _id: iid2, + needUserMaps: [ + { + needName: needName2, + users: [ + {uid: userA}, + {uid: userB}, + ] + } + ], + }); + + }); + + it('should show userA be assigned to two incidents via getUserActiveIncidents', () => { + + const activeIncidents = getUserActiveIncidents(userA); + console.log(`activeIncidents: \n ${JSON.stringify(activeIncidents)}`); + chai.assert(activeIncidents.includes(iid1)); + chai.assert(activeIncidents.includes(iid2)); + + }); + + it('should show userB be assigned to one incident via getUserActiveIncidents', () => { + + const activeIncidents = getUserActiveIncidents(userB); + console.log(`activeIncidents: \n ${JSON.stringify(activeIncidents)}`); + + chai.assert(activeIncidents.includes(iid2)); + + }); + + it('should show userA be assigned to two incidents via activeIncidents collection helper', () => { + + const activeIncidents = Meteor.users.findOne(userA).activeIncidents(); + console.log(`activeIncidents: \n ${JSON.stringify(activeIncidents)}`); + chai.assert(activeIncidents.includes(iid1)); + chai.assert(activeIncidents.includes(iid2)); + + }); + + it('should show userB be assigned to one incident via activeIncidents collection helper', () => { + + const activeIncidents = Meteor.users.findOne(userB).activeIncidents(); + console.log(`activeIncidents: \n ${JSON.stringify(activeIncidents)}`); + + chai.assert(activeIncidents.includes(iid2)); + + }); +}); diff --git a/imports/startup/server/register-api.js b/imports/startup/server/register-api.js index 409803d1..a8b9398b 100644 --- a/imports/startup/server/register-api.js +++ b/imports/startup/server/register-api.js @@ -9,7 +9,6 @@ import '../../api/UserMonitor/locations/methods.js'; import '../../api/UserMonitor/detectors/server/publications.js'; import '../../api/UserMonitor/detectors/methods.js'; import '../../api/OpportunisticCoordinator/server/identifier.js'; -import '../../api/OpportunisticCoordinator/identifier.js'; import '../../api/OpportunisticCoordinator/server/publications.js'; import '../../api/OCEManager/progressorHelper.js'; import '../../api/OCEManager/server/publications.js'; From 15e906a6f582984197dd8aa5675bc272b9747d0f Mon Sep 17 00:00:00 2001 From: youralien Date: Sat, 18 May 2019 02:45:29 -0500 Subject: [PATCH 073/132] replaced user.profile.activeIncidents with user.activeIncidents() method call --- imports/api/OCEManager/OCEs/methods.js | 18 ------------- .../OCEManager/OCEs/server/publications.js | 4 +-- imports/api/OCEManager/progressor.tests.js | 2 +- .../populateDatabase.js | 1 - .../server/executor.js | 2 +- .../server/identifier.js | 10 ++----- imports/api/Testing/endToEndSimple.test.js | 14 ++++------ imports/api/UserMonitor/locations/methods.js | 9 ------- imports/api/UserMonitor/users/methods.js | 26 ------------------- imports/startup/accounts_config.js | 1 - imports/startup/server/fixtures.js | 1 - imports/ui/pages/home.js | 2 +- 12 files changed, 12 insertions(+), 78 deletions(-) diff --git a/imports/api/OCEManager/OCEs/methods.js b/imports/api/OCEManager/OCEs/methods.js index b82fdb96..edf6bd69 100644 --- a/imports/api/OCEManager/OCEs/methods.js +++ b/imports/api/OCEManager/OCEs/methods.js @@ -373,24 +373,6 @@ export const updateRunningIncident = (incident) => { // FIXME(rlouie): not accessing old need names here, so another function has to do this manually on submissions }); - // clear the user activeIncidents before clearing the availabilities - let old_needUserMaps = Availability.find({_id: incident._id}).needUserMaps; - _.forEach(old_needUserMaps, (needUserMap) => { - if (needUserMap.users.length > 0) { - _.forEach(needUserMap.users, (uid) => { - Meteor.users.update( - { - _id: uid - }, { - $pull: { - "profile.activeIncidents": incident._id - } - }); - // TODO(rlouie): maybe store activeIncidentNeedPlaceDistance info, so then pull incidents/needs like this too - }); - } - }); - Availability.update( { _id: incident._id, diff --git a/imports/api/OCEManager/OCEs/server/publications.js b/imports/api/OCEManager/OCEs/server/publications.js index f849cb82..71c3dbc2 100644 --- a/imports/api/OCEManager/OCEs/server/publications.js +++ b/imports/api/OCEManager/OCEs/server/publications.js @@ -18,7 +18,7 @@ Meteor.publish('experiences.activeUser', function () { const user = Meteor.users.findOne(this.userId); let experienceIds = Incidents.find({ - _id: { $in: user.profile.activeIncidents } + _id: { $in: user.activeIncidents() } }).fetch().map((x) => { return x.eid }); @@ -84,7 +84,7 @@ Meteor.publish('incidents.activeUser', function () { } else { const user = Meteor.users.findOne(this.userId); return Incidents.find({ - _id: { $in: user.profile.activeIncidents } + _id: { $in: user.activeIncidents() } }); } }); diff --git a/imports/api/OCEManager/progressor.tests.js b/imports/api/OCEManager/progressor.tests.js index 8d98fdfc..19b63ace 100644 --- a/imports/api/OCEManager/progressor.tests.js +++ b/imports/api/OCEManager/progressor.tests.js @@ -83,7 +83,7 @@ describe('Progressor Tests - Single Submission', function() { it('should remove the incident from active incidents in users profile', function() { const user = Meteor.users.findOne({_id: submissionObject.uid}); - chai.assert.isFalse(user.profile.activeIncidents.includes(submissionObject.iid), + chai.assert.isFalse(user.activeIncidents().includes(submissionObject.iid), 'active incident not removed from user profile'); }); diff --git a/imports/api/OpportunisticCoordinator/populateDatabase.js b/imports/api/OpportunisticCoordinator/populateDatabase.js index d45bbdbe..feadf7e7 100644 --- a/imports/api/OpportunisticCoordinator/populateDatabase.js +++ b/imports/api/OpportunisticCoordinator/populateDatabase.js @@ -15,7 +15,6 @@ export const insertTestUser = (username) => { user.profile.lastParticipated = null; user.profile.lastNotified = null; user.profile.pastIncidents = []; - user.profile.activeIncidents = []; user.profile.staticAffordances = user.profile.staticAffordances || {}; Meteor.users.insert(user); }; diff --git a/imports/api/OpportunisticCoordinator/server/executor.js b/imports/api/OpportunisticCoordinator/server/executor.js index bc43d82d..1a16f03c 100644 --- a/imports/api/OpportunisticCoordinator/server/executor.js +++ b/imports/api/OpportunisticCoordinator/server/executor.js @@ -63,7 +63,7 @@ export const runNeedsWithThresholdMet = (incidentsWithUsersToRun) => { } let newUsersMeta = usersMeta.filter(function(userMeta) { - return !Meteor.users.findOne(userMeta.uid).profile.activeIncidents.includes(iid); + return !Meteor.users.findOne(userMeta.uid).activeIncidents().includes(iid); }); //administrative updates diff --git a/imports/api/OpportunisticCoordinator/server/identifier.js b/imports/api/OpportunisticCoordinator/server/identifier.js index 7b685cea..c1c0eac1 100644 --- a/imports/api/OpportunisticCoordinator/server/identifier.js +++ b/imports/api/OpportunisticCoordinator/server/identifier.js @@ -8,11 +8,7 @@ import {Locations} from "../../UserMonitor/locations/locations"; import {numUnfinishedNeeds} from "../../OCEManager/progressor"; import {addEmptySubmissionsForNeed} from "../../OCEManager/OCEs/methods.js"; -import { - _addActiveIncidentToUser, - _removeActiveIncidentFromUser, - _removeIncidentFromUserEntirely -} from "../../UserMonitor/users/methods"; +import {_removeActiveIncidentFromUser} from "../../UserMonitor/users/methods"; import {doesUserMatchNeed, getNeedDelay} from "../../OCEManager/OCEs/methods"; import {log, serverLog} from "../../logs"; import {flattenAffordanceDict} from "../../UserMonitor/detectors/methods"; @@ -214,7 +210,7 @@ let decommissionIfSustained = (userId, incidentId, needName, decommissionDelay) log.warning(`No user exists for uid = ${userId}`); return; } - let activeIncidents = user.profile.activeIncidents; + let activeIncidents = user.activeIncidents(); if (!activeIncidents.includes(incidentId)) { log.info(`No need to decommission { uid: ${userId} } from { iid: ${incidentId} }`); return; @@ -262,7 +258,6 @@ let decommissionIfSustained = (userId, incidentId, needName, decommissionDelay) */ export const adminUpdatesForAddingUserToIncident = (uid, iid, needName) => { _addUserToAssignmentDb(uid, iid, needName); - _addActiveIncidentToUser(uid, iid); // TODO(rlouie): add extra incident/need/place/distance info // _addActiveIncidentNeedPlaceDistanceToUsers(uid, incidentNeedPlaceDistance); @@ -302,7 +297,6 @@ export const adminUpdatesForRemovingUserToIncident = (uid, iid, needName) => { export const adminUpdatesForRemovingUserToIncidentEntirely = (uid, iid, needName) => { //TODO: make this function take a single user not an array _removeUserFromAssignmentDb(uid, iid, needName); - _removeIncidentFromUserEntirely(uid, iid); }; /** diff --git a/imports/api/Testing/endToEndSimple.test.js b/imports/api/Testing/endToEndSimple.test.js index de6fca16..62f64419 100644 --- a/imports/api/Testing/endToEndSimple.test.js +++ b/imports/api/Testing/endToEndSimple.test.js @@ -64,9 +64,8 @@ describe('Simple End To End', function () { let iid = incident._id; let user = findUserByUsername(USERNAME); - console.log('user.profile.activeIncidents', user.profile.activeIncidents); //user has incident as an active incident - chai.assert(user.profile.activeIncidents.includes(iid), 'active incident not added to user profile'); + chai.assert(user.activeIncidents().includes(iid), 'active incident not added to user profile'); //assignments has user assigned let assignmentEntry = Assignments.findOne({ _id: iid }); @@ -115,9 +114,8 @@ describe('Simple End To End', function () { let iid = incident._id; let user = findUserByUsername(USERNAME); - console.log('user.profile.activeIncidents', user.profile.activeIncidents); //user has incident as an active incident - chai.assert(user.profile.activeIncidents.includes(iid), 'decommissioned prematurely - active incident not added to user profile'); + chai.assert(user.activeIncidents().includes(iid), 'decommissioned prematurely - active incident not added to user profile'); //assignments has user assigned let assignmentEntry = Assignments.findOne({ _id: iid }); @@ -148,9 +146,8 @@ describe('Simple End To End', function () { let iid = incident._id; let user = findUserByUsername(USERNAME); - console.log('user.profile.activeIncidents', user.profile.activeIncidents); //user has incident as an active incident - chai.assert(user.profile.activeIncidents.includes(iid), 'remain assigned while back in vicinity -- active incident not added to user profile'); + chai.assert(user.activeIncidents().includes(iid), 'remain assigned while back in vicinity -- active incident not added to user profile'); //assignments has user assigned let assignmentEntry = Assignments.findOne({ _id: iid }); @@ -191,7 +188,7 @@ describe('Simple End To End', function () { Meteor.setTimeout(function () { try { let user = findUserByUsername(USERNAME); - chai.assert.isFalse(user.profile.activeIncidents.includes(iid), 'active incident not removed from user profile'); + chai.assert.isFalse(user.activeIncidents().includes(iid), 'active incident not removed from user profile'); chai.assert(user.profile.pastIncidents.includes(iid), 'past incident not added to user profile'); done(); } catch (err) { done(err); } @@ -231,9 +228,8 @@ describe('Simple End To End', function () { let iid = incident._id; let user = findUserByUsername(USERNAME); - console.log('user.profile.activeIncidents', user.profile.activeIncidents); //user has incident as an active incident - chai.assert(user.profile.activeIncidents.includes(iid), 'active incident not added to user profile'); + chai.assert(user.activeIncidents().includes(iid), 'active incident not added to user profile'); //assignments has user assigned let assignmentEntry = Assignments.findOne({ _id: iid }); diff --git a/imports/api/UserMonitor/locations/methods.js b/imports/api/UserMonitor/locations/methods.js index 109a6f47..b0e5dba2 100644 --- a/imports/api/UserMonitor/locations/methods.js +++ b/imports/api/UserMonitor/locations/methods.js @@ -208,15 +208,6 @@ export const userNotifiedTooRecently = (user) => { return (now - lastNotified) < waitTimeAfterNotified; }; -/** - * Checks if user has an active incident, meaning they were assigned to an incident - * - * @param user {Object} has Meteor.users Schema - * @return {boolean} whether user is currently assigned to an experience or not - */ -export const userIsAssignedAlready = (user) => { - return user.profile.activeIncidents.length > 0; -}; /** * Computes distance between a start and end location in meters using the haversine forumla. diff --git a/imports/api/UserMonitor/users/methods.js b/imports/api/UserMonitor/users/methods.js index 9566f3cd..94fb13d7 100644 --- a/imports/api/UserMonitor/users/methods.js +++ b/imports/api/UserMonitor/users/methods.js @@ -29,23 +29,10 @@ export const findUserByUsername = function (username) { return Meteor.users.findOne({ 'username': username }); }; -export const _addActiveIncidentToUser = function (uid, iid) { - Meteor.users.update({ - _id: uid - }, { - $addToSet: { - 'profile.activeIncidents': iid - } - }); -}; - export const _removeActiveIncidentFromUser = function (uid, iid) { Meteor.users.update({ _id: uid }, { - $pull: { - 'profile.activeIncidents': iid - }, $addToSet: { 'profile.pastIncidents': iid } @@ -54,19 +41,6 @@ export const _removeActiveIncidentFromUser = function (uid, iid) { // TODO(rlouie): remove the active incident/need/place/dist info too }; -export const _removeIncidentFromUserEntirely = function (uid, iid) { - Meteor.users.update({ - _id: uid - }, { - $pull: { - 'profile.activeIncidents': iid - } - }); - - // TODO(rlouie): remove the active incident/need/place/dist info too -}; - - export const getEmails = new ValidatedMethod({ name: 'users.getEmails', validate: new SimpleSchema({ diff --git a/imports/startup/accounts_config.js b/imports/startup/accounts_config.js index fbadd801..dffe11df 100644 --- a/imports/startup/accounts_config.js +++ b/imports/startup/accounts_config.js @@ -41,7 +41,6 @@ AccountsTemplates.configure({ info.profile.lastParticipated = null; info.profile.lastNotified = null; info.profile.pastIncidents = []; - info.profile.activeIncidents = []; info.profile.staticAffordances = {}; }, }); diff --git a/imports/startup/server/fixtures.js b/imports/startup/server/fixtures.js index b9dee2b9..bc81ce64 100644 --- a/imports/startup/server/fixtures.js +++ b/imports/startup/server/fixtures.js @@ -140,7 +140,6 @@ function createTestData(){ "profile.lastParticipated": null, "profile.lastNotified": null, "profile.pastIncidents": [], - "profile.activeIncidents": [], "profile.staticAffordances": {} } }, { diff --git a/imports/ui/pages/home.js b/imports/ui/pages/home.js index 5942ebad..deb378e2 100644 --- a/imports/ui/pages/home.js +++ b/imports/ui/pages/home.js @@ -79,7 +79,7 @@ Template.home.helpers({ } }, noActiveIncidents() { - let currActiveIncidents = Meteor.users.findOne(Meteor.userId()).profile.activeIncidents; + let currActiveIncidents = Meteor.users.findOne(Meteor.userId()).activeIncidents(); return currActiveIncidents === null || currActiveIncidents.length === 0; }, getCurrentExperience(iid) { From b2bb235f86e0f41e7c187dca09941b97c64c2cae Mon Sep 17 00:00:00 2001 From: youralien Date: Sat, 18 May 2019 03:47:24 -0500 Subject: [PATCH 074/132] use getUserActiveIncidents in home blaze template Summary: trying to call user.activeIncidents() was erroring in the template helper function noActiveIncidents. Tests: Tried loading home page both on first login, and after participating in an experience. No errors thrown, and views load as expected. --- imports/api/UserMonitor/users/methods.js | 7 ++++--- imports/ui/pages/home.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/imports/api/UserMonitor/users/methods.js b/imports/api/UserMonitor/users/methods.js index 94fb13d7..ca566c78 100644 --- a/imports/api/UserMonitor/users/methods.js +++ b/imports/api/UserMonitor/users/methods.js @@ -6,7 +6,8 @@ import { Assignments } from "../../OpportunisticCoordinator/databaseHelpers"; // via dburles:collection-helpers Meteor.users.helpers({ /** - * usage: Meteor.users.findOne().activeIncidents() + * usage in Meteor client/server code: Meteor.users.findOne().activeIncidents() + * usage in Meteor Blaze template JS code: N/A -- had difficulty doing this * * @returns: activeIncidents {Array} array of incident iids e.g. [iid1, iid2] */ @@ -19,10 +20,10 @@ Meteor.users.helpers({ * activeIncidents are the ones in which a user is assigned. * * @param uid - * @return activeIncidents {Array} array of incident iids e.g. [iid1, iid2] + * @return activeIncidents {Array} array of incident iids e.g. [iid1, iid2] or empty array [] */ export const getUserActiveIncidents = (uid) => { - return Assignments.find({"needUserMaps.users.uid": uid}, {fields: {_id: 1}}).map(doc => doc._id); + return Assignments.find({"needUserMaps.users.uid": uid}, {fields: {_id: 1}}).fetch().map(doc => doc._id); }; export const findUserByUsername = function (username) { diff --git a/imports/ui/pages/home.js b/imports/ui/pages/home.js index deb378e2..d667fdc3 100644 --- a/imports/ui/pages/home.js +++ b/imports/ui/pages/home.js @@ -11,6 +11,7 @@ import { Assignments } from '../../api/OpportunisticCoordinator/databaseHelpers' import '../components/active_experience.js'; import {needAggregator} from "../../api/OpportunisticCoordinator/strategizer"; import {setIntersection} from "../../api/custom/arrayHelpers"; +import {getUserActiveIncidents} from "../../api/UserMonitor/users/methods"; Template.home.onCreated(function () { this.state = new ReactiveDict(); @@ -79,8 +80,14 @@ Template.home.helpers({ } }, noActiveIncidents() { - let currActiveIncidents = Meteor.users.findOne(Meteor.userId()).activeIncidents(); - return currActiveIncidents === null || currActiveIncidents.length === 0; + let user = Meteor.users.findOne(Meteor.userId()); + + // note(rlouie): was forced to use getUserActiveIncidents rather than user.activeIncidents collection helper + let currActiveIncidents = getUserActiveIncidents(user._id); + + return (typeof currActiveIncidents === 'undefined' || + currActiveIncidents === null || + currActiveIncidents.length === 0); }, getCurrentExperience(iid) { Template.instance().state.get('render'); From 19620da82eb14e98278fe00614e20b6c26898d76 Mon Sep 17 00:00:00 2001 From: Navin Gopaul Date: Sat, 18 May 2019 20:38:01 -0500 Subject: [PATCH 075/132] continued work on bumpedThree static affordances --- imports/api/Testing/testingconstants.js | 166 ++++++++++-------------- imports/startup/server/fixtures.js | 11 +- simulatelocations.py | 3 - 3 files changed, 75 insertions(+), 105 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 2dd566c4..39e628ea 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -8,6 +8,7 @@ import {notify, notifyUsersInIncident, notifyUsersInNeed} from "../Opportunistic import {Incidents} from "../OCEManager/OCEs/experiences"; import {Schema} from "../schema"; import {serverLog} from "../logs"; +import { log } from "util"; let LOCATIONS = { 'park': { @@ -741,9 +742,12 @@ let DETECTORS = { export const getDetectorId = (detector) => { let db_detector = Detectors.findOne({description: detector.description}); if (db_detector) { - console.log(JSON.stringify(db_detector)); + console.log('getting db detector for', detector.description, 'which is', db_detector._id); + console.log(db_detector) return db_detector._id; } else { + console.log('getting detector for', detector.description, 'which is', detector._id); + return detector._id; } }; @@ -758,10 +762,6 @@ Meteor.methods({ throw new Meteor.Error('getDetectorId.keynotfound', `Detector by the name '${name}' was not found in CONSTANTS.DETECTORS`); } - - console.log('CONSTANTS.DETECTORS: ' + CONSTANTS.DETECTORS[name]._id); - console.log('db.detectors preferably: ' + getDetectorId(CONSTANTS.DETECTORS[name])) - } }); @@ -1011,8 +1011,6 @@ function createBumped() { }; let bumpedCallback = function (sub) { - console.log("calling the bumped callback!!!"); - let otherSub = Submissions.findOne({ uid: { $ne: sub.uid @@ -1112,8 +1110,6 @@ const createHalfHalf = function( let completedCallback = function(sub) { - console.log("Another pair of halves completed a photo"); - let submissions = Submissions.find({ iid: sub.iid, needName: sub.needName @@ -1165,20 +1161,19 @@ const createHalfHalf = function( }; const createBumpedThree = function() { + // console.log(DETECTORS); const bumpedThreeCallback = function (sub) { - console.log("A bumpedThree experience completed!"); - let submissions = Submissions.find({ iid: sub.iid, needName: sub.needName }).fetch(); - + let participants = submissions.map((submission) => { return submission.uid; }); - + notify(participants, sub.iid, 'See images from your group bumped experience!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); - + } - + let experience = { name: 'Group Bumped', participateTemplate: 'bumpedThree', @@ -1187,7 +1182,7 @@ const createBumpedThree = function() { description: 'Share your experience with your friend and their friend!', notificationText: 'Share your experience with your friend and their friend!', callbacks: [{ - trigger: `cb.numberOfSubmissions() === 3`, // ? + trigger: `cb.numberOfSubmissions() === 3`, function: bumpedThreeCallback.toString(), }] }; @@ -1195,89 +1190,48 @@ const createBumpedThree = function() { const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; const places = [ - // ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], + ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], ]; - - const needs = places.map(place => { - const [detectorName, situationDescription, instruction] = place; - return { - needName: `Bumped Three`, - situation: { - detector: getDetectorId(DETECTORS[detectorName]), - number: '1' - }, - toPass: { - situationDescription: `Having a good time ${situationDescription}?`, - instruction: `${instruction}` - }, - numberNeeded: 3, - // notificationDelay: 90 uncomment for testing - } - }); - - console.log(needs); - - staticAffordances.forEach(triad => { - // experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, needs)]; - addStaticAffordanceToNeeds(triad, [ { needName: 'Bumped Three', - situation: { detector: getDetectorId(DETECTORS['coffee']), number: '1' }, - toPass: - { situationDescription: 'Having a good time at a coffee shop?', - instruction: 'Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)' }, - numberNeeded: 3 }, - { needName: 'Bumped Three', - situation: { detector: getDetectorId(DETECTORS['daytime']), number: '1' }, - toPass: - { situationDescription: 'Having a good time today?', - instruction: 'Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel.' }, - numberNeeded: 3 } ]) - }); - - // _.forEach(triads, (triad) => { - // _.forEach(places, (place) => { - // const [detectorName, situationDescription, instruction] = place; - // let newVars = JSON.parse(JSON.stringify(DETECTORS[detectorName]['variables'])); - // newVars.push(`var ${triad};`); - - // let newRules = JSON.parse(JSON.stringify(DETECTORS[detectorName]['rules'])); - // let lastRule = newRules.pop(); - // let lastRuleNoSemicolon = lastRule.split(';')[0]; - // lastRule = `(${triad} && (${lastRuleNoSemicolon}));`; - // newRules.push(lastRule); - - - // let detector = { - // '_id': Random.id(), // use addStaticAffordanceToNeed function and getdetectorid instead of random , use halfhalfsunny as an example - // 'description': DETECTORS[detectorName].description + triad, - // 'variables': newVars, - // 'rules': newRules - // }; - // DETECTORS[detectorName + triad] = detector; - - // const need = { - // needName: `bumped three: ${detectorName} ${triad}`, - // situation: { - // detector: detector._id, + // const needs = places.map(place => { + // const [detectorName, situationDescription, instruction] = place; + // return { + // needName: `Bumped Three ${detectorName}`, + // situation: { + // detector: getDetectorId(DETECTORS[detectorName]), // number: '1' - // }, - // toPass: { - // situationDescription: `Having a good time ${situationDescription}?`, - // instruction: `${instruction}` - // }, - // numberNeeded: 3, - // // notificationDelay: 90 uncomment for testing - // }; - // let callback = { - // trigger: `cb.numberOfSubmissions("${need.needName}") === 3`, - // function: bumpedThreeCallback.toString(), - // }; - // experience.contributionTypes.push(need); - // experience.callbacks.push(callback) - // }); + // }, + // toPass: { + // situationDescription: `Having a good time ${situationDescription}?`, + // instruction: `${instruction}` + // }, + // numberNeeded: 3, + // // notificationDelay: 90 uncomment for testing + // } // }); - console.log(experience); + + + staticAffordances.forEach(triad => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, ((places) => + places.map(place => { + const [detectorName, situationDescription, instruction] = place; + return { + needName: `Bumped Three ${detectorName}`, + situation: { + detector: getDetectorId(DETECTORS[detectorName]), + number: 1 + }, + toPass: { + situationDescription: `Having a good time ${situationDescription}?`, + instruction: `${instruction}` + }, + numberNeeded: 3, + // notificationDelay: 90 uncomment for testing + } + }) + )(places))]; + }); return experience; } @@ -1797,6 +1751,7 @@ const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { 'rules': newRules }; } + console.log( DETECTORS[newDetectorKey].description, DETECTORS[newDetectorKey]._id); return newDetectorKey; }; @@ -1809,13 +1764,17 @@ const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { */ const addStaticAffordanceToNeeds = function(staticAffordance, contributionTypes) { return _.map(contributionTypes, (need) => { - console.log(need.situation.detector); - const detectorKey = _.keys(DETECTORS).find(key => DETECTORS[key]._id === need.situation.detector); - if (!detectorKey) { - throw `Exception in addStaticAffordanceToNeeds: could not find corresponding detector for ${JSON.stringify(need)}` - } - const newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); - need.situation.detector = getDetectorId(DETECTORS[newDetectorKey]); + let detectorKey; + _.forEach(_.keys(DETECTORS), (key) => { + if (DETECTORS[key]._id === need.situation.detector) { + detectorKey = key; + } + }); + // WILL THROW ERROR if we don't find the matching detector id + + let newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); + need.situation.detector = DETECTORS[newDetectorKey]._id; + console.log('adding to need', newDetectorKey, DETECTORS[newDetectorKey]._id); return need; }); }; @@ -1925,6 +1884,10 @@ const sendNotificationTwoHalvesCompleted = function(sub) { '/apicustomresults/' + sub.iid + '/' + sub.eid); }; +let TRIADIC_EXPERIENCES = { + bumpedThree: createBumpedThree(), +} + let EXPERIENCES = { bumped: createBumped(), bumpedThree: createBumpedThree(), @@ -3149,6 +3112,7 @@ export const CONSTANTS = { // Comment out if you would like to only test specific experiences // 'EXPERIENCES': (({ halfhalfEmbodiedMimicry }) => ({ halfhalfEmbodiedMimicry }))(EXPERIENCES), 'EXPERIENCES': EXPERIENCES, + // 'EXPERIENCES': TRIADIC_EXPERIENCES, 'DETECTORS': DETECTORS }; diff --git a/imports/startup/server/fixtures.js b/imports/startup/server/fixtures.js index 02a8e2fc..60ed8b35 100644 --- a/imports/startup/server/fixtures.js +++ b/imports/startup/server/fixtures.js @@ -39,6 +39,7 @@ Meteor.methods({ Object.values(CONSTANTS.DETECTORS).forEach(function (value) { Detectors.insert(value); }); + log.info(`${CONSTANTS.DETECTORS}`); log.info(`Populated ${ Detectors.find().count() } detectors`); }, startStorytime(){ @@ -173,7 +174,7 @@ function createTestData(){ Meteor.users.update({ _id: {$in: [uid1, uid3, uid5]} }, { - $set: { 'profile.staticAffordances.lovesDTR': true} + $set: { 'profile.staticAffordances.lovesDTR': true } }, { multi: true }); @@ -186,5 +187,13 @@ function createTestData(){ multi: true }); + Meteor.users.update({ + _id: {$in: [uid1, uid2, uid3, uid4, uid5]} + }, { + $set: { 'profile.staticAffordances': { "triadOne": true} } + }, { + multi: true + }); + log.debug('FOR LOCATION TESTING RUN >>>> python simulatelocations.py '+ uid1 + " " + uid2 + " " + uid3+" " + uid4 + " " + uid5 ); } diff --git a/simulatelocations.py b/simulatelocations.py index c278d49b..a9733207 100644 --- a/simulatelocations.py +++ b/simulatelocations.py @@ -302,16 +302,13 @@ def allUsersCoffee(): print("all users at coffee") if __name__ == "__main__": -<<<<<<< HEAD # allUsersGrocery() allUsersCoffee() #allUsersAtPark() # garrettAndMegBump() -======= # single user movement # setLocation(burgers, sys.argv[1]) # allUsersGrocery() #garrettAndMegBump() ->>>>>>> fix-need-blocking #time.sleep(2) #onlyGarretAtLake() From 2fa1bfbfbe848ee3356322db1cb5ae225b53caee Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Sun, 19 May 2019 13:50:29 -0500 Subject: [PATCH 076/132] notes --- imports/api/Testing/testingconstants.js | 31 ++++++++++- imports/api/testing/collective-narrative.js | 5 +- imports/api/testing/conversion.js | 60 +++++++++++++++++++++ imports/api/testing/testingconstants.js | 31 ++++++++++- 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 imports/api/testing/conversion.js diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 3c3ba464..f8b96fd9 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -1841,7 +1841,7 @@ function addActionToItem(item, action) { //action.item = item; } -function writeNarrative() { +function writeNarrative1() { // create setting var Common_Room = new Setting("Common ROOM", "grocery" /* "Common Room", "inside_a_building || in_a_school_building" */); var Bedroom = new Setting("bedroom", "grocery" /* "Bedroom", "inside_a_building || in_a_dorm" */); @@ -1931,7 +1931,34 @@ function writeNarrative() { return chapter_list; } -let test_chapter = writeNarrative()[0]; +function writeNarrative2() { + var shop = new Setting("Weapons Shop", "grocery" /* "Common Room", "inside_a_building || in_a_school_building" */); + + // create character + //var wand = new Object("wand", "hermione", "1"); + //var health = new Object("health", "ron", "2B"); + // name, chapters, objects, actions, contexts, first appearance + var Alan = new Character("Alan", [], {"1" : "grocery"}); + var Billy = new Character("Billy", [], {"1" : "grocery"}); + var Caleb = new Character("Caleb", [], {"1" : "grocery"}); + var Shopkeeper = new Character("Shopkeeper", [], {"1" : "grocery"}); + + + var axe = new Item("axe", true, []) + addItemToCharacter(axe, Shopkeeper); + + + var give_axe= new Action("give axe", [transfer]) + console.log("give_wand.repercussions " + give_wand.repercussions) + var murder = new Action("murder someone", [kill]) + addActionToItem(axe, give_axe); + addActionToItem(axe, murder); + // create chapter + var chapter_one = new Chapter("1", shop, [Alan, Billy, Caleb, Shopkeeper], [axe], "anyCharDead = true;"); + +} + +let test_chapter = writeNarrative2()[0]; function convertChapterToExperience(chapter) { // total list of actions that will be completed in the chapter diff --git a/imports/api/testing/collective-narrative.js b/imports/api/testing/collective-narrative.js index 2b18461d..ee25751a 100644 --- a/imports/api/testing/collective-narrative.js +++ b/imports/api/testing/collective-narrative.js @@ -10,7 +10,8 @@ let money = new Object("money", Alan, true, [buy]) function kill(recipient) { - recipient.status = dead; + recipient.status = false; + anyCharDead = true; } function buy(purchase) { @@ -160,7 +161,7 @@ function convertChapterToExperience(chapter) { notify( uids, sub.iid, - "Chapter 1 is complete. Find out what happened here!", + "Hermione has died, ending this chapter", "", "/apicustomresults/" + sub.iid + "/" + sub.eid ); diff --git a/imports/api/testing/conversion.js b/imports/api/testing/conversion.js new file mode 100644 index 00000000..95f44ab2 --- /dev/null +++ b/imports/api/testing/conversion.js @@ -0,0 +1,60 @@ +//CE has needs +//CN has characters, which each have a context attribute, which is set to needs + +//CE has uids for participants +//CN hs characters, which each have a participant attribute, which is set to a uid or null + +//CE has iids +//CN has stories whose instances have iids + +//CE experiences callback into each other +//CN story chapters callback into each other until the end condition is reached. + +character { + name: string + specific: boolean + /* + if specific == true: + character will be referred to as character.name when referred to in text + else: + character will be referred to by the participant's name + */ + participant: uid //assigned based on context, or manually set by author + context: detector + static affordances + /* + if participant is set manually by the author before the story begins OR recast == false and participant != null (the participant has already been cast at least once) + add the participant to the context so that the system knows to look for that specific person in the context + */ + recast: boolean + /* + if recast == true: + participant is reset to null after the chapter ends + */ +} + +/* +general flow of casting + get affordances from context tracker + match participants to characters who have those contexts + set participant attribute of character through uid + experience runs + if recast is true, reset participant attributes + iid remains the same + restart cycle of context tracker +*/ +IMPORTANT: can characters in the same chapter have different contexts, or do they all need to have the same one? + +//prompts require a response from participants. +prompt { + question: string + response: + string, + picture, + range + //response usually maps to a story variable +} + +text can usually be written as string + var + string + +//getting variable information from detectors +var restaurant = character.participant.situation.detector \ No newline at end of file diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index 3c3ba464..f8b96fd9 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -1841,7 +1841,7 @@ function addActionToItem(item, action) { //action.item = item; } -function writeNarrative() { +function writeNarrative1() { // create setting var Common_Room = new Setting("Common ROOM", "grocery" /* "Common Room", "inside_a_building || in_a_school_building" */); var Bedroom = new Setting("bedroom", "grocery" /* "Bedroom", "inside_a_building || in_a_dorm" */); @@ -1931,7 +1931,34 @@ function writeNarrative() { return chapter_list; } -let test_chapter = writeNarrative()[0]; +function writeNarrative2() { + var shop = new Setting("Weapons Shop", "grocery" /* "Common Room", "inside_a_building || in_a_school_building" */); + + // create character + //var wand = new Object("wand", "hermione", "1"); + //var health = new Object("health", "ron", "2B"); + // name, chapters, objects, actions, contexts, first appearance + var Alan = new Character("Alan", [], {"1" : "grocery"}); + var Billy = new Character("Billy", [], {"1" : "grocery"}); + var Caleb = new Character("Caleb", [], {"1" : "grocery"}); + var Shopkeeper = new Character("Shopkeeper", [], {"1" : "grocery"}); + + + var axe = new Item("axe", true, []) + addItemToCharacter(axe, Shopkeeper); + + + var give_axe= new Action("give axe", [transfer]) + console.log("give_wand.repercussions " + give_wand.repercussions) + var murder = new Action("murder someone", [kill]) + addActionToItem(axe, give_axe); + addActionToItem(axe, murder); + // create chapter + var chapter_one = new Chapter("1", shop, [Alan, Billy, Caleb, Shopkeeper], [axe], "anyCharDead = true;"); + +} + +let test_chapter = writeNarrative2()[0]; function convertChapterToExperience(chapter) { // total list of actions that will be completed in the chapter From 4ac7abddd1d8f6f96789bff45180345b380464ef Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Sun, 19 May 2019 14:48:17 -0500 Subject: [PATCH 077/132] began writing Murder Mystery using pure CE --- imports/api/Testing/testingconstants.js | 84 +++++++++++++++++++++++-- imports/api/testing/testingconstants.js | 84 +++++++++++++++++++++++-- imports/ui/pages/api_custom.html | 36 +++++++++++ 3 files changed, 192 insertions(+), 12 deletions(-) diff --git a/imports/api/Testing/testingconstants.js b/imports/api/Testing/testingconstants.js index 5778552e..3a00bd4d 100644 --- a/imports/api/Testing/testingconstants.js +++ b/imports/api/Testing/testingconstants.js @@ -999,6 +999,79 @@ const createIndependentStorybook = () => { }; }; +const createMurderMystery = function() { + // console.log(DETECTORS); + const MurderMysteryCallback = function (sub) { + let submissions = Submissions.find({ + iid: sub.iid, + needName: sub.needName + }).fetch(); + + let participants = submissions.map((submission) => { return submission.uid; }); + + notify(participants, sub.iid, 'See images from your group bumped experience!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + + } + + let experience = { + name: 'Murder Mystery', + participateTemplate: 'mm', + resultsTemplate: 'mmresults', + contributionTypes: [], + description: "You've been invited to participate in a murder mystery!", + notificationText: "You've been invited to participate in a murder mystery!", + callbacks: [] + }; + + + const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; + const places = [ + ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], + ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], + ]; + + // const needs = places.map(place => { + // const [detectorName, situationDescription, instruction] = place; + // return { + // needName: `Bumped Three ${detectorName}`, + // situation: { + // detector: getDetectorId(DETECTORS[detectorName]), + // number: '1' + // }, + // toPass: { + // situationDescription: `Having a good time ${situationDescription}?`, + // instruction: `${instruction}` + // }, + // numberNeeded: 3, + // // notificationDelay: 90 uncomment for testing + // } + // }); + + + staticAffordances.forEach(triad => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, ((places) => + places.map(place => { + const [detectorName, situationDescription, instruction] = place; + return { + needName: `Murder Mystery ${detectorName}`, + situation: { + detector: getDetectorId(DETECTORS[detectorName]), + number: 1 + }, + toPass: { + situationDescription: `Having a good time ${situationDescription}?`, + instruction: `${instruction}` + }, + numberNeeded: 3, + // notificationDelay: 90 uncomment for testing + } + }) + )(places))]; + }); + + return experience; +} + function createBumped() { let experience = { name: 'Bumped', @@ -1176,7 +1249,7 @@ const createBumpedThree = function() { let experience = { name: 'Group Bumped', - participateTemplate: 'bumpedThree', + participateTemplate: 'bumpedThreeInitial', resultsTemplate: 'bumpedThreeResults', contributionTypes: [], description: 'Share your experience with your friend and their friend!', @@ -1188,10 +1261,9 @@ const createBumpedThree = function() { }; - const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; + const staticAffordances = ['participantOne', 'participantTwo', 'participantThree']; const places = [ - ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], - ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], + ["coffee", "at a coffee shop", "Please help us build the story by answering some initial questions about your situation!"], ]; // const needs = places.map(place => { @@ -1212,8 +1284,8 @@ const createBumpedThree = function() { // }); - staticAffordances.forEach(triad => { - experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, ((places) => + staticAffordances.forEach(participant => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(participant, ((places) => places.map(place => { const [detectorName, situationDescription, instruction] = place; return { diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js index 5778552e..3a00bd4d 100644 --- a/imports/api/testing/testingconstants.js +++ b/imports/api/testing/testingconstants.js @@ -999,6 +999,79 @@ const createIndependentStorybook = () => { }; }; +const createMurderMystery = function() { + // console.log(DETECTORS); + const MurderMysteryCallback = function (sub) { + let submissions = Submissions.find({ + iid: sub.iid, + needName: sub.needName + }).fetch(); + + let participants = submissions.map((submission) => { return submission.uid; }); + + notify(participants, sub.iid, 'See images from your group bumped experience!', '', '/apicustomresults/' + sub.iid + '/' + sub.eid); + + } + + let experience = { + name: 'Murder Mystery', + participateTemplate: 'mm', + resultsTemplate: 'mmresults', + contributionTypes: [], + description: "You've been invited to participate in a murder mystery!", + notificationText: "You've been invited to participate in a murder mystery!", + callbacks: [] + }; + + + const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; + const places = [ + ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], + ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], + ]; + + // const needs = places.map(place => { + // const [detectorName, situationDescription, instruction] = place; + // return { + // needName: `Bumped Three ${detectorName}`, + // situation: { + // detector: getDetectorId(DETECTORS[detectorName]), + // number: '1' + // }, + // toPass: { + // situationDescription: `Having a good time ${situationDescription}?`, + // instruction: `${instruction}` + // }, + // numberNeeded: 3, + // // notificationDelay: 90 uncomment for testing + // } + // }); + + + staticAffordances.forEach(triad => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, ((places) => + places.map(place => { + const [detectorName, situationDescription, instruction] = place; + return { + needName: `Murder Mystery ${detectorName}`, + situation: { + detector: getDetectorId(DETECTORS[detectorName]), + number: 1 + }, + toPass: { + situationDescription: `Having a good time ${situationDescription}?`, + instruction: `${instruction}` + }, + numberNeeded: 3, + // notificationDelay: 90 uncomment for testing + } + }) + )(places))]; + }); + + return experience; +} + function createBumped() { let experience = { name: 'Bumped', @@ -1176,7 +1249,7 @@ const createBumpedThree = function() { let experience = { name: 'Group Bumped', - participateTemplate: 'bumpedThree', + participateTemplate: 'bumpedThreeInitial', resultsTemplate: 'bumpedThreeResults', contributionTypes: [], description: 'Share your experience with your friend and their friend!', @@ -1188,10 +1261,9 @@ const createBumpedThree = function() { }; - const staticAffordances = ['triadOne', 'triadTwo', 'triadThree']; + const staticAffordances = ['participantOne', 'participantTwo', 'participantThree']; const places = [ - ["coffee", "at a coffee shop", "Send a picture of your drink and add some caption about it! (Why you ordered it, why you like it, etc.)"], - ["daytime", "today", "Sometimes, the weather affects our mood! Take a picture showing the weather and add a caption about how it makes you feel."], + ["coffee", "at a coffee shop", "Please help us build the story by answering some initial questions about your situation!"], ]; // const needs = places.map(place => { @@ -1212,8 +1284,8 @@ const createBumpedThree = function() { // }); - staticAffordances.forEach(triad => { - experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(triad, ((places) => + staticAffordances.forEach(participant => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(participant, ((places) => places.map(place => { const [detectorName, situationDescription, instruction] = place; return { diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index 33ac52d3..81a27b9a 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -44,6 +44,42 @@ + + + - - @@ -536,11 +536,11 @@

Step 2

Help build a rainbow collage

Take a picture of something {{this.contributionTemplate.name}} so we can showcase all the colors.

-
-
- {{> Template.dynamic template="camera" data=(passContributionName this.situationNeedName)}} -
@@ -551,63 +551,63 @@

Help build a rainbow collage

- - + + - - - + + + {{/if}} + - - - - - - + + +
--> + diff --git a/imports/ui/pages/oldtc.js b/imports/ui/pages/oldtc.js new file mode 100644 index 00000000..27e8a010 --- /dev/null +++ b/imports/ui/pages/oldtc.js @@ -0,0 +1,572 @@ +import { Meteor } from "meteor/meteor"; + +import { Submissions } from "../OCEManager/currentNeeds"; + +import { addContribution } from '../OCEManager/OCEs/methods'; +import {Detectors} from "../UserMonitor/detectors/detectors"; +import {notify, notifyUsersInIncident, notifyUsersInNeed} from "../OpportunisticCoordinator/server/noticationMethods"; +import {Incidents} from "../OCEManager/OCEs/experiences"; +import {Schema} from "../schema"; +import {serverLog} from "../logs"; +import { log } from "util"; +import { Messages } from '../../api/messages/messages.js'; + +let LOCATIONS = { + 'park': { + lat: 42.056838, + lng: -87.675940 + }, + 'lakefill': { + lat: 42.054902, + lng: -87.670197 + }, + 'burgers': { + lat: 42.046131, + lng: -87.681559 + }, + 'grocery': { + lat: 42.047621, + lng: -87.679488 + }, + 'sushi': { // sashimi sashimi near whole foods + lat: 42.048068, + lng: -87.6811262 + }, + 'grocery2': { + lat: 42.039818, + lng: -87.680088 + } +}; + +let USERS = { + meg: { + username: 'meg', + email: 'meg@email.com', + password: 'password', + profile: { + firstName: 'Meg', + lastName: 'Grasse' + } + }, + andrew: { + username: 'andrew', + email: 'andrew@email.com', + password: 'password', + profile: { + firstName: 'Andrew', + lastName: 'Finke' + } + }, + josh: { + username: 'josh', + email: 'josh@email.com', + password: 'password', + profile: { + firstName: 'Josh', + lastName: 'Shi' + } + } +}; + +let DETECTORS = { + night: { + _id: 'Wth3TB9Lcf6me6vgy', + description: 'places where it\'s nighttime,', + variables: ['var nighttime;'], + rules: ['(nighttime);'] + }, + daytime: { + _id: 'tyZMZvPKkkSPR4FpG', + description: 'places where it\'s daytime,', + variables: ['var daytime;'], + rules: ['daytime;'] + }, + coffee: { + _id: 'saxQsfSaBiHHoSEYK', + description: 'coffee', + variables: ['var coffeeroasteries;', + 'var coffee;', + 'var cafes;', + 'var coffeeshops;', + 'var coffeeteasupplies;' + ], + rules: ['(coffeeroasteries || coffee) || ((coffeeshops || coffeeteasupplies) || cafes)'] + }, + busy: { + _id: 'saxQsfSaBiHHoSEZX', + description: 'user reports to be busy', + variables: ['var busy;'], + rules: ['busy;'] + }, + train: { + _id: '2wH5bFr77ceho5BgF', + description: 'trains', + variables: ['var publictransport;', 'var trainstations;', 'var trains;'], + rules: ['(trainstations || trains) || publictransport;'] + }, + rainy: { + _id: 'puLHKiGkLCJWpKc62', + description: 'rainy', + variables: ['var rain;'], + rules: ['(rain);'] + }, + sunny: { + _id: '6vyrBtdDAyRArMasj', + description: 'clear', + variables: ['var clear;', 'var daytime;'], + rules: ['(clear && daytime);'] + }, + cloudy: { + _id: 'sorCvK53fyi5orAmj', + description: 'clouds', + variables: ['var clouds;', 'var daytime;'], + rules: ['(clouds && daytime);'] + }, + hour0: { + _id: "v2ANTJr1I7wle3Ek8", + description: "during 00:00", + variables: ["var hour;"], + rules: ["hour == 0"] + } +}; + +export const getDetectorId = (detector) => { + let db_detector = Detectors.findOne({description: detector.description}); + if (db_detector) { + console.log('getting db detector for', detector.description, 'which is', db_detector._id); + console.log(db_detector) + return db_detector._id; + } else { + console.log('getting detector for', detector.description, 'which is', detector._id); + + return detector._id; + } +}; + +Meteor.methods({ + getDetectorId({name}) { + new SimpleSchema({ + name: { type: String } + }).validate({name}); + + if (!(name in CONSTANTS.DETECTORS)) { + throw new Meteor.Error('getDetectorId.keynotfound', + `Detector by the name '${name}' was not found in CONSTANTS.DETECTORS`); + } + } +}); + +// this is the code that will eventually be generated by the CN compiler! +const createMurderMystery = function() { + let values = [ + 'not busy at all', + 'a little busy', + 'somewhat busy', + 'pretty busy', + 'very busy' + ]; + // console.log(DETECTORS); + /* + let places = ["coffee", "coffee", "coffee", "coffee", "coffee",]; + let detectorIds = places.map((x) => { return Random.id(); }); + let detectorNames = []; + */ + let dropdownText = [ + 'not busy at all', + 'a little busy', + 'somewhat busy', + 'pretty busy', + 'busy' + ]; + /* + let questions = [ + "How busy is the coffee shop right now?", + "Name an interesting option on the menu", + "What did you order?" + ] + */ + let question1 = "How busy is the coffee shop right now?" + let question2 = "Name an interesting option on the menu" + let question3 = "What did you order?" + + let DROPDOWN_OPTIONS = _.zip(dropdownText); + + const MurderMysteryCallback = function (sub) { + let submissions = Submissions.find({ + iid: sub.iid, + needName: sub.needName + }).fetch(); + + console.log("in callback") + console.log(submissions.length) + + experience = Experiences.update({ + "_id": sub.eid + }, { + "$set": { + "participateTemplate": "murderMysteryChat" + } + }) + + + + for (let i = 0; i < submissions.length; i++) { + console.log("busyness: " + submissions[i].content.busy) + var key = submissions[i].content.busy; + var affordances = {} + affordances[key] = true; + console.log("staticAffordances: " + affordances.key) + Meteor.users.update({ + _id: submissions[i].uid + }, { + $set: { + ['profile.staticAffordances.' + key]: true + + } + }); + + //find instance of CN that the submission came from + let instance = Incidents.findOne(submissions[i].iid); + console.log("detector ID: " + instance.contributionTypes[0].situation.detector) + let detector_id = instance.contributionTypes[0].situation.detector + console.log("detector rules: " + Detectors.findOne(detector_id).rules) + let rules = Detectors.findOne(detector_id).rules; + + let participant = Meteor.users.findOne(submissions[i].uid); + + let others = Meteor.users.find({ + "profile.pastIncidents": submissions[i].iid + }).fetch() + + let other_participants = [] + + console.log("others length: " + others.length) + + for (let i = 0; i < others.length; i++) { + let other = others[i] + console.log("other: " + other.profile.firstName) + console.log("participant: " + participant.profile.firstName) + //in the future, need to account for when participants have the same first name (use UID instead) + if (other.profile.firstName != participant.profile.firstName) { + other_participants.push(other.profile.firstName) + console.log("other: " + other.profile.firstName + " " + other_participants.length) + } + } + + console.log("participants: " + participant); + + //need to figure out a way to get other users in same experience + //let others = "" + + //check to see how busy the user is + let character = [] + if (participant.profile.staticAffordances.busy) { + console.log("casting a murderer") + rules = submissions[i].content.busy + " && " + rules + //rules += " && " + submissions[i].content.busy + ";" + //rules = addStaticAffordanceToDetector(submissions[i].content.busy, instance.contributionTypes[0].situation.detector) + character.push([rules, "murderer", "murderMysteryChat", "Try to avoid being caught and weasel your way out of the clues!", participant._id, other_participants]) + } else { + console.log("casting an innocent") + rules = submissions[i].content.busy + " && " + rules + //rules += " && " + submissions[i].content.busy + ";" + //rules = addStaticAffordanceToDetector(submissions[i].content.busy, instance.contributionTypes[0].situation.detector) + character.push([rules, "innocent", "murderMysteryChat", "Try to prove your innocence and find the real murderer!", participant._id, other_participants]) + } + + console.log("character length" + character.length) + + let extraAffordances = [] + + extraAffordances.push(submissions[i].content.busy) + + _.forEach(character, (charac) => { + + let [detectorName, role, template, instruction, user, others] = charac; + + var need = { + needName: "Murder Mystery" + role, + situation: { + detector: detectorName, + number: 2 + }, + participateTemplate: template, + toPass: { + role: role, + instruction: instruction, + user: user, + dropdownChoices: { + name: others, + options: others + } + }, + numberNeeded: 2 + }; + //next_experience.contributionTypes.push(need) + addContribution(sub.iid, need); + console.log("more needs" + instance.contributionTypes.length) + + Meteor.call("sendWhisper", role, user, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + }) + + let info = "We have our first clue. The murderer is in a busy coffee shop!" + + Meteor.setTimeout(function() {Meteor.call("sendPrompt", info, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + })}, 3000) + + let theOrder = ""; + let theMenu = ""; + let theUser = ""; + + if (submissions[i].content.busy == "busy") { + theOrder = submissions[i].content.order; + theMenu = submissions[i].content.menu; + theUser = Meteor.users.findOne(submissions[i].uid).profile.firstName; + } + + let info2 = "Here's the second clue. The murderer ordered " + theOrder + "!"; + + Meteor.setTimeout(function() {Meteor.call("sendPrompt", info2, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + })}, 6000) + + let info3 = "Here's the last clue. The murderer is in a coffee shop that sells " + theMenu + "!"; + + Meteor.setTimeout(function() {Meteor.call("sendPrompt", info3, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + })}, 9000) + + let info4 = "Now it's time to cast your vote! Who do you think the murderer is?"; + + Meteor.setTimeout(function() {Meteor.call("sendPrompt", info4, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + })}, 12000) + + let info5 = "Now that your votes are cast, let's find out who's right. The murderer is " + theUser + "!"; + + Meteor.setTimeout(function() {Meteor.call("sendPrompt", info5, (error, response) => { + if (error) { + alert(error.reason); + } else { + //Cookie.set("name", response.name); + $input.val(""); + } + })}, 15000) + + }); + + } + + //which way to find participants and set this.toPass.characterName? + // let participants = submissions.map((submission) => { return submission.uid; }); + + // let participant = Meteor.users.findOne({ + // "_id": sub.uid, + // }) + + + + // let max = submissions[0] + + // for (let i = 0; i < submissions.length; i++) { + + // if (submissions[i].content.busy >= max.content.busy) { + // max = submissions[i] + // } + // } + + + + } + + let experience = { + name: 'Murder Mystery', + participateTemplate: 'murderMysteryInitial', + resultsTemplate: 'murderMysteryChat', + contributionTypes: [ + ], + description: "You've been invited to participate in a murder mystery!", + notificationText: "You've been invited to participate in a murder mystery!", + callbacks: [{ + trigger: 'cb.newSubmission() && (cb.numberOfSubmissions() == 1)', + // substitute any variables used outside of the callback function scope + function: eval('`' + MurderMysteryCallback.toString() + '`'), + }] + }; + + + const staticAffordances = ['cn']; + const places = [ + ["coffee", "at a coffee shop", "Please help us build the story by answering some initial questions about your situation!"], + ]; + + // const needs = places.map(place => { + // const [detectorName, situationDescription, instruction] = place; + // return { + // needName: `Bumped Three ${detectorName}`, + // situation: { + // detector: getDetectorId(DETECTORS[detectorName]), + // number: '1' + // }, + // toPass: { + // situationDescription: `Having a good time ${situationDescription}?`, + // instruction: `${instruction}` + // }, + // numberNeeded: 3, + // // notificationDelay: 90 uncomment for testing + // } + // }); + + +staticAffordances.forEach(affordance => { + experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(affordance, ((places) => + places.map(place => { + const [detectorName, situationDescription, instruction] = place; + return { + needName: `Murder Mystery ${detectorName}`, + situation: { + detector: getDetectorId(DETECTORS[detectorName]), + number: 3 + }, + participateTemplate: 'murderMysteryInitial', + toPass: { + situationDescription: `Having a good time ${situationDescription}?`, + instruction: `${instruction}`, + question1: `${question1}`, + question2: `${question2}`, + question3: `${question3}`, + dropdownChoices: { + name: values, + options: DROPDOWN_OPTIONS + } + }, + numberNeeded: 3, + // notificationDelay: 90 uncomment for testing + } + }) + )(places))]; +}); + +return experience; +} + + +/** + * Side effect: Changes the global DETECTORS object, adding another detector with key "detectorKey_staticAffordance" + * + * @param staticAffordance + * @param detectorKey + * @returns newDetectorKey + */ + + const addStaticAffordanceToDetector = function(staticAffordance, detectorKey) { + let newVars = JSON.parse(JSON.stringify(DETECTORS[detectorKey]['variables'])); + newVars.push(`var ${staticAffordance};`); + let newRules = JSON.parse(JSON.stringify(DETECTORS[detectorKey]['rules'])); + // modify last detector rule + // when rules has a flat structure where rules.length == 1, last rule is the predicate + // i.e. ['(diners || restaurants || cafeteria || food_court);'] + // when rules have a nested structure where rules.length > 1, last rule is the predicate + // i.e. ['worship_places = (buddhist_temples || churches);', '(worship_places || landmarks);'] + let lastRule = newRules.pop(); + // each rule has a `;` at end, i.e. (rain && park); + // in order to modify the rule, must add predicate preceding the rule + let lastRuleNoSemicolon = lastRule.split(';')[0]; + lastRule = `(${staticAffordance} && (${lastRuleNoSemicolon}));`; + newRules.push(lastRule); + + let newDetectorKey = `${detectorKey}_${staticAffordance}`; + // Change DETECTORS if newDetectorKey does not already exist (some experiences might have already created coffee_mechanismRich, for example) + if (!(newDetectorKey in DETECTORS)) { + DETECTORS[newDetectorKey] = { + '_id': Random.id(), + 'description': `${DETECTORS[detectorKey].description} ${staticAffordance}`, + 'variables': newVars, + 'rules': newRules + }; + } + console.log( DETECTORS[newDetectorKey].description, DETECTORS[newDetectorKey]._id); + return newDetectorKey; +}; + +/** + * + * @param staticAffordances [String] the affordance to add + * i.e. 'mechanismRich' + * @param contributionTypes [Array] list of all the needs by which to modify + * @return + */ + const addStaticAffordanceToNeeds = function(staticAffordance, contributionTypes) { + return _.map(contributionTypes, (need) => { + let detectorKey; + _.forEach(_.keys(DETECTORS), (key) => { + if (DETECTORS[key]._id === need.situation.detector) { + detectorKey = key; + } + }); + // WILL THROW ERROR if we don't find the matching detector id + + let newDetectorKey = addStaticAffordanceToDetector(staticAffordance, detectorKey); + need.situation.detector = DETECTORS[newDetectorKey]._id; + console.log('adding to need', newDetectorKey, DETECTORS[newDetectorKey]._id); + return need; + }); +}; + +/** + * + * @param contributionTypes + * @param triggerTemplate [String] should be written as a string, with ES6 templating syntax + * i.e. "cb.newSubmission(\"${need.needName}\")" + * If using templating syntax, you have access to the each individual need object + * @param sendNotificationFn + */ + const notifCbForMultiNeeds = function(contributionTypes, triggerTemplate, sendNotificationFn) { + return contributionTypes.map((need) => { + return { + trigger: eval('`' + triggerTemplate + '`'), + function: sendNotificationFn.toString() + }; + }); +}; + +let EXPERIENCES = { + murder: createMurderMystery() +}; + +export const CONSTANTS = { + 'LOCATIONS': LOCATIONS, + 'USERS': USERS, + // Comment out if you would like to only test specific experiences + // 'EXPERIENCES': (({ halfhalfEmbodiedMimicry }) => ({ halfhalfEmbodiedMimicry }))(EXPERIENCES), + 'EXPERIENCES': EXPERIENCES, + // 'EXPERIENCES': TRIADIC_EXPERIENCES, + 'DETECTORS': DETECTORS +}; From 6447fedee40e68e3dceb94e757bc077100fa1332 Mon Sep 17 00:00:00 2001 From: Sanfeng Wang Date: Tue, 4 Jun 2019 18:27:56 -0500 Subject: [PATCH 099/132] COMPILER WORKS UP UNTIL CALLBACK WOOOOOOOOO --- imports/ui/components/contributions.html | 4 ++-- imports/ui/pages/api_custom.html | 5 ++--- imports/ui/pages/api_custom.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/imports/ui/components/contributions.html b/imports/ui/components/contributions.html index 35701387..d1815f36 100644 --- a/imports/ui/components/contributions.html +++ b/imports/ui/components/contributions.html @@ -64,8 +64,8 @@ diff --git a/imports/ui/pages/api_custom.html b/imports/ui/pages/api_custom.html index e3081e8e..bd692f0e 100644 --- a/imports/ui/pages/api_custom.html +++ b/imports/ui/pages/api_custom.html @@ -92,9 +92,7 @@ {{#each q in this.toPass.questions}}

{{q.question}}

- {{/each}} - - + -