| @@ -0,0 +1 @@ | ||
| web: node ./bin/www |
| @@ -0,0 +1,48 @@ | ||
| # Bakery | ||
|
|
||
| An Optimizely A/B test deployment tool developed by the [Firefox Growth Team](https://wiki.mozilla.org/Growth_Team) at Mozilla. | ||
|
|
||
| # Table of Contents | ||
|
|
||
| 1. [About](https://github.com/mozilla/bakery#about) | ||
| 2. [Usage](https://github.com/mozilla/bakery#usage) | ||
| 3. [Contributing](https://github.com/mozilla/bakery#contributing) | ||
|
|
||
| # About | ||
|
|
||
| Bakery is a remote Optimizely A/B test creation tool which utilizes the [Optimizely REST API](http://developers.optimizely.com/rest/introduction/index.html) and a modified version of the [FunnelEnvy Optimizely-Node Tool](https://github.com/funnelenvy/optimizely-node). The repository hosts two different deployment tools, one on the Web (stable) and one as a Node.js script (unstable). The web application is written using Node.js, Express.js, and Nunjucks. The script is written using Node.js. | ||
|
|
||
| # Usage | ||
|
|
||
| Clone the repository: | ||
|
|
||
| 1. Open your Terminal | ||
| 2. Run `git clone https://github.com/mozilla/bakery.git` | ||
| 3. Run `cd bakery` | ||
|
|
||
| Installing neccessary dependencies: | ||
|
|
||
| 1. Download and Install [Node](https://nodejs.org/en/download/) | ||
| 2. Run `npm install` | ||
| 3. Locate the file [OptimizelyClient.js](https://github.com/mozilla/bakery/blob/additionalEdits/OptimizelyClient.js) in the root directory of Bakery | ||
| 4. Copy that file to node_modules/optimizely-node-client/lib/ replacing the existing OptimzielyClient.js file | ||
|
|
||
| Authenticating Bakery: | ||
|
|
||
| 1. Generate an API token from the [token generator](http://app.optimizely.com/tokens), or use an existing API token on the account. | ||
| 2. Copy that token to your [Access.js](https://github.com/mozilla/bakery/blob/additionalEdits/access.js#L3) file, and replace `'your token here'` with the token (don't forget the single quotations around the token). | ||
|
|
||
| Running Bakery Web-Tool (Stable): | ||
|
|
||
| 1. Run `node ./bin/www.js` | ||
| 2. Open Firefox and navigate to [localhost:3000](http://localhost:3000) | ||
|
|
||
| Running Bakery Script-Tool (Unstable): | ||
|
|
||
| 2. Run `node bakeryScript.js` | ||
|
|
||
| # Contributing | ||
|
|
||
| Interested in Contributing? | ||
|
|
||
| Check out [CONTRIBUTING.md](https://github.com/mozilla/bakery/blob/master/CONTRIBUTING.md) |
| @@ -0,0 +1,4 @@ | ||
| 'use strict'; | ||
|
|
||
| exports.token = 'your token here' | ||
|
|
| @@ -0,0 +1,51 @@ | ||
| 'use strict'; | ||
|
|
||
| var path = require('path'); | ||
|
|
||
| var express = require('express'); | ||
| var logger = require('morgan'); | ||
| var bodyParser = require('body-parser'); | ||
| var nunjucks = require('nunjucks'); | ||
| var access = require('./access'); | ||
| var OptimizelyClient = require('optimizely-node-client'); | ||
| var cookieParser = require('cookie-parser'); | ||
| var session = require('express-session'); | ||
|
|
||
| var config = require('./lib/config'); | ||
|
|
||
| // Routes | ||
| var index = require('./routes/index'); | ||
|
|
||
| var app = express(); | ||
| var env; | ||
|
|
||
| app.use(cookieParser('123456789QWERTY')); | ||
| app.use(session(session({ | ||
| genid: function(req) { | ||
| return genuuid() // use UUIDs for session IDs | ||
| }, | ||
| secret: '123456789QWERTY' | ||
| }))); | ||
|
|
||
| // Set up Nunjucks | ||
| env = nunjucks.configure('views', { | ||
| autoescape: true, | ||
| express: app | ||
| }); | ||
|
|
||
| // Global template variables | ||
| env.addGlobal('brand', config.brand); | ||
| env.addFilter('println', function(str) { | ||
| console.log(str); | ||
| }); | ||
|
|
||
| app.use(logger('dev')); | ||
| app.use(bodyParser.json()); | ||
| app.use(bodyParser.urlencoded({ | ||
| extended: false | ||
| })); | ||
| app.use(express.static(path.join(__dirname, 'public'))); | ||
|
|
||
| app.use('/', index); | ||
|
|
||
| module.exports = app; |
| @@ -0,0 +1,302 @@ | ||
| //requires | ||
| var access = require('./access'); | ||
| var OptimizelyClient = require('optimizely-node-client'); | ||
| var prompt = require('prompt'); | ||
|
|
||
| //initialize experiment vars | ||
| var projectID; | ||
| var expDescription; | ||
| var expStatus = "Not started"; | ||
| var expUrlConditions; | ||
| var expURL; | ||
| var expActiveMode = "immediate"; | ||
| var expAudienceIDs = []; | ||
| var expVariations = [""]; | ||
| var expType = "ab"; | ||
| var goalExpIDs; | ||
| var numVariations; | ||
|
|
||
| //Get and set API token | ||
| var API_TOKEN = access.token; | ||
| var oc = new OptimizelyClient(API_TOKEN); | ||
|
|
||
| var projects = oc.getProjects(); | ||
|
|
||
| //create JSON schema for first prompt (experiment, goal, audience) | ||
| var expSchema = { | ||
| properties: { | ||
| Title: { | ||
| message: "Experiment Title", | ||
| requred: true, | ||
| }, | ||
| Goal: { | ||
| /*pattern: ALL GOAL TITLES,*/ | ||
| message: "Goal ID", | ||
| required: false | ||
| }, | ||
| Audience: { | ||
| /*pattern: ALL GOAL TITLES,*/ | ||
| message: "Audience ID", | ||
| required: false | ||
| }, | ||
| Optimizely_Test_URL: { | ||
| message: "Editor URL", | ||
| required: true | ||
| }, | ||
| ExperimentURL: { | ||
| message: "Url Target", | ||
| required: true | ||
| }, | ||
| Regex: { | ||
| message: "Regex (Y/N)", | ||
| required: true, | ||
| pattern: /(Y|N|y|n)/ | ||
| }, | ||
| Number_Of_Variations: { | ||
| message: "Number of Variations", | ||
| required: true | ||
| }, | ||
| Segment: { | ||
| message: "Percentage of Visitors Included (0-100)", | ||
| required: true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //create JSON schema for 2-n variation prompt | ||
| var varSchema = { | ||
| properties: { | ||
| Weight: { | ||
| message: "Percentage of Visitors (all "+numVariations+" must add up to 100)", | ||
| required: true | ||
| }, | ||
| Description: { | ||
| message: "Variation Title", | ||
| required: true | ||
| }, | ||
| JS: { | ||
| message: "Custom Javascript", | ||
| required: false | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //create JSON schema for schedule prompt | ||
| var schedSchema = { | ||
| properties: { | ||
| Start_Time: { | ||
| message: "Start Time", | ||
| required: false | ||
| }, | ||
| Stop_Time: { | ||
| message: "Stop Time", | ||
| required: false | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //create JSON schema for schedule prompt | ||
| var projectSchema = { | ||
| properties: { | ||
| ID: { | ||
| message: "Project ID", | ||
| required: true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| projects.then(function(projectsList) { | ||
|
|
||
| console.log("PROJECTS:"); | ||
| projectsList.forEach(function(project) { | ||
| console.log(" " + project.project_name + " ID: " + project.id) | ||
| }); | ||
|
|
||
| console.log("\n"); | ||
|
|
||
| prompt.get(projectSchema, function (err,result) { | ||
|
|
||
| projectID = result.ID; | ||
|
|
||
| //get goals | ||
| var goals = oc.getGoals({ | ||
| id: projectID | ||
| }); | ||
|
|
||
| //get response from goals | ||
| goals.then(function(goalsList) { | ||
|
|
||
| //print every goal title | ||
| console.log("PROJECT GOALS:"); | ||
| goalsList.forEach(function(goal) { | ||
| console.log(" " + goal.title + " ID: " + goal.id); | ||
| }); | ||
|
|
||
| //get audiences | ||
| var audienceList = oc.getAudiences({ | ||
| id: projectID | ||
| }); | ||
|
|
||
| //get response from audiences | ||
| audienceList.then(function(audienceList) { | ||
| //print each audience name and ID | ||
| console.log("PROJECT AUDIENCES:"); | ||
| audienceList.forEach(function(audience) { | ||
| console.log(" " + audience.name + " ID: " + audience.id); | ||
| }); | ||
|
|
||
| console.log("\n"); | ||
|
|
||
| //prompt user for experiment info | ||
| console.log("TEST PARAMETERS:"); | ||
| prompt.start(); | ||
| prompt.get(expSchema, function (err,result) { | ||
| goal = result.Goal; | ||
| expDescription = result.Title; | ||
| expURL = result.Optimizely_Test_URL; | ||
| expAudienceIDs[expAudienceIDs.length] = parseInt(result.Audience); | ||
| chosenGoalID = parseInt(result.Goal); | ||
| percentageIncluded = result.Segment*100; | ||
| numVariations = result.Number_Of_Variations; | ||
|
|
||
| if(result.Regex == "Y" || result.Regex == "y") | ||
| { | ||
| expUrlConditions = [{ | ||
| "match_type": "regex", | ||
| "value": result.ExperimentURL | ||
| }]; | ||
| } | ||
| else | ||
| { | ||
| expUrlConditions = [{ | ||
| "match_type": "simple", | ||
| "value": result.ExperimentURL | ||
| }]; | ||
| } | ||
|
|
||
| //Create experiment | ||
| experimentPosted = oc.createExperiment({ | ||
| project_id: projectID, | ||
| description: expDescription, | ||
| status: expStatus, | ||
| url_conditions: expUrlConditions, | ||
| edit_url: expURL, | ||
| activation_mode: expActiveMode, | ||
| experiment_type: "ab", | ||
| variation_ids: expVariations | ||
| }); | ||
|
|
||
| //get info for experiment just created | ||
| experimentPosted.then(function(experimentDetails) { | ||
| console.log("\n"); | ||
| console.log("Created experiment..."); | ||
|
|
||
| //get id for experiment just created | ||
| var experimentID = experimentDetails.id; | ||
| //prompt user for the percentage they want to include | ||
|
|
||
| //update our created experiment with these details | ||
| var update = oc.updateExperiment({ | ||
| id: experimentID, | ||
| audience_ids: expAudienceIDs, | ||
| percentage_included: percentageIncluded | ||
| }); | ||
|
|
||
| update.done(function(result) { console.log("Updated experiment...\n"); | ||
| console.log("EXPERIMENT SCHEUDLE"); | ||
| console.log("Ex start/stop time: 2015-12-20T08:00:00Z"); | ||
| prompt.get(schedSchema, function (err,result2) { | ||
|
|
||
|
|
||
| if(result2.Stop_Time || result2.Start_Time) | ||
| { | ||
| //Create schedule for experiment w/ experiment ID | ||
| var createSchedule = oc.createSchedule({ | ||
| "stop_time": result2.Stop_Time, | ||
| "start_time": result2.Start_Time, | ||
| "experiment_id": experimentID | ||
| }); | ||
|
|
||
| createSchedule.done(function(result) { console.log("Added schedule..."); }); | ||
| } | ||
|
|
||
|
|
||
| variationsList = oc.getVariations({ | ||
| id: experimentID | ||
| }); | ||
|
|
||
| variationsList.then(function(variations) { | ||
| console.log("Retrieved default variations..."); | ||
|
|
||
| variations.forEach(function(variation) { | ||
| var deleted = oc.deleteVariation({ | ||
| id: variation.id | ||
| }); | ||
| }); | ||
|
|
||
| console.log("Deleted default variations..."); | ||
|
|
||
| //get the array of experiments for the goal we would like | ||
| goalsList.forEach(function(goal) { | ||
| if(goal.id = chosenGoalID) | ||
| { | ||
| goalExpIDs = goal.experiment_ids; | ||
| } | ||
| }); | ||
|
|
||
| //append our experiment ID to the end of that array of experiments | ||
| goalExpIDs[goalExpIDs.length] = experimentID; | ||
|
|
||
| //post that info to optimizely | ||
| var addGoal = oc.putGoal({ | ||
| id: chosenGoalID, | ||
| experiment_ids: goalExpIDs | ||
| }); | ||
|
|
||
| addGoal.done(function(result) { | ||
| console.log("Added goal...\n"); | ||
|
|
||
| console.log("DEFAULT VARIATION AMT: 2"); | ||
| console.log("VARIATION 1 PARAMETERS:"); | ||
| prompt.get(varSchema, function (err,result3) { | ||
| //prompt user for the variation information | ||
| var varWeight = result3.Weight*100; | ||
| var varDescription = result3.Description; | ||
| var varJS = result3.JS || ""; | ||
|
|
||
| //create variation | ||
| var createVar = oc.createVariation({ | ||
| experiment_id: experimentID, | ||
| description: varDescription, | ||
| weight: varWeight, | ||
| js_component: varJS | ||
| }); | ||
| createVar.done(function(result) { | ||
| console.log("Created variation 1..."); | ||
| prompt.get(varSchema, function (err,result4) { | ||
| //prompt user for the variation information | ||
| varWeight = result4.Weight*100; | ||
| varDescription = result4.Description; | ||
| varJS = result4.JS || ""; | ||
|
|
||
| //create variation | ||
| var createVar2 = oc.createVariation({ | ||
| experiment_id: experimentID, | ||
| description: varDescription, | ||
| weight: varWeight, | ||
| js_component: varJS | ||
| }); | ||
| createVar2.done(function(result) {console.log("Created variation 2..."); }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
| @@ -0,0 +1,88 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Module dependencies. | ||
| */ | ||
|
|
||
| var app = require('../app'); | ||
| var debug = require('debug')('bakery:server'); | ||
| var http = require('http'); | ||
|
|
||
| /** | ||
| * Get port from environment and store in Express. | ||
| */ | ||
|
|
||
| var port = normalizePort(process.env.PORT || '3000'); | ||
| app.set('port', port); | ||
|
|
||
| /** | ||
| * Create HTTP server. | ||
| */ | ||
|
|
||
| var server = http.createServer(app); | ||
|
|
||
| /** | ||
| * Listen on provided port, on all network interfaces. | ||
| */ | ||
|
|
||
| server.listen(port); | ||
| server.on('error', onError); | ||
| server.on('listening', onListening); | ||
|
|
||
| /** | ||
| * Normalize a port into a number, string, or false. | ||
| */ | ||
|
|
||
| function normalizePort(val) { | ||
| var port = parseInt(val, 10); | ||
|
|
||
| if (isNaN(port)) { | ||
| // named pipe | ||
| return val; | ||
| } | ||
|
|
||
| if (port >= 0) { | ||
| // port number | ||
| return port; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Event listener for HTTP server "error" event. | ||
| */ | ||
|
|
||
| function onError(error) { | ||
| if (error.syscall !== 'listen') { | ||
| throw error; | ||
| } | ||
|
|
||
| var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; | ||
|
|
||
| // handle specific listen errors with friendly messages | ||
| switch (error.code) { | ||
| case 'EACCES': | ||
| console.error(bind + ' requires elevated privileges'); | ||
| process.exit(1); | ||
| break; | ||
| case 'EADDRINUSE': | ||
| console.error(bind + ' is already in use'); | ||
| process.exit(1); | ||
| break; | ||
| default: | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Event listener for HTTP server "listening" event. | ||
| */ | ||
|
|
||
| function onListening() { | ||
| var addr = server.address(); | ||
| var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; | ||
| debug('Listening on ' + bind); | ||
| } |
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "name": "Bakery", | ||
| "dependencies": { | ||
| "jquery": "~2.1.4" | ||
| } | ||
| } |
| @@ -0,0 +1,31 @@ | ||
| 'use strict'; | ||
|
|
||
| var gulp = require('gulp'); | ||
|
|
||
| var js = ['**/*.js', '!node_modules/**/*.js', '!public/lib/**/*.js']; | ||
| var json = ['**/*.json', '!node_modules/**/*.json', '!public/lib/**/*.json']; | ||
|
|
||
| gulp.task('beautify', ['beautify:javascript']); | ||
|
|
||
| gulp.task('beautify:javascript', function() { | ||
| var beautify = require('gulp-jsbeautifier'); | ||
|
|
||
| gulp.src(js.concat(json), { | ||
| base: './' | ||
| }) | ||
| .pipe(beautify({ | ||
| indentSize: 4, | ||
| keepFunctionIndentation: true | ||
| })) | ||
| .pipe(gulp.dest('./')); | ||
| }); | ||
|
|
||
| gulp.task('test', ['test:jshint']); | ||
|
|
||
| gulp.task('test:jshint', function() { | ||
| var jshint = require('gulp-jshint'); | ||
|
|
||
| return gulp.src(js) | ||
| .pipe(jshint()) | ||
| .pipe(jshint.reporter('default')); | ||
| }); |
| @@ -0,0 +1,7 @@ | ||
| 'use strict'; | ||
|
|
||
| var config = { | ||
| brand: 'Bakery' | ||
| }; | ||
|
|
||
| module.exports = config; |
| @@ -0,0 +1,25 @@ | ||
| { | ||
| "name": "Bakery", | ||
| "version": "0.0.1", | ||
| "private": true, | ||
| "scripts": { | ||
| "postinstall": "./node_modules/bower/bin/bower install" | ||
| }, | ||
| "dependencies": { | ||
| "body-parser": "^1.13.2", | ||
| "bower": "^1.5.3", | ||
| "debug": "^2.2.0", | ||
| "express": "^4.13.1", | ||
| "morgan": "^1.6.1", | ||
| "nunjucks": "^2.1.0", | ||
| "prompt": "^0.2.14", | ||
| "optimizely-node-client": "^0.5.0", | ||
| "cookie-parser": "^1.4.0", | ||
| "express-session": "^1.12.1" | ||
| }, | ||
| "devDependencies": { | ||
| "gulp": "^3.9.0", | ||
| "gulp-jsbeautifier": "^1.0.1", | ||
| "gulp-jshint": "^1.11.2" | ||
| } | ||
| } |
| @@ -0,0 +1,198 @@ | ||
| 'use strict'; | ||
|
|
||
| // Written by: Francesco Polizzi | ||
| // Date: December 15, 2015 | ||
| // Purpose: To asynchronously make optimizely client API calls based upon the current step in the test creation. | ||
| // Input parameters: callback - a function which is executed once the asynchronous call is completed | ||
| // step - An integer value used to segment different calls needed at different steps in the test creation process | ||
| // projectID - The ID for the project selected by the user | ||
|
|
||
| var access = require('../access.js'); | ||
| var OptimizelyClient = require('optimizely-node-client'); | ||
|
|
||
| //Get and set API token | ||
| var API_TOKEN = access.token; | ||
| var oc = new OptimizelyClient(API_TOKEN); | ||
|
|
||
| //initialize JSON object | ||
| var data = [{}]; | ||
|
|
||
| module.exports = { | ||
| initialize: function (callback, step, projectID, goal, audience, expTitle, editorURL, experimentURL, isRegex, pctVisitors, varTitle, varPercent, customJS, startTime, stopTime) { | ||
| switch(step) | ||
| { | ||
| case 0: | ||
| { | ||
| // get projects | ||
| var projects = oc.getProjects(); | ||
|
|
||
| // get response from projects | ||
| projects.done(function(projectsList) { | ||
| var x=0; | ||
| // for each object in the json objects array, add that object to the data array | ||
| projectsList.forEach(function(project) { | ||
| var projectObj = {name : project.project_name, id : project.id }; | ||
| data[x] = projectObj; | ||
| x++; | ||
| }); | ||
| // return projects to callback | ||
| callback(data); | ||
| }); | ||
| break; | ||
| } | ||
| case 1: | ||
| { | ||
| // get goals | ||
| var goals = oc.getGoals({ | ||
| id: projectID | ||
| }); | ||
|
|
||
| // get response from goals | ||
| goals.then(function(goalsList) { | ||
| var x=0; | ||
| goalsList.forEach(function(goal) { | ||
| var goalObj = {name : goal.title, id : goal.id}; | ||
| data[x] = goalObj; | ||
| x++ | ||
| }); | ||
|
|
||
| data[x] = {name : "*", id : "*"}; | ||
| x++; | ||
|
|
||
| // get audiences | ||
| var audienceList = oc.getAudiences({ | ||
| id: projectID | ||
| }); | ||
|
|
||
| // get response from audiences | ||
| audienceList.then(function(audienceList) { | ||
| audienceList.forEach(function(audience) { | ||
| var audienceObj = {name : audience.name, id : audience.id}; | ||
| data[x] = audienceObj; | ||
| x++ | ||
| }); | ||
| // return goals + audiences to callback | ||
| callback(data); | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| case 4: | ||
| { | ||
| console.log("Beginning build process."); | ||
|
|
||
| if(isRegex == "true") | ||
| { | ||
| var expUrlConditions = [{ | ||
| "match_type": "regex", | ||
| "value": experimentURL | ||
| }]; | ||
| } | ||
| else | ||
| var expUrlConditions = [{ | ||
| "match_type": "simple", | ||
| "value": experimentURL | ||
| }]; | ||
|
|
||
| var expVariations = [""]; | ||
| var expAudienceIDs = []; | ||
|
|
||
| // Create experiment | ||
| var experimentPosted = oc.createExperiment({ | ||
| project_id: projectID, | ||
| description: expTitle, | ||
| status: "Not started", | ||
| url_conditions: expUrlConditions, | ||
| edit_url: editorURL, | ||
| activation_mode: "immediate", | ||
| experiment_type: "ab", | ||
| variation_ids: expVariations | ||
| }); | ||
|
|
||
| // get info for experiment just created | ||
| experimentPosted.then(function(experimentDetails) { | ||
| var experimentID = experimentDetails.id; | ||
| expAudienceIDs[expAudienceIDs.length] = parseInt(audience); | ||
|
|
||
| // update our created experiment with these details | ||
| var update = oc.updateExperiment({ | ||
| id: experimentID, | ||
| audience_ids: expAudienceIDs, | ||
| percentage_included: pctVisitors*100 | ||
| }); | ||
|
|
||
| // when we are done updating the experiment | ||
| update.done(function(result) { | ||
| console.log("Updated experiment..."); | ||
|
|
||
| // if we have a start or stop time to add | ||
| if(startTime || stopTime) | ||
| { | ||
| //Create schedule for experiment w/ experiment ID | ||
| var createSchedule = oc.createSchedule({ | ||
| "stop_time": stopTime, | ||
| "start_time": startTime, | ||
| "experiment_id": experimentID | ||
| }); | ||
|
|
||
| createSchedule.done(function(result) { console.log("Added schedule..."); }); | ||
| } | ||
|
|
||
| var variationsList = oc.getVariations({ | ||
| id: experimentID | ||
| }); | ||
|
|
||
| variationsList.then(function(variations) { | ||
| console.log("Retrieved default variations..."); | ||
|
|
||
| variations.forEach(function(variation) { | ||
| if(variation.description != "Original") | ||
| var deleted = oc.deleteVariation({ | ||
| id: variation.id | ||
| }); | ||
| }); | ||
|
|
||
| console.log("Deleted default variation #1..."); | ||
|
|
||
| var goalExpIDs = []; | ||
| goalExpIDs[goalExpIDs.length] = experimentID; | ||
|
|
||
| //post that info to optimizely | ||
| var addGoal = oc.putGoal({ | ||
| id: goal, | ||
| experiment_ids: goalExpIDs | ||
| }); | ||
|
|
||
| addGoal.done(function(result) { | ||
| console.log("Added goal..."); | ||
|
|
||
| var varWeight = varPercent*100; | ||
| var varDescription = varTitle; | ||
| var varJS = customJS || ""; | ||
|
|
||
| //create variation | ||
| var createVar = oc.createVariation({ | ||
| experiment_id: experimentID, | ||
| description: varDescription, | ||
| weight: varWeight, | ||
| js_component: varJS | ||
| }); | ||
|
|
||
| createVar.done(function(result) { | ||
| console.log("Created variation 1..."); | ||
|
|
||
| data[0] = experimentID; | ||
|
|
||
| callback(data); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| break; | ||
| } | ||
|
|
||
| } | ||
| } | ||
| }; |
| @@ -0,0 +1,28 @@ | ||
| 'use strict'; | ||
|
|
||
| var access = require('./access.js'); | ||
| var OptimizelyClient = require('optimizely-node-client'); | ||
|
|
||
| //Get and set API token | ||
| var API_TOKEN = access.token; | ||
| var oc = new OptimizelyClient(API_TOKEN); | ||
|
|
||
| //initialize JSON object | ||
| var data = [{}]; | ||
|
|
||
| var projects = oc.getProjects(); | ||
|
|
||
|
|
||
| module.exports = { | ||
| initialize: function (callback) { | ||
| projects.done(function(projectsList) { | ||
| var x=0; | ||
| projectsList.forEach(function(project) { | ||
| var projectObj = {name : project.project_name, id : project.id }; | ||
| data[x] = projectObj; | ||
| x++; | ||
| }); | ||
| callback(data); | ||
| }); | ||
| } | ||
| }; |
| @@ -0,0 +1,79 @@ | ||
| 'use strict'; | ||
|
|
||
| $(document).ready(function() { | ||
|
|
||
| // Show URL form when checkboxes are clicked | ||
| (function() { | ||
| $('#modifications .page input[type="checkbox"]').change(function() { | ||
| $(this).closest('.page').find('.url').toggleClass('hidden'); | ||
| }); | ||
| }()); | ||
|
|
||
|
|
||
| /* | ||
| var projectsList = JSON.stringify(projToClient) | ||
| var projectsListDOM = document.getElementById("projectsList"); | ||
| projectsList.forEach(function(project) { | ||
| var option = document.createElement("option"); | ||
| option.text = project.project_name; | ||
| option.value = project.id; | ||
| projectsListDOM.add(option); | ||
| });*/ | ||
|
|
||
| // Fake progress | ||
| (function() { | ||
| $('#build-funnelcake').submit(function(event) { | ||
| event.preventDefault(); | ||
|
|
||
| $(this).remove(); | ||
|
|
||
| var $statusUL = $('main').append('<section id="status"><h1>Status</h1></section>').find('#status').append('<ul>').find('ul'); | ||
| fakeStatus($statusUL); | ||
|
|
||
| setTimeout(function() { | ||
| var $resultsUL = $('main').append('<section id="results"><h1>Results</h1></section>').find('#results').append('<ul>').find('ul'); | ||
| $resultsUL.append('<li><a href="http://example.com?funnelcake-example-a">Funnelcake</a></li>'); | ||
| $resultsUL.append('<li><a href="http://example.com?funnelcake-example-b">Experiment details</a></li>'); | ||
| $resultsUL.append('<li><a href="http://example.com?funnelcake-example-c">A/B test</a></li>'); | ||
| }, 20000); | ||
| }); | ||
|
|
||
| function fakeStatus($statusUL) { | ||
| var messages = [ | ||
| 'Building funnelcake', | ||
| 'Updating funnelcake master tracker', | ||
| 'Publishing experiment details', | ||
| 'Creating A/B test' | ||
| ]; | ||
|
|
||
| function addMessage() { | ||
| $statusUL.append('<li>' + messages.shift() + '</li>'); | ||
|
|
||
| var dots = 0; | ||
| var addDots = setInterval(function() { | ||
| $statusUL.find('li:last-child').append('.'); | ||
| dots++; | ||
|
|
||
| if (dots === 3) { | ||
| clearInterval(addDots); | ||
| } | ||
| }, 1000); | ||
|
|
||
| setTimeout(function() { | ||
| $statusUL.find('li:last-child').append(' done'); | ||
| }, 4000); | ||
| } | ||
|
|
||
| addMessage(); | ||
| var addMessages = setInterval(function() { | ||
| addMessage(); | ||
|
|
||
| if (messages.length === 0) { | ||
| clearInterval(addMessages); | ||
| } | ||
| }, 5000); | ||
| } | ||
| }()); | ||
|
|
||
| }); |
| @@ -0,0 +1,13 @@ | ||
| // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. | ||
| require('../../js/transition.js') | ||
| require('../../js/alert.js') | ||
| require('../../js/button.js') | ||
| require('../../js/carousel.js') | ||
| require('../../js/collapse.js') | ||
| require('../../js/dropdown.js') | ||
| require('../../js/modal.js') | ||
| require('../../js/tooltip.js') | ||
| require('../../js/popover.js') | ||
| require('../../js/scrollspy.js') | ||
| require('../../js/tab.js') | ||
| require('../../js/affix.js') |
| @@ -0,0 +1,3 @@ | ||
| var access = require('./access') | ||
|
|
||
| console.log(access.token); |
| @@ -0,0 +1,53 @@ | ||
| fieldset { | ||
| padding: 0 10px; | ||
| margin: 0 0 15px; | ||
| } | ||
|
|
||
| .field, .field textarea { | ||
| display: block; | ||
| } | ||
|
|
||
| .field { | ||
| margin: 15px 0; | ||
| } | ||
|
|
||
| .field textarea { | ||
| margin: 5px 0 0; | ||
| } | ||
|
|
||
| #background input[type="text"] { | ||
| display: block; | ||
| } | ||
|
|
||
| .url { | ||
| margin-left: 5px; | ||
| width: 50em; | ||
| } | ||
|
|
||
| .hidden { | ||
| visibility: hidden; | ||
| } | ||
|
|
||
| input[type="submit"] { | ||
| font-size: 1.1em; | ||
| } | ||
|
|
||
| #status ul, #results ul { | ||
| padding-left: 0; | ||
| } | ||
|
|
||
| #status h1 { | ||
| margin-top: 0; | ||
| } | ||
|
|
||
| #status ul { | ||
| list-style-type: none; | ||
| } | ||
|
|
||
| #results ul { | ||
| list-style-position: inside; | ||
| } | ||
|
|
||
| .user-request-wrapper { | ||
| display: block; | ||
| } |
| @@ -0,0 +1,12 @@ | ||
| html, body { | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| body { | ||
| font-family: Helvetica, Arial, sans-serif; | ||
| } | ||
|
|
||
| main { | ||
| padding: 15px; | ||
| } |
| @@ -0,0 +1,295 @@ | ||
| 'use strict'; | ||
|
|
||
| var express = require('express'); | ||
| var router = express.Router(); | ||
|
|
||
| var stepAPI = 0; | ||
| var stepPage = 0; | ||
| var title= "MORALIT.ai"; | ||
|
|
||
| var userRequestsArray = []; | ||
|
|
||
| // requesting root directory | ||
| router.get('/', function(request, response) { | ||
| console.log("index.html woo"); | ||
| response.render('index.html', {request_history: userRequestsArray}); | ||
| }); | ||
|
|
||
| router.post('/', function(request, response) { | ||
| var userInput = request.body.moral_form_input; | ||
|
|
||
| //var result = require('./processEthics')(userInput); | ||
|
|
||
| userRequestsArray.push(userInput); | ||
| response.render('index.html', {request_history: userRequestsArray}); | ||
| }); | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| // requesting expDetails page | ||
| router.get('/expDetails', function(request, response) { | ||
| console.log("test"); | ||
| if(request.session.lastPage) | ||
| { | ||
| if(request.session.lastPage == "index") | ||
| { | ||
| // set the step for the API process | ||
| stepAPI = 1; | ||
|
|
||
| // store that project in memory | ||
| var ProjID = request.query.projects; | ||
| request.session.projectID = request.query.projects; | ||
|
|
||
| // render our page with projects, chosen project, and step | ||
| require('../../process/process.js').initialize(function (goalAudience) { | ||
| // set the step for the API process | ||
| stepAPI = 2; | ||
|
|
||
| // parse goalAudience for our separated goals and audiences | ||
| request.session.goals = parseGoals(goalAudience); | ||
| request.session.audiences = parseAudiences(goalAudience); | ||
|
|
||
| // render the page | ||
| response.render('expDetails.html', {step: stepAPI, audiences: request.session.audiences, goals: request.session.goals, | ||
| exptitle: request.session.expTitle, | ||
| editorURL: request.session.editorURL, | ||
| experimentURL: request.session.experimentURL, | ||
| isRegex: request.session.isRegex, | ||
| pctVisitors: request.session.pctVisitors, | ||
| title: title, brand: brand | ||
| }); | ||
| request.session.lastPage = "/expDetails"; | ||
| stepPage = 2; | ||
| }, | ||
| stepAPI, | ||
| request.session.projectID); | ||
| } | ||
| else | ||
| { | ||
| if(request.session.lastPage == "/advDetails") | ||
| { | ||
| // render the page | ||
| response.render('expDetails.html', {step: stepAPI, audiences: request.session.audiences, goals: request.session.goals, chosenAudience: request.session.audience, chosenGoal: request.session.goal, | ||
| exptitle: request.session.expTitle, | ||
| editorURL: request.session.editorURL, | ||
| experimentURL: request.session.experimentURL, | ||
| isRegex: request.session.isRegex, | ||
| pctVisitors: request.session.pctVisitors, | ||
| title: title, brand: brand | ||
| }); | ||
| request.session.lastPage = "/expDetails"; | ||
| stepPage = 2; | ||
| } | ||
| else | ||
| { | ||
| response.redirect(301, "/"); | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| response.redirect(301, "/"); | ||
| } | ||
|
|
||
| // set the page step | ||
| }); | ||
|
|
||
| router.get('/advDetails', function(request, response) { | ||
| if(request.session.lastPage && request.session.lastPage == "/expDetails") | ||
| { | ||
| stepAPI = 3; | ||
| // get querystring parameters from our form submit and store them in the session | ||
| request.session.expTitle = request.query.exptitle; | ||
| request.session.editorURL = request.query.editorURL; | ||
| request.session.experimentURL = request.query.experimentURL; | ||
| request.session.isRegex = request.query.isRegex; | ||
| request.session.pctVisitors = request.query.pctVisitors; | ||
| // we will default the number of variations to 2 for right now | ||
| // request.session.numVariations = request.query.numVariations; | ||
| request.session.audience = request.query.audience; | ||
| request.session.goal = request.query.goals; | ||
|
|
||
| console.log("experiment title: " +request.session.expTitle); | ||
|
|
||
| // render the page | ||
| response.render('advDetails.html', {step: stepAPI, | ||
| vartitle: request.session.varTitle, | ||
| varPercent: request.session.varPercent, | ||
| customJS: request.session.customJS, | ||
| startTime: request.session.startTime, | ||
| stopTime: request.session.stopTime, | ||
| title: title, brand: brand | ||
| }); | ||
|
|
||
| request.session.lastPage = "/advDetails"; | ||
|
|
||
| stepPage = 3; | ||
| } | ||
| else | ||
| { | ||
| if(request.session.lastPage == "/confirm") | ||
| { | ||
| stepAPI = 3; | ||
| // render the page | ||
| response.render('advDetails.html', {step: stepAPI, | ||
| vartitle: request.session.varTitle, | ||
| varPercent: request.session.varPercent, | ||
| customJS: request.session.customJS, | ||
| startTime: request.session.startTime, | ||
| stopTime: request.session.stopTime, | ||
| title: title, brand: brand | ||
| }); | ||
|
|
||
| request.session.lastPage = "/advDetails"; | ||
|
|
||
| stepPage = 3; | ||
| } | ||
| else | ||
| { | ||
| response.redirect(301, "/"); | ||
| } | ||
| } | ||
|
|
||
| }); | ||
|
|
||
| router.get('/confirm',function(request, response) { | ||
| if(request.session.lastPage == "/advDetails") | ||
| { | ||
| // get querystring parameters from our form submit and store them in the session | ||
| request.session.varTitle = request.query.vartitle; | ||
| request.session.varPercent = request.query.varPercent; | ||
| request.session.customJS = request.query.customJS; | ||
| request.session.startTime = request.query.startTime; | ||
| request.session.stopTime = request.query.stopTime; | ||
|
|
||
| console.log("experiment title: " + request.session.expTitle); | ||
|
|
||
| response.render('confirm.html', {audiences: request.session.audiences, goals: request.session.goals, chosenAudience: request.session.audience, chosenGoal: request.session.goal, | ||
| exptitle: request.session.expTitle, | ||
| editorURL: request.session.editorURL, | ||
| experimentURL: request.session.experimentURL, | ||
| isRegex: request.session.isRegex, | ||
| pctVisitors: request.session.pctVisitors, | ||
| projects: request.session.projects, | ||
| chosenProj: request.session.projectID, | ||
| vartitle: request.session.varTitle, | ||
| varPercent: request.session.varPercent, | ||
| customJS: request.session.customJS, | ||
| startTime: request.session.startTime, | ||
| stopTime: request.session.stopTime, | ||
| title: title, brand: brand | ||
| }); | ||
|
|
||
| request.session.lastPage = "/confirm"; | ||
| } | ||
| else | ||
| { | ||
| response.redirect(301, "/"); | ||
| } | ||
| }); | ||
|
|
||
| router.get('/success',function(request, response) { | ||
| if(request.session.lastPage == "/confirm") | ||
| { | ||
| stepAPI = 4; | ||
|
|
||
| require('../../process/process.js').initialize(function (data) { | ||
| var testURL = "https://app.optimizely.com/edit?experiment_id=" + data[0]; | ||
|
|
||
| response.render('success.html', {testURL: testURL, title: title, brand: brand}); | ||
| }, | ||
| stepAPI, | ||
| request.session.projectID, | ||
| request.session.goal, | ||
| request.session.audience, | ||
| request.session.expTitle, | ||
| request.session.editorURL, | ||
| request.session.experimentURL, | ||
| request.session.isRegex, | ||
| request.session.pctVisitors, | ||
| request.session.varTitle, | ||
| request.session.varPercent, | ||
| request.session.customJS, | ||
| request.session.startTime, | ||
| request.session.stopTime); | ||
| } | ||
| else | ||
| { | ||
| response.redirect(301, "/"); | ||
| } | ||
| }); | ||
|
|
||
| // parseGoals function | ||
| // Purpose: To parse our goalAudience array for values (our goals) before our sentinal (*) | ||
| function parseGoals(goalAudience) | ||
| { | ||
| var goals = []; | ||
|
|
||
| for(var x=0; x<goalAudience.length; x++) { | ||
| if(goalAudience[x].name == "*") | ||
| { | ||
| return goals; | ||
| } | ||
| else | ||
| { | ||
| goals.push(goalAudience[x]); | ||
| } | ||
| } | ||
|
|
||
| return goals; | ||
| } | ||
|
|
||
| // parseGoals function | ||
| // Purpose: To parse our goalAudience array for values (our audiences) after our sentinal (*) | ||
| function parseAudiences(goalAudience) | ||
| { | ||
| var audiences = []; | ||
| var isAudiences = false; | ||
|
|
||
| for(var x=0; x<goalAudience.length; x++) { | ||
| if(goalAudience[x].name == "*") | ||
| { | ||
| isAudiences = true; | ||
| } | ||
| else | ||
| { | ||
| if(isAudiences) { | ||
| audiences.push(goalAudience[x]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return audiences; | ||
| } | ||
|
|
||
| module.exports = router; |
| @@ -0,0 +1,63 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block styles %} | ||
| <link rel="stylesheet" href="/assets/styles/index.css" /> | ||
| {% endblock %} | ||
|
|
||
| {% block breadcrumb %} | ||
| <li><a href="/">Project Selection</a></li> | ||
| <li><a href="/expDetails">Experiment Details</a></li> | ||
| <li class="active"><a>Variation & Schedule</a></li> | ||
| {% endblock %} | ||
|
|
||
| {% block head %} | ||
| <h1>Variation & Schedule</h1> | ||
| <p>Define your variation details and add a schedule (optional).</p> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <form id="build-funnelcake" action="/confirm/" method="get"> | ||
| <fieldset id="background"> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Variation Title:</p> | ||
| </div> | ||
| <input autofocus required placeholder="FC73 Prerelease" name="vartitle" style="width:100%;" type="text" value="{{vartitle}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Percentage Included:</p> | ||
| </div> | ||
| <input required placeholder="50" min="0" max="100" type="number" name="varPercent" style="width:100%;" value="{{varPercent}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Custom Javascript:</p> | ||
| </div> | ||
| <textarea placeholder="$(".os_win > .download-link").attr({"href":"https://download.mozilla.org/?product=firefox-42.0-SSL-f60&os=win&lang=en-US"});" name="customJS" rows="4" cols="150">{{ customJS }}</textarea> | ||
| </div> | ||
| </fieldset> | ||
| <fieldset id="background"> | ||
| <legend>Schedule (Optional)</legend> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Start Time:</p> | ||
| </div> | ||
| <input placeholder="2016-1-10T08:00:00Z" name="startTime" style="width:100%;" type="text" value="{{startTime}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Stop Time:</p> | ||
| </div> | ||
| <input placeholder="2016-12-20T08:00:00Z" name="stopTime" style="width:100%;" type="text" value="{{stopTime}}"> | ||
| </input> | ||
| </div> | ||
| </fieldset> | ||
| <input type="button" name="formSubmits" value="Back" onclick="window.location='/expDetails';" /> | ||
| <input type="submit" value="Next" /> | ||
| </form> | ||
| {% endblock %} | ||
|
|
| @@ -0,0 +1,25 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en-US" dir="ltr"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| {% if title %} | ||
| <title>{{ title }} </title> | ||
| {% endif %} | ||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css"> | ||
| {% block styles %}{% endblock %} | ||
| </head> | ||
| <body> | ||
| <main> | ||
| <div class="container"> | ||
| <div style="margin-bottom: 20px;"> | ||
| {% block head %}{% endblock %} | ||
| </div> | ||
|
|
||
| {% block content %}{% endblock %} | ||
| </div> | ||
| </main> | ||
| <script src="/lib/jquery/dist/jquery.min.js"></script> | ||
| <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script> | ||
| {% block scripts %}{% endblock %} | ||
| </body> | ||
| </html> |
| @@ -0,0 +1,160 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block styles %} | ||
| <link rel="stylesheet" href="/assets/styles/index.css" /> | ||
| {% endblock %} | ||
|
|
||
| {% block breadcrumb %} | ||
| <li><a href="/">Project Selection</a></li> | ||
| <li><a href="/expDetails">Experiment Details</a></li> | ||
| <li><a href="/advDetails">Variation & Schedule</a></li> | ||
| <li class="active"><a>Build Confirmation</a></li> | ||
| {% endblock %} | ||
|
|
||
| {% block head %} | ||
| <h1>Build Confirmation</h1> | ||
| <p>Check that your experiment details are correct, and then hit build!</p> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <form id="build-funnelcake" action="/success/" method="get"> | ||
| <fieldset id="background"> | ||
| <div class="field"> | ||
| <p>Project:</p> | ||
| <select disabled name="projects" id="projectsList"> | ||
| {% if chosenProj %} | ||
| {% for project in projects %} | ||
| {% if project.id == chosenProj %} | ||
| <option value={{project.id}} selected>{{ project.name }}</option> | ||
| {% else %} | ||
| <option value={{project.id}}>{{ project.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO PROJ FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for project in projects %} | ||
| <option value={{project.id}}>{{ project.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO PROJ FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
|
|
||
| </select> | ||
| </div> | ||
| <div class="field"> | ||
| <p>Experiment Title:</p> | ||
| <input disabled style="width: 100%;" name="exptitle" type="text" value="{{exptitle}}" /> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <p>Editor URL:</p> | ||
| <input disabled name="editorURL" type="text" value="{{editorURL}}"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <p>Experiment URL:</p> | ||
| <input disabled name="experimentURL" type="text" value="{{experimentURL}}"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <p>Experiment URL is Regular Expression:</p> | ||
| <input disabled name="isRegex" type="text" value="{{isRegex}}"> | ||
| </div> | ||
| <div class="field"> | ||
| <p>Audience:</p> | ||
| <select name="audience" id="audienceList" disabled> | ||
| {% if chosenAudience %} | ||
| {% for audience in audiences %} | ||
| {% if audience.id == chosenAudience %} | ||
| <option value={{audience.id}} selected>{{ audience.name }}</option> | ||
| {% else %} | ||
| <option value={{audience.id}}>{{ audience.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO AUDIENCE FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for audience in audiences %} | ||
| <option value={{audience.id}}>{{ audience.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO AUDIENCE FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
|
|
||
| </select> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <p>Goal:</p> | ||
| <select name="goals" id="goalsList" disabled> | ||
| {% if chosenGoal %} | ||
| {% for goal in goals %} | ||
| {% if goal.id == chosenGoal %} | ||
| <option value={{goal.id}} selected>{{ goal.name }}</option> | ||
| {% else %} | ||
| <option value={{goal.id}}>{{ goal.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO GOAL FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for goal in goals %} | ||
| <option value={{goal.id}}>{{ goal.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO GOAL FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
|
|
||
| </select> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <p>Percentage of Visitors Included in Experiment:</p> | ||
| <input disabled name="pctVisitors" type="text" value="{{pctVisitors}}"> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Variation Title:</p> | ||
| </div> | ||
| <input disabled name="vartitle" type="text" value="{{vartitle}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Percentage Included:</p> | ||
| </div> | ||
| <input disabled name="varPercent" type="text" value="{{varPercent}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Custom Javascript:</p> | ||
| </div> | ||
| <input disabled name="customJS" type="text" value="{{customJS}}"> | ||
| </input> | ||
| </div> | ||
| {% if startTime or stopTime %} | ||
| <legend>Schedule (Optional)</legend> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Start Time:</p> | ||
| </div> | ||
| <input disabled name="startTime" type="text" value="{{startTime}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Stop Time:</p> | ||
| </div> | ||
| <input disabled name="stopTime" type="text" value="{{stopTime}}"> | ||
| </input> | ||
| </div> | ||
| </fieldset> | ||
| {% endif %} | ||
| <input type="button" name="formSubmit" value="Back" onclick="window.location='/advDetails';" /> | ||
| <input type="button" value="Build Funnelcake" onclick="window.location='/success';" /> | ||
| </form> | ||
| {% endblock %} | ||
|
|
| @@ -0,0 +1,9 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block content %} | ||
| <p>{{ message }}</p> | ||
|
|
||
| {% if error %} | ||
| <pre>{{ error }}</pre> | ||
| {% endif %} | ||
| {% endblock %} |
| @@ -0,0 +1,129 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block styles %} | ||
| <link rel="stylesheet" href="/assets/styles/index.css" /> | ||
| {% endblock %} | ||
|
|
||
| {% block breadcrumb %} | ||
| <li><a href="/">Project Selection</a></li> | ||
| <li class="active"><a>Experiment Details</a></li> | ||
| {% endblock %} | ||
|
|
||
| {% block head %} | ||
| <h1>Experiment Details</h1> | ||
| <p>Now define your experiment specifics.</p> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <form id="build-funnelcake" action="/advDetails/" method="get"> | ||
| <fieldset id="background"> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Experiment Title:</p> | ||
| </div> | ||
| <input autofocus required placeholder="[bug 20400] New-tab Funnelcake Build" style="width:100%;" name="exptitle" type="text" value="{{exptitle}}"> | ||
| </input> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Editor URL:</p> | ||
| </div> | ||
| <input type="url" placeholder="https://www.mozilla.org/en-US/firefox/new/" style="width:100%;" name="editorURL" value="{{editorURL}}"> | ||
| </input> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Experiment URL:</p> | ||
| </div> | ||
| <input required placeholder="^https:\/\/www\.mozilla\.org\/en-US/firefox\/new\/$" style="width:100%;" name="experimentURL" type="text" value="{{experimentURL}}"> | ||
| </input> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Experiment URL is Regular Expression (true/false):</p> | ||
| </div> | ||
| <input required placeholder="true" name="isRegex" type="text" style="width:100%;" value="{{isRegex}}"> | ||
| </input> | ||
| </div> | ||
| <div class="field"> | ||
| <div> | ||
| <p>Audience:</p> | ||
| </div> | ||
| <select name="audience" id="audienceList"> | ||
| {% if chosenAudience %} | ||
| {% for audience in audiences %} | ||
| {% if audience.id == chosenAudience %} | ||
| <option value={{audience.id}} selected>{{ audience.name }}</option> | ||
| {% else %} | ||
| <option value={{audience.id}}>{{ audience.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO AUDIENCE FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for audience in audiences %} | ||
| <option value={{audience.id}}>{{ audience.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO AUDIENCE FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
|
|
||
| </select> | ||
| </div> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Goal:</p> | ||
| </div> | ||
| <select name="goals" id="goalsList"> | ||
| {% if chosenGoal %} | ||
| {% for goal in goals %} | ||
| {% if goal.id == chosenGoal %} | ||
| <option value={{goal.id}} selected>{{ goal.name }}</option> | ||
| {% else %} | ||
| <option value={{goal.id}}>{{ goal.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO GOAL FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for goal in goals %} | ||
| <option value={{goal.id}}>{{ goal.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO GOAL FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
|
|
||
| </select> | ||
| </div> | ||
|
|
||
| <!-- We are defaulting the number of variations to 2 for now | ||
| <div class="field"> | ||
| <div> | ||
| <p>Number of Variations:</p> | ||
| </div> | ||
| <input name="numVariations" type="text"> | ||
| {{ numVariations }} | ||
| </input> | ||
| </div> | ||
| --> | ||
|
|
||
| <div class="field"> | ||
| <div> | ||
| <p>Percentage of Visitors Included in Experiment:</p> | ||
| </div> | ||
| <input required placeholder="30" min="0" max="100" type="number" name="pctVisitors" style="width:100%;" value="{{pctVisitors}}"> | ||
| </input> | ||
| </div> | ||
|
|
||
| </fieldset> | ||
| <input type="button" name="formSubmit" value="Back" onclick="window.location='/';" /> | ||
| <input type="submit" name="formSubmit" value="Next" /> | ||
| </form> | ||
| {% endblock %} | ||
|
|
| @@ -0,0 +1,49 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block styles %} | ||
| <link rel="stylesheet" href="/assets/styles/index.css" /> | ||
| {% endblock %} | ||
|
|
||
| {% block head %} | ||
| <h1><span class=" glyphicon glyphicon-modal-window" aria-hidden="true"></span>MORALIT.ai</h1> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <form id="build-funnelcake" action="/" method="post"> | ||
| <div id="step1"> | ||
| <!-- <legend>Project</legend> | ||
| <div class="field"> | ||
| <select autofocus name="projects" id="projectsList"> | ||
| {% if chosenProj %} | ||
| {% for project in projects %} | ||
| {% if project.id == chosenProj %} | ||
| <option value={{project.id}} selected>{{ project.name }}</option> | ||
| {% else %} | ||
| <option value={{project.id}}>{{ project.name }}</option> | ||
| {% endif %} | ||
| {% else %} | ||
| <option value="1">ERROR: NO PROJ FOUND</option> | ||
| {% endfor %} | ||
| {% else %} | ||
| {% for project in projects %} | ||
| <option value={{project.id}}>{{ project.name }}</option> | ||
| {% else %} | ||
| <option value="1">ERROR: NO PROJ FOUND</option> | ||
| {% endfor %} | ||
| {% endif %} | ||
| </select> | ||
| </div> --> | ||
|
|
||
| is it moral ? <input type="text" name="moral_form_input"><br> | ||
| <input type="submit" value="Submit"> | ||
|
|
||
| {% if request_history %} | ||
| {% for request in request_history %} | ||
| <div class ="user-request-wrapper">{{ request }}</div> | ||
| {% endfor %} | ||
| {% endif %} | ||
| </div> | ||
| </form> | ||
| {% endblock %} | ||
|
|
| @@ -0,0 +1,23 @@ | ||
| {% extends 'base.html' %} | ||
|
|
||
| {% block styles %} | ||
| <link rel="stylesheet" href="/assets/styles/index.css" /> | ||
| {% endblock %} | ||
|
|
||
| {% block breadcrumb %} | ||
| <li><a href="/">Project Selection</a></li> | ||
| <li><a href="/expDetails">Experiment Details</a></li> | ||
| <li><a href="/advDetails">Variation & Schedule</a></li> | ||
| <li><a href="/confirm">Build Confirmation</a></li> | ||
| <li class="active"><a>Success!</a></li> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <div> | ||
| <center> | ||
| <h1 style="font-size:100px;"><span class="glyphicon glyphicon-saved" aria-hidden="true"></span></h1> | ||
| <h1>Success!</h1> | ||
| <a href={{testURL}}>View your funnelcake live.</a> | ||
| </center> | ||
| </div> | ||
| {% endblock %} |