373 LICENSE

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -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'

51 app.js
@@ -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);
});
}
};
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -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);

Large diffs are not rendered by default.

@@ -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="$(&quot.os_win > .download-link&quot).attr({&quothref&quot:&quothttps://download.mozilla.org/?product=firefox-42.0-SSL-f60&os=win&lang=en-US&quot});" 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 %}