diff --git a/imports/api/OpportunisticCoordinator/server/strategizer.js b/imports/api/OpportunisticCoordinator/server/strategizer.js index f5bb8c7b..87165e83 100644 --- a/imports/api/OpportunisticCoordinator/server/strategizer.js +++ b/imports/api/OpportunisticCoordinator/server/strategizer.js @@ -6,7 +6,6 @@ import {Assignments} from "../databaseHelpers"; import {getNeedObject} from "./identifier"; import {Experiences} from "../../OCEManager/OCEs/experiences"; import {createIncidentFromExperience, startRunningIncident} from "../../OCEManager/OCEs/methods"; -import {CONSTANTS} from "../../testing/testingconstants"; // import {Meteor} from "meteor/meteor"; import {numberSubmissionsRemaining, usersAlreadyAssignedToNeed, usersAlreadySubmittedToNeed} from "../strategizer"; diff --git a/imports/api/testing/ce_examples.js b/imports/api/Testing/ce_examples.js similarity index 100% rename from imports/api/testing/ce_examples.js rename to imports/api/Testing/ce_examples.js diff --git a/imports/api/Testing/chi20experiences.js b/imports/api/Testing/chi20experiences.js new file mode 100644 index 00000000..dd935421 --- /dev/null +++ b/imports/api/Testing/chi20experiences.js @@ -0,0 +1,358 @@ +import {getDetectorId} from "./testingconstants"; +import { CONSTANTS } from "./testingconstants"; +import {Submissions} from "../OCEManager/currentNeeds"; +import {notify} from "../OpportunisticCoordinator/server/noticationMethods"; +import {Meteor} from "meteor/meteor"; +import {addContribution} from "../OCEManager/OCEs/methods"; + +let DETECTORS = CONSTANTS.DETECTORS; + +// current model +// 0. to create a new experience, we write a fully defined CE API spec as as static definition +// 1. on file load, the detectors and experiences JSON are created [by accessing the current static definition of DETECTORS or the existing Detectors database] +// 2. on updateOCE, we simply access this static definition and make the database reflect this static definition + +// current obstacle with current model +// 0. across different branches, it's hard to manage all the experiences we are creating +// updateOCE finds on experience name, but the experiences for different groups are named the same + +// findings with using current model +// 0. all the linking logic happens at static definition time, in one file +// 0a. this means we need to make sure on file load, all the links are sorted out + +// what if we used factory method pattern? to create objects not on file load, but based on runtime calls +// 0. this is less of a "fixtures" model, but rather building blocks that can be used at run time +// 1. there could be factory methods for creation/updating of OCEs +// 2. definitions for different studies or deployments are activated through runtime e.g., meteor method calls +// 3. that would allow deployments to differ not based on code, but purely on the database? + + + +let CHI20_Olin_EXPERIENCES = { + halfhalf_sunny_knowsOlin: { + _id: Random.id(), + name: 'Hand Silhouette', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Hand Silhouette 1', + situation: { + detector: getDetectorId(DETECTORS.sunny), + number: '1' + }, + toPass: { + instruction: 'Is the <span style="color: #0351ff">weather clear and sunny</span> where you are? Take a photo, <span style="color: #0351ff">holding your hand towards the sky, covering the sun.</span>', + 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', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A hand silhouette was completed','View the photo').toString() + }] + }, + halfhalf_grocery_knowsOlin: { + _id: Random.id(), + name: 'Grocery Buddies', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Grocery Buddies 1', + notificationSubject: 'Inside a grocery store?', + notificationText: 'Share an experience with others who are also grocery shopping', + situation: { + detector: getDetectorId(DETECTORS.grocery), + number: '1' + }, + toPass: { + instruction: 'Are you at the <span style="color: #0351ff">grocery store</span>? Take a photo, <span style="color: #0351ff">holding a fruit or vegetable</span> outstretched with your hands.', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Grocery Buddies photo completed','View the photo').toString() + }] + }, + halfhalf_bar_knowsOlin: { + _id: Random.id(), + name: 'Cheers', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Cheers 1', + notificationSubject: 'Drinking at a bar?', + notificationText: 'Share an experience with others who are also drinking at a bar', + situation: { + detector: getDetectorId(DETECTORS.bar), + number: '1' + }, + toPass: { + instruction: 'What are you <span style="color: #0351ff">drinking at the bar</span>? Take a photo, while <span style="color: #0351ff">raising your glass or bottle</span> in front of you.', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Cheers photo completed','View the photo').toString() + }] + }, + halfhalf_religious_knowsOlin: { + _id: Random.id(), + name: 'Religious Architecture', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Religious Architecture 1', + notificationSubject: 'Visiting a place of worship?', + notificationText: 'Share an experience with others who are also visiting a place of worship', + situation: { + detector: getDetectorId(DETECTORS.castle), + number: '1' + }, + toPass: { + instruction: 'Do you notice the <span style="color: #0351ff">details of the religious building</span> near you? Do so now, by outstretching your hand and pointing out of the elements that stick out to you most.', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Religious Architecture photo completed','View the photo').toString() + }] + }, + halfhalf_sunset_knowsOlin: { + _id: Random.id(), + name: 'Sunset Together', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Sunset Together 1', + notificationSubject: 'Can you see the sunset?', + notificationText: 'Share an experience with others who are also watching the sunset', + situation: { + detector: getDetectorId(DETECTORS.sunset), + number: '1' + }, + toPass: { + instruction: 'What does the <span style="color: #0351ff">sunset</span> look like where you are? Find a good view; then, take a photo, with your hands outstretched towards the sun.', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Sunset Together photo completed','View the photo').toString() + }] + }, + halfhalf_asian_knowsOlin: { + _id: Random.id(), + name: 'Eating with Chopsticks', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Eating with Chopsticks 1', + notificationSubject: 'Eating at an asian restaurant?', + notificationText: 'Share an experience with others who are also eating asian food', + situation: { + detector: getDetectorId(DETECTORS.eating_with_chopsticks), + number: '1' + }, + toPass: { + instruction: 'Are you eating <span style="color: #0351ff">asian food</span> right now? Take a photo of what you are eating, <span style="color: #0351ff">holding your chopsticks.</span>', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('An Eating with Chopsticks photo completed','View the photo').toString() + }] + }, + halfhalf_books_knowsOlin: { + _id: Random.id(), + name: 'Book Buddies', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Book Buddies 1', + notificationSubject: 'Are you at a library?', + notificationText: 'Share an experience with others who are also at the library', + situation: { + detector: getDetectorId(DETECTORS.library), + number: '1' + }, + toPass: { + instruction: 'Sorry to interrupt your <span style="color: #0351ff">reading</span>! Find the nearest book, and take a photo <span style="color: #0351ff">holding up the book to your face.</span>', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Book Buddies photo completed','View the photo').toString() + }] + }, + halfhalf_leakmask_knowsOlin: { + _id: Random.id(), + name: 'Leaf Mask', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Leaf Mask 1', + notificationSubject: 'Are you at a park?', + notificationText: 'Share an experience with others who are also at a park', + situation: { + detector: getDetectorId(DETECTORS.forest), + number: '1' + }, + toPass: { + instruction: 'Find a <span style="color: #0351ff">leaf in the park</span>. Take a photo of the <span style="color: #0351ff">leaf covering your face, like it was a mask.</span>', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A Feet to the trees photo completed','View the photo').toString() + }] + }, + halfhalf_puddles_knowsOlin: { + _id: Random.id(), + name: 'Puddle Feet', + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: 'Puddle Feet 1', + notificationSubject: 'Are you outside while its raining?', + notificationText: 'Share an experience with others who are enjoying or enduring the rain', + situation: { + detector: getDetectorId(DETECTORS.rainy), + number: '1' + }, + toPass: { + instruction: 'Is it <span style="color: #0351ff">raining</span> today? Find a <span style="color: #0351ff">puddle</span> on the ground. Take a photo of yourself, stomping on the puddle!', + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify('A "Puddle Feet" photo completed','View the photo').toString() + }] + }, + halfhalf_coffeesideoflaughs_knowsOlin: { + _id: Random.id(), + name: "Coffee with a side of Laughs", + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + // needName MUST have structure "My Need Name XYZ" + needName: "Coffee with a side of Laughs 1", + notificationSubject: 'Inside a coffee shop?', + notificationText: 'Share an experience with others who are also at a coffee shop', + situation: { + detector: getDetectorId(DETECTORS.coffee), // any place that has cups (cafes + bars + restaurants) + number: '1' + }, + toPass: { + instruction: `Do you have <span style="color: #0351ff">a cup or glass</span> you are drinking? Take a photo with it in the middle of the picture. You can even try to <span style="color: #0351ff">pour some extra "cream"</span> into it too!`, + exampleImage: 'https://s3.us-east-2.amazonaws.com/ce-platform/oce-example-images/half-half-teasing-lotion-in-a-cup.jpg' + }, + numberNeeded: 2, + notificationDelay: 90, + }]), + description: 'While drinking coffee at a cafe, create a half half photo.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify("A 'Coffee with a side of Laughs' photo completed",'View the photo').toString() + }] + }, + halfhalf_bigbites_knowsOlin: { + _id: Random.id(), + name: "Big Bites", + group: 'Olin', + participateTemplate: 'halfhalfParticipate', + resultsTemplate: 'halfhalfResults', + contributionTypes: addStaticAffordanceToNeeds('knowsOlin', [{ + needName: "Big Bites 1", // Any restaurant that would serve something you'd eat with your hands (burrito, tacos, hotdogs, sandwiches, wraps, burgers, tradamerican, newamerican ) + notificationSubject: 'Eating at a restaurant?', + notificationText: 'Share an experience with others who are enjoying big bites of their meal', + situation: { + detector: getDetectorId(DETECTORS.big_bite_restaurant), + number: '1' + }, + toPass: { + instruction: `Are you <span style="color: #0351ff">eating food that would require a big bite</span> right now? Take a photo of yourself <span style="color: #0351ff">holding up your food</span> to the middle of the screen.`, + 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.', + notificationText: 'View this and other available experiences', + callbacks: [{ + trigger: '(cb.numberOfSubmissions() % 2) === 0', + function: halfhalfRespawnAndNotify("A 'Big Bites' photo completed",'View the photo').toString() + }] + }, +}; diff --git a/imports/api/testing/cn.js b/imports/api/Testing/cn.js similarity index 100% rename from imports/api/testing/cn.js rename to imports/api/Testing/cn.js diff --git a/imports/api/testing/conversion.js b/imports/api/Testing/conversion.js similarity index 100% rename from imports/api/testing/conversion.js rename to imports/api/Testing/conversion.js diff --git a/imports/api/testing/mincn.js b/imports/api/Testing/mincn.js similarity index 100% rename from imports/api/testing/mincn.js rename to imports/api/Testing/mincn.js diff --git a/imports/api/testing/createNewExperiences.js b/imports/api/testing/createNewExperiences.js deleted file mode 100644 index 7d660ed3..00000000 --- a/imports/api/testing/createNewExperiences.js +++ /dev/null @@ -1,385 +0,0 @@ -// import { Meteor } from "meteor/meteor"; - -import { Submissions } from "../OCEManager/currentNeeds"; -import { Detectors } from "../UserMonitor/detectors/detectors"; -import { Experiences, Incidents } from "../OCEManager/OCEs/experiences"; - -import { CONSTANTS } from "./testingconstants"; -import { - addContribution, createIncidentFromExperience, startRunningIncident, - updateExperienceCollectionDocument, updateIncidentFromExperience, updateRunningIncident -} from "../OCEManager/OCEs/methods"; - - -Meteor.methods({ - /** createOCE - general function to start OCEs through a Meteor RPC, i.e. through the browser console - * - * @param name [String] A key in CONSTANTS.EXPERIENCES object found in testingconstants.js - */ - createOCE({name}) { - new SimpleSchema({ - name: { type: String } - }).validate({name}); - - if (!(name in CONSTANTS.EXPERIENCES)) { - throw new Meteor.Error('createOCE.keynotfound', - `OCE by the name '${name}' was not found in CONSTANTS.EXPERIENCES`); - } - - let exp = CONSTANTS.EXPERIENCES[name]; - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); - }, - updateOCE({name}) { - // FIXME(rlouie): NOTE: you must run Meteor method call updateSubmissionNeedName in addition to this function - new SimpleSchema({ - name: { type: String } - }).validate({name}); - - if (!(name in CONSTANTS.EXPERIENCES)) { - throw new Meteor.Error('updateOCE.keynotfound', - `OCE by the name '${name}' was not found in CONSTANTS.EXPERIENCES`); - } - - let exp = CONSTANTS.EXPERIENCES[name]; - let old_exp = Experiences.findOne({name: exp.name}); - if (!old_exp) { - throw new Meteor.Error('updateOCE.experiencenotfound', - `Experience with name '${exp.name}' was not found in Experiences collection`); - } - - let eid = old_exp._id; - let old_incident = Incidents.findOne({eid: eid}); - if (!old_incident) { - throw new Meteor.Error('updateOCE.incidentnotfound', - `Incident with eid = '${eid}' was not found in Incidents collection`); - } - - updateExperienceCollectionDocument(eid, exp); - let incident = updateIncidentFromExperience(eid, exp); - updateRunningIncident(incident); - // FIXME(rlouie): NOTE: you must run Meteor method call updateSubmissionNeedName in addition to this function - }, - /** upsertDetector - general function to update or insert detectors through a Meteor RPC - * - * @param name [String] A key in CONSTANTS.DETECTORS object found in testingconstants.js - */ - upsertDetector({name}) { - new SimpleSchema({ - name: { type: String } - }).validate({name}); - - if (!(name in CONSTANTS.DETECTORS)) { - throw new Meteor.Error('upsertDetector.keynotfound', - `Detector by the name '${name}' was not found in CONSTANTS.DETECTORS`); - } - - let {numberAffected, insertedId} = Detectors.upsert({ - // Selector - // Note: Don't select on _id, since _id in CONSTANTS.DETECTORS changes from deployments cuz of Random.id() - description: CONSTANTS.DETECTORS[name].description, - }, { - // Modifier - $set : { - variables: CONSTANTS.DETECTORS[name].variables, - rules: CONSTANTS.DETECTORS[name].rules - } - } - ); - console.log(`Method call to "upsertDetector"! numberAffected: ${numberAffected}, insertedId: ${insertedId}`); - } -}); - -/* FIXME: Experiences are not workable, as the detector ids have changed - * TODO: Write a storytime construction helper / macro which can generate different versions of storytime structure -function createNewSpookyStorytime() { - let storytimeCallback = function(sub) { - Meteor.users.update( - { - _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"] - ]; - - // remove options just chosen - options = options.filter(function(x) { - return x[1] !== affordance; - }); - - let needName = "page" + Random.id(3); - - // done so you can use a different callback on last page - if (cb.numberOfSubmissions() === 7) { - needName = "pageFinal"; - } - - // need = contribution - 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 = ["night", "niceish_day", "restaurant", "sunset", "coffee"]; - let detectorIds = [ - "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 = { - _id: detectorIds[i], - description: CONSTANTS.DETECTORS[place].description + "_SpookyStorytime", - variables: newVars, - rules: [ - "(" + - CONSTANTS.DETECTORS[place].rules[0] + - " ) && !participatedInSpookyStorytime;" - ] - }; - Detectors.insert(det); - - i++; - }); - - 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"] - ]; - - let firstSentence = - "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) { - return x.uid; - }); - notify( - uids, - sub.iid, - "Our spooky story is finally complete. Click here to read it!", - "", - "/apicustomresults/" + sub.iid + "/" + sub.eid - ); - }; - - let exp = { - _id: "Qeeb9pTQDviBuv5Dd", //Random.id(), - name: "Spooky Storytime", - participateTemplate: "storyPage", - resultsTemplate: "storybook", - contributionTypes: [ - { - 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() - } - ] - }; - - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); -} - -function createNewSpookyNevilleStorytime() { - let storytimeCallback = function(sub) { - Meteor.users.update( - { - _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"] - ]; - - 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 = ["night", "niceish_day", "restaurant", "coffee"]; - let detectorIds = [ - "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", - variables: newVars, - rules: [ - "(" + - CONSTANTS.DETECTORS[place].rules[0] + - " ) && !participatedInSpookyHarryStorytime;" - ] - }; - Detectors.insert(det); - - i++; - }); - - 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"] - ]; - - let firstSentence = - "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) { - return x.uid; - }); - notify( - uids, - sub.iid, - "Our spooky Neville Longbottom story is finally complete. Click here to read it!", - "", - "/apicustomresults/" + sub.iid + "/" + sub.eid - ); - }; - - let exp = { - _id: "QC7LGdoDCZqCY8mWb", //Random.id(), - name: "Spooky Storytime", - participateTemplate: "storyPage", - resultsTemplate: "storybook", - contributionTypes: [ - { - 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() - } - ] - }; - - Experiences.insert(exp); - let incident = createIncidentFromExperience(exp); - startRunningIncident(incident); -} -*/ diff --git a/imports/api/testing/endToEndSimple.test.js b/imports/api/testing/endToEndSimple.test.js deleted file mode 100644 index 62f64419..00000000 --- a/imports/api/testing/endToEndSimple.test.js +++ /dev/null @@ -1,251 +0,0 @@ -import { resetDatabase } from 'meteor/xolvio:cleaner'; -import { Users } from '../UserMonitor/users/users'; -import { Incidents } from "../OCEManager/OCEs/experiences"; -import { CONSTANTS } from './testingconstants'; -import { onLocationUpdate } from '../UserMonitor/locations/methods'; -import { findUserByUsername } from "../UserMonitor/users/methods"; -import { Assignments} from "../OpportunisticCoordinator/databaseHelpers"; -import { Random } from 'meteor/random' -import { Detectors } from "../UserMonitor/detectors/detectors"; -import { updateSubmission} from "../OCEManager/progressor"; -import "../OCEManager/progressorHelper"; -import {insertTestUser, startTestOCE} from "../OpportunisticCoordinator/populateDatabase"; - - - -describe('Simple End To End', function () { - this.timeout(5*60*1000); - - let second = false; - let OCE_NAME = 'sameSituationAwareness'; - let NEEDNAME = 'Shopping for groceries'; - let USERNAME = 'garrett'; - let DETECTOR = CONSTANTS.DETECTORS.grocery; - let LOCATION = CONSTANTS.LOCATIONS.grocery; - let LOCATION2 = CONSTANTS.LOCATIONS.grocery2; - let LOCATION_NOMATCH = CONSTANTS.LOCATIONS.sushi; - - beforeEach((done) => { - - if (second) { - done(); - - } else { - second = true; - resetDatabase(); - insertTestUser(USERNAME); - Detectors.insert(DETECTOR); - startTestOCE(OCE_NAME); - - let uid = findUserByUsername(USERNAME)._id; - let bgLocationObj = { - "coords": { - "latitude": LOCATION.lat, - "longitude": LOCATION.lng - }, - "activity": {"type": "unknown", "confidence": 100} - }; - onLocationUpdate(uid, bgLocationObj, function() { - done(); - }); - } - }); - - it('user gets added to experience', (done) => { - const contributionForNeed = CONSTANTS.EXPERIENCES[OCE_NAME].contributionTypes.find(function(x) { - return x.needName === NEEDNAME; - }); - const notificationDelay = contributionForNeed.notificationDelay; - - // Wait to check if user.profile and assignments has changed, > notificationDelay seconds after first matching - Meteor.setTimeout(function() { - try { - let incident = Incidents.findOne({ eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id }); - let iid = incident._id; - let user = findUserByUsername(USERNAME); - - //user has incident as an active incident - chai.assert(user.activeIncidents().includes(iid), 'active incident not added to user profile'); - - //assignments has user assigned - let assignmentEntry = Assignments.findOne({ _id: iid }); - - let needUserMap = assignmentEntry.needUserMaps.find((x) => { - return x.needName === NEEDNAME; - }); - - chai.assert.typeOf(needUserMap.users, 'array', 'no needUserMap in Assignment DB'); - chai.assert(needUserMap.users.find(usermeta => usermeta.uid), 'uid not in needUserMap in Assignment DB'); - - done(); - } catch (err) { done(err); } - }, (notificationDelay + 5) * 1000); - }); - - it('user steps away from vicinity but returns within delay time', function(done) { - - // FIXME(rlouie): Because we are smoothing location updates, it does not make sense to suddenly - // transport someone to another place in this model - - // move to a location on same block, but that should be outside of affordance radius - let uid = findUserByUsername(USERNAME)._id; - let bgLocationObj = { - "coords": { - "latitude": LOCATION_NOMATCH.lat, - "longitude": LOCATION_NOMATCH.lng - }, - "activity": {"type": "unknown", "confidence": 100} - }; - onLocationUpdate(uid, bgLocationObj, function() { - console.log("Temporarily moved outside of vicinity of grocery store"); - }); - - const contributionForNeed = CONSTANTS.EXPERIENCES[OCE_NAME].contributionTypes.find(function(x) { - return x.needName === NEEDNAME; - }); - const notificationDelay = contributionForNeed.notificationDelay; - - // Wait some time less than notification Delay before moving back in - Meteor.setTimeout(function() { - - console.log("Checking if decommissioned prematurely with first non-match"); - // SAME CHECKS AS FIRST TIME, JUST HOPING NOW YOU DIDNT GET REMOVED - let incident = Incidents.findOne({ eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id }); - let iid = incident._id; - let user = findUserByUsername(USERNAME); - - //user has incident as an active incident - 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 }); - - let needUserMap = assignmentEntry.needUserMaps.find((x) => { - return x.needName === NEEDNAME; - }); - - chai.assert.typeOf(needUserMap.users, 'array', 'decommissioned prematurely - no needUserMap in Assignment DB'); - chai.assert(needUserMap.users.find(usermeta => usermeta.uid), 'decommissioned prematurely - uid not in needUserMap in Assignment DB'); - - // Move back to location - onLocationUpdate( - uid, { - "coords": { - "latitude": LOCATION.lat, - "longitude": LOCATION.lng - }, - "activity": {"type": "unknown", "confidence": 100} - }, function() { - console.log("Returned to the vicinity of Grocery Store") - }); - - Meteor.setTimeout(function() { - try { - // Users still active, now that they have moved back - let incident = Incidents.findOne({ eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id }); - let iid = incident._id; - let user = findUserByUsername(USERNAME); - - //user has incident as an active incident - 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 }); - - let needUserMap = assignmentEntry.needUserMaps.find((x) => { - return x.needName === NEEDNAME; - }); - - chai.assert.typeOf(needUserMap.users, 'array', 'remain assigned while back in vicinity -- no needUserMap in Assignment DB'); - chai.assert(needUserMap.users.find(usermeta => usermeta.uid), 'remain assigned while back in vicinity -- uid not in needUserMap in Assignment DB'); - - done(); - } catch (err) { done(err); } - }, 2 * 1000); - - }, (notificationDelay) * 0.2 * 1000); - }); - - it('user participates in experience', (done) => { - let incident = Incidents.findOne({ eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id }); - let iid = incident._id; - let uid = findUserByUsername(USERNAME)._id; - - let submission = { - uid: uid, - eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id, - iid: iid, - needName: NEEDNAME, - content: {}, - timestamp: Date.now(), - lat: LOCATION.lat, - lng: LOCATION.lng, - }; - - updateSubmission(submission); - - // Wait several seconds so the observe changes of Submissions collection can run - Meteor.setTimeout(function () { - try { - let user = findUserByUsername(USERNAME); - 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); } - }, 5 * 1000); - - }); - - it('user ALSO SHOULD be able to participate again, if need.allowRepeatContributions: true', (done) => { - - // wait for userParticipatedTooRecently check to expire - Meteor.setTimeout(() => { - - // FIXME(rlouie): Because we are smoothing location updates, it does not make sense to suddenly - // transport someone to another place in this model - - // move to another situation that matches the same need - let uid = findUserByUsername(USERNAME)._id; - let bgLocationObj = { - "coords": { - "latitude": LOCATION2.lat, - "longitude": LOCATION2.lng - }, - "activity": {"type": "unknown", "confidence": 100} - }; - onLocationUpdate(uid, bgLocationObj, function() { - }); - - const contributionForNeed = CONSTANTS.EXPERIENCES[OCE_NAME].contributionTypes.find(function(x) { - return x.needName === NEEDNAME; - }); - const notificationDelay = contributionForNeed.notificationDelay; - - // Wait to check if user.profile and assignments has changed, > notificationDelay seconds after first matching - Meteor.setTimeout(function() { - try { - let incident = Incidents.findOne({ eid: CONSTANTS.EXPERIENCES[OCE_NAME]._id }); - let iid = incident._id; - let user = findUserByUsername(USERNAME); - - //user has incident as an active incident - chai.assert(user.activeIncidents().includes(iid), 'active incident not added to user profile'); - - //assignments has user assigned - let assignmentEntry = Assignments.findOne({ _id: iid }); - - let needUserMap = assignmentEntry.needUserMaps.find((x) => { - return x.needName === NEEDNAME; - }); - - chai.assert.typeOf(needUserMap.users, 'array', 'no needUserMap in Assignment DB'); - chai.assert(needUserMap.users.find(usermeta => usermeta.uid), 'uid not in needUserMap in Assignment DB'); - - done(); - } catch (err) { done(err); } - }, (notificationDelay + 5) * 1000); - - // for value to wait on local, @see userParticipatedTooRecently in imports/api/UserMonitor/locations/methods.js - }, 60 * 1000); - }); -}); diff --git a/imports/api/testing/testingconstants.js b/imports/api/testing/testingconstants.js deleted file mode 100644 index 18e6c08f..00000000 --- a/imports/api/testing/testingconstants.js +++ /dev/null @@ -1,515 +0,0 @@ -// import { Meteor } from "meteor/meteor"; -import { Submissions } from "../OCEManager/currentNeeds"; -import { addContribution } from '../OCEManager/OCEs/methods'; -import { Detectors } from "../UserMonitor/detectors/detectors"; -import { Incidents } from "../OCEManager/OCEs/experiences"; -import { cn } from "./cn"; - -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 = { - 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;'] - }, - 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`); - } - } -}); - -//the CN compiler; takes in author-defined syntax -const convertCNtoCE = function(script) { - - let storyName = script[0] - let storyDescription = script[1] - let storyNotification = script[2] - let generalContext = script[3] - let templates = script[4] - let preStoryInfo = script[5] - let characterRoles = script[6] - let prompts = script[7] - let questions = [] - - //create series of questions based on what information the author specified they wanted preStoryInfo - for (info in preStoryInfo) { - let temp = {}; - temp.question = info.question; - //if the question requires a short answer response - if (info.responseType == "text") { - temp.responseType = "text" - temp.responseData = info.responseData - //if the question has a list of choices to choose from - } else { - temp.responseData = [] - for (choice in info.responseData) { - temp.responseData.push(choice) - } - } - questions.push(temp); - } - - //console.log("questions: " + questions) - - let values = [ - 'not busy at all', - 'a little busy', - 'somewhat busy', - 'pretty busy', - 'very busy' - ]; - - let dropdownText = [ - 'not busy at all', - 'a little busy', - 'somewhat busy', - 'pretty busy', - 'busy' - ]; - - let DROPDOWN_OPTIONS = _.zip(dropdownText); - - //callback function, occurs once the pre-story contexts have been submitted - const MurderMysteryCallback = function (sub) { - let submissions = Submissions.find({ - iid: sub.iid, - needName: sub.needName - }).fetch(); - - let contribution = Incidents.findOne({ _id: sub.iid}); - - //console.log("in callback") - //console.log(submissions.length) - - //update the UI to fit the one after the callback - experience = Experiences.update({ - "_id": sub.eid - }, { - "$set": { - "participateTemplate": contribution.contributionTypes[0].toPass.template - } - }) - - //iterate through each submission, casting a character and creating its respective detector for each one - 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) - - //find every other participant in the experience - 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); - - - - //check to see how busy the user is - let characterRoles = contribution.contributionTypes[0].toPass.characterRoles; - let character = [] - let cast = false; - //iterate through each character role, and find the role that best fits the current submission or participant - for (let k = 0; k < characterRoles.length; k++) { - for (let j = 0; j < characterRoles[k].context.length; j++){ - console.log("within inner loop " + j + " " + k) - if (participant.profile.staticAffordances[characterRoles[k].context[j]]) { - console.log("Casting a " + characterRoles[k].roleName); - rules = submissions[i].content.busy + " && " + rules; - character.push([rules, characterRoles[k].roleName, contribution.contributionTypes[0].toPass.template, characterRoles[k].instruction, participant._id, other_participants]) - cast = true; - break; - } - } - if (cast) { - break; - }; - } - - console.log("character length" + character.length) - - let extraAffordances = [] - - extraAffordances.push(submissions[i].content.busy) - - //create the detector for the character - _.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, instruction, (error, response) => { - if (error) { - alert(error.reason); - } else { - //Cookie.set("name", response.name); - //$input.val(""); - } - }) - - let prompts = contribution.contributionTypes[0].toPass.prompts; - - //when casting the murderer, set up the series of hints that will be sent out as verious points - if (submissions[i].content.busy == "very busy") { - - for (let z = 0; z < prompts.length-1; z++) { - if (prompts[z].info != "") { - prompts[z].info = submissions[i].content[prompts[z].info] + "!"; - } - let info = prompts[z].prompt + prompts[z].info; - Meteor.setTimeout(function() {Meteor.call("sendPrompt", info, (error, response) => { - if (error) { - alert(error.reason); - } else { - //Cookie.set("name", response.name); - //$input.val(""); - } - })}, prompts[z].timing * 1000) - } - - //the last prompt is the ending of the story, which will reveal who the murderer is - let ending = prompts[prompts.length-1].prompt + participant.profile.firstName + "!"; - - Meteor.setTimeout(function() {Meteor.call("sendPrompt", ending, (error, response) => { - if (error) { - alert(error.reason); - } else { - //Cookie.set("name", response.name); - //$input.val(""); - } - })}, prompts[prompts.length-1].timing * 1000) - } - }); - } - } - - //create a general experience instance, specify how many people are needed before the story begins - let experience = { - name: storyName, - participateTemplate: templates[0], - resultsTemplate: templates[1], - contributionTypes: [ - ], - description: storyDescription, - notificationText: storyNotification, - callbacks: [{ - trigger: 'cb.newSubmission() && (cb.numberOfSubmissions() == 3)', - // substitute any variables used outside of the callback function scope - function: eval('`' + MurderMysteryCallback.toString() + '`'), - }] - }; - - const staticAffordances = ['cn']; - const places = [ - [generalContext[0], generalContext[1], storyNotification], - ]; - -//create the initial detector/contribution type for people to submit their pre-story context -staticAffordances.forEach(affordance => { - experience.contributionTypes = [...experience.contributionTypes, ...addStaticAffordanceToNeeds(affordance, ((places) => - places.map(place => { - const [detectorName, situationDescription, instruction] = place; - return { - needName: `${experience.name} ${detectorName}`, - situation: { - detector: getDetectorId(DETECTORS[detectorName]), - number: 3 - }, - participateTemplate: templates[0], - toPass: { - prompts: prompts, - characterRoles: characterRoles, - template: templates[1], - situationDescription: `Having a good time ${situationDescription}?`, - instruction: `${instruction}`, - questions: preStoryInfo, - dropdownChoices: { - name: values, - options: DROPDOWN_OPTIONS - } - }, - numberNeeded: 3, - // notificationDelay: 90 uncomment for testing - } - }) - )(places))]; -}); -return experience; -} - -/** - * Following is the code that an author has to write to create a CN. - * This is the concise syntax that is then compiled into a working CN. - * - */ - - - -/** - * Following are CE helper functions: - * - * 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 = { - cn: convertCNtoCE(cn()) -}; - -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 -}; diff --git a/imports/startup/server/fixtures.js b/imports/startup/server/fixtures.js index 484887fe..cf98071b 100644 --- a/imports/startup/server/fixtures.js +++ b/imports/startup/server/fixtures.js @@ -11,7 +11,7 @@ import { Assignments, Availability } from "../../api/OpportunisticCoordinator/da import { Images, Avatars } from '../../api/ImageUpload/images.js'; import { log } from '../../api/logs.js'; -import { CONSTANTS } from "../../api/testing/testingconstants"; +import { CONSTANTS } from "../../api/Testing/testingconstants"; import { createIncidentFromExperience, startRunningIncident } from "../../api/OCEManager/OCEs/methods.js"; import { findUserByUsername } from '../../api/UserMonitor/users/methods'; import { Detectors } from "../../api/UserMonitor/detectors/detectors"; diff --git a/imports/startup/server/register-api.js b/imports/startup/server/register-api.js index aeea8706..4baf5519 100644 --- a/imports/startup/server/register-api.js +++ b/imports/startup/server/register-api.js @@ -15,6 +15,6 @@ import '../../api/OpportunisticCoordinator/server/publications.js'; import '../../api/OCEManager/progressorHelper.js'; import '../../api/OCEManager/server/publications.js'; import '../../api/OpportunisticCoordinator/server/noticationMethods.js'; -import '../../api/testing/createNewExperiences.js'; +import '../../api/Testing/createNewExperiences.js'; import '../../api/Logging/page_log/methods.js'; import '../../api/Logging/groundtruth_log/methods.js';