<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    Card = mongoose.model('Card'),
    Pack = mongoose.model('Pack'),
    _ = require('lodash');

/**
 * Get the error message from error object
 */
var getErrorMessage = function (err) {
    var message = '';

    if (err.code) {
        switch (err.code) {
            case 11000:
            case 11001:
                message = 'Card already exists';
                break;
            default:
                message = 'Something went wrong';
        }
    } else {
        for (var errName in err.errors) {
            if (err.errors[errName].message) message = err.errors[errName].message;
        }
    }

    return message;
};

/**
 * Create a Card
 */
exports.create = function (req, res) {
    var card = new Card(req.body);

    if (req.body.userId) {
        card.user = req.body.userId;
    } else {
        card.user = req.user;
    }

    card.save(function (err) {
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * Show the current Card
 */
exports.read = function (req, res) {
    res.jsonp(req.card);
};

/**
 * Update a Card
 */
exports.update = function (req, res) {
    var card = req.card;

    console.log(req.body.starDate);

    card.__v = undefined;
    card = _.extend(card, req.body);

    if (!req.body.startDate) {
        card.startDate = undefined;
    }

    if (!req.body.dueDate) {
        card.dueDate = undefined;
    }

    if (!req.body.answer) {
        card.answer = undefined;
    }

    if (!req.body.question) {
        card.question = undefined;
    }

    card.slaves.forEach(function (cid) {

        var findCard = Card.find({'_id': cid}).exec(function (err) {
            if (err) {
                console.log('Error: ' + err);
            }
        });

        findCard.then(function (findCardResult) {
            var c = findCardResult[0];
            //c = _.extend(c, req.body);

            c.question = card.question;
            c.validanswers = card.validanswers;
            c.invalidanswers = card.invalidanswers;
            c.validreverseanswers = card.validreverseanswers;
            c.invalidreverseanswers = card.invalidreverseanswers;
            c.answer = card.answer;
            c.due = card.due;
            c.after = card.after;
            c.validation = card.validation;
            c.images = card.images;
            c.bothways = card.bothways;
            c.alternatives = card.alternatives;
            c.alternativequestions = card.alternativequestions;
            c.sound = card.sound;
            c.soundback = card.soundback;
            c.format = card.format;
            c.type = card.type;
            c.save();

        });
    });

    card.save(function (err) {
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * Delete an Card
 */
exports.delete = function (req, res) {

    var card = req.card;

    function deleteCardFromItsPack(card) {
        card.packs.forEach(function (pid) {
            var findPack = Pack.find({'_id': pid}).exec(function (err) {
                if (err) {
                    console.log('Error: ' + err);
                }
            });

            findPack.then(function (findPackResult) {
                for (var i = 0; i < findPackResult[0].cards.length; i++) {
                    if (findPackResult[0].cards[i].toString() === card._id.toString()) {
                        findPackResult[0].cards.splice(i, 1);
                    }

                }
//                console.log('pack is now: ' + findPackResult[0].cards);
                findPackResult[0].save();
            });
        });
    }

    card.slaves.forEach(function (cid) {

        var findCard = Card.find({'_id': cid}).exec(function (err) {
            if (err) {
                console.log('Error: ' + err);
            }
        });

        findCard.then(function (findCardResult) {

            deleteCardFromItsPack(findCardResult[0]);

//            console.log('removing slave: ' + findCardResult[0]._id);
            findCardResult.remove();
        });
    });

    deleteCardFromItsPack(card);

//    console.log('removing original: ' + card._id);
    card.remove(function (err) {
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * List of Cards
 */
exports.list = function (req, res) {

    if (req.query._id) {

        Card.find({'_id': req.query._id}).exec(function (err, packs) {
            if (err) {
                return res.send(400, {
                    message: getErrorMessage(err)
                });
            } else {
                res.jsonp(packs);
            }
        });
    } else {

        Card.find().sort('-created').populate('user', 'displayName').exec(function (err, cards) {
            if (err) {
                return res.send(400, {
                    message: getErrorMessage(err)
                });
            } else {
                res.jsonp(cards);
            }
        });
    }
};

/**
 * Card middleware
 */
exports.cardByID = function (req, res, next, id) {
    Card.findById(id).populate('user', 'displayName').exec(function (err, card) {
        if (err) return next(err);
        if (!card) return next(new Error('Failed to load Card ' + id));
        req.card = card;
        next();
    });
};

/**
 * Card authorization middleware
 */
exports.hasAuthorization = function (req, res, next) {

    if (req.user.roles.indexOf('admin') > -1) {
        next();
    } else if (req.card.user && (req.card.user._id !== req.user._id)) {
        return res.send(403, 'User is not authorized');
    } else {
        next();
    }
};<commit_msg>debugging card update in production<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    Card = mongoose.model('Card'),
    Pack = mongoose.model('Pack'),
    _ = require('lodash');

/**
 * Get the error message from error object
 */
var getErrorMessage = function (err) {
    var message = '';

    if (err.code) {
        switch (err.code) {
            case 11000:
            case 11001:
                message = 'Card already exists';
                break;
            default:
                message = 'Something went wrong '+err;
        }
    } else {
        for (var errName in err.errors) {
            if (err.errors[errName].message) message = err.errors[errName].message;
        }
    }

    return message;
};

/**
 * Create a Card
 */
exports.create = function (req, res) {
    var card = new Card(req.body);

    if (req.body.userId) {
        card.user = req.body.userId;
    } else {
        card.user = req.user;
    }

    card.save(function (err) {
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * Show the current Card
 */
exports.read = function (req, res) {
    res.jsonp(req.card);
};

/**
 * Update a Card
 */
exports.update = function (req, res) {
    var card = req.card;

    console.log('updating card');

    card.__v = undefined;
    card = _.extend(card, req.body);

    if (!req.body.startDate) {
        card.startDate = undefined;
    }

    if (!req.body.dueDate) {
        card.dueDate = undefined;
    }

    if (!req.body.answer) {
        card.answer = undefined;
    }

    if (!req.body.question) {
        card.question = undefined;
    }

    console.log('after update');
    console.log(card);

    card.slaves.forEach(function (cid) {

        var findCard = Card.find({'_id': cid}).exec(function (err) {
            if (err) {
                console.log('Error: ' + err);
            }
        });

        findCard.then(function (findCardResult) {
            var c = findCardResult[0];
            //c = _.extend(c, req.body);

            c.question = card.question;
            c.validanswers = card.validanswers;
            c.invalidanswers = card.invalidanswers;
            c.validreverseanswers = card.validreverseanswers;
            c.invalidreverseanswers = card.invalidreverseanswers;
            c.answer = card.answer;
            c.due = card.due;
            c.after = card.after;
            c.validation = card.validation;
            c.images = card.images;
            c.bothways = card.bothways;
            c.alternatives = card.alternatives;
            c.alternativequestions = card.alternativequestions;
            c.sound = card.sound;
            c.soundback = card.soundback;
            c.format = card.format;
            c.type = card.type;
            c.save();

        });
    });

    console.log('saving');

    card.save(function (err) {
        console.log(err);
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * Delete an Card
 */
exports.delete = function (req, res) {

    var card = req.card;

    function deleteCardFromItsPack(card) {
        card.packs.forEach(function (pid) {
            var findPack = Pack.find({'_id': pid}).exec(function (err) {
                if (err) {
                    console.log('Error: ' + err);
                }
            });

            findPack.then(function (findPackResult) {
                for (var i = 0; i < findPackResult[0].cards.length; i++) {
                    if (findPackResult[0].cards[i].toString() === card._id.toString()) {
                        findPackResult[0].cards.splice(i, 1);
                    }

                }
//                console.log('pack is now: ' + findPackResult[0].cards);
                findPackResult[0].save();
            });
        });
    }

    card.slaves.forEach(function (cid) {

        var findCard = Card.find({'_id': cid}).exec(function (err) {
            if (err) {
                console.log('Error: ' + err);
            }
        });

        findCard.then(function (findCardResult) {

            deleteCardFromItsPack(findCardResult[0]);

//            console.log('removing slave: ' + findCardResult[0]._id);
            findCardResult.remove();
        });
    });

    deleteCardFromItsPack(card);

//    console.log('removing original: ' + card._id);
    card.remove(function (err) {
        if (err) {
            return res.send(400, {
                message: getErrorMessage(err)
            });
        } else {
            res.jsonp(card);
        }
    });
};

/**
 * List of Cards
 */
exports.list = function (req, res) {

    if (req.query._id) {

        Card.find({'_id': req.query._id}).exec(function (err, packs) {
            if (err) {
                return res.send(400, {
                    message: getErrorMessage(err)
                });
            } else {
                res.jsonp(packs);
            }
        });
    } else {

        Card.find().sort('-created').populate('user', 'displayName').exec(function (err, cards) {
            if (err) {
                return res.send(400, {
                    message: getErrorMessage(err)
                });
            } else {
                res.jsonp(cards);
            }
        });
    }
};

/**
 * Card middleware
 */
exports.cardByID = function (req, res, next, id) {
    Card.findById(id).populate('user', 'displayName').exec(function (err, card) {
        if (err) return next(err);
        if (!card) return next(new Error('Failed to load Card ' + id));
        req.card = card;
        next();
    });
};

/**
 * Card authorization middleware
 */
exports.hasAuthorization = function (req, res, next) {

    if (req.user.roles.indexOf('admin') > -1) {
        next();
    } else if (req.card.user && (req.card.user._id !== req.user._id)) {
        return res.send(403, 'User is not authorized');
    } else {
        next();
    }
};<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	errorHandler = require('./errors'),
	Game = mongoose.model('Game'),
	_ = require('lodash');

function getGameAdapter(type) {
	var path = './games/games.' + type;
	return require(path);
}

/**
 * Create a Game
 */
exports.create = function(req, res) {
	var game = new Game(req.body);
	game.current_thrower = game.player1;

	// Verify that the current user is at least one of
	// the players in the game.
	if(req.user._id !== req.body.player1._id &&
		req.user._id !== req.body.player2._id) {
		return res.status(400).send({
			message: 'You must be one of the players in the created game.'
		});
	}

	var adapter = getGameAdapter(game.game_type.toLowerCase());
	game.scoreboard = adapter.createScoreboard();

	game.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			// For the frontend, we need the populated values
			// for the display name as well now that we know
			// the ID of the winning player.
			if (game.winner !== null) {

				// todo Figure out how to properly get this when the game
				// just finishes
				game.winner = game.current_thrower;
			}
			res.jsonp(game);
		}
	});
};

/**
 * Show the current Game
 */
exports.read = function(req, res) {
	res.jsonp(req.game);
};

/**
 * Update a Game
 */
exports.update = function(req, res) {
	var game = req.game ;

	var adapter = getGameAdapter(game.game_type.toLowerCase());
	game = adapter.updateGameWithRound(req.body.round, game);

    game.current_thrower = (game.current_thrower.id === game.player1.id) ? game.player2 : game.player1;

	//game = _.extend(game , updated_game);

	game.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(game);
		}
	});
};

/**
 * Delete an Game
 */
exports.delete = function(req, res) {
	var game = req.game ;

	game.remove(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(game);
		}
	});
};

/**
 * List of Games
 */
exports.list = function(req, res) { Game.find().sort('-created').
	populate('player1', 'displayName').
	populate('player2', 'displayName').
	populate('current_thrower', 'displayName').
	populate('winner', 'displayName').
	exec(function(err, games) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(games);
		}
	});
};

/**
 * Game middleware
 */
exports.gameByID = function(req, res, next, id) { Game.findById(id).
	populate('player1', 'displayName').
	populate('player2', 'displayName').
	populate('current_thrower', 'displayName').
	populate('winner', 'displayName').
	exec(function(err, game) {
		if (err) return next(err);
		if (! game) return next(new Error('Failed to load Game ' + id));
		console.log(game);
		req.game = game ;
		next();
	});
};

/**
 * Game authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
	if (req.game.player1.id !== req.user.id && req.game.player2.id !== req.user.id) {
		return res.status(403).send('User is not authorized');
	}
	next();
};<commit_msg>Correct logic for checking current user is one of the players in created game.<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	errorHandler = require('./errors'),
	Game = mongoose.model('Game'),
	_ = require('lodash');

function getGameAdapter(type) {
	var path = './games/games.' + type;
	return require(path);
}

/**
 * Create a Game
 */
exports.create = function(req, res) {
	var game = new Game(req.body);
	game.current_thrower = game.player1;

	// Verify that the current user is at least one of
	// the players in the game.
	console.log(req.user._id);
	console.log(req.body.player1);
	console.log(req.body.player2);
	if(parseInt(req.user._id) !== parseInt(req.body.player1) &&
		parseInt(req.user._id) !== parseInt(req.body.player2)) {
		return res.status(400).send({
			message: 'You must be one of the players in the created game.'
		});
	}

	var adapter = getGameAdapter(game.game_type.toLowerCase());
	game.scoreboard = adapter.createScoreboard();

	game.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			// For the frontend, we need the populated values
			// for the display name as well now that we know
			// the ID of the winning player.
			if (game.winner !== null) {

				// todo Figure out how to properly get this when the game
				// just finishes
				game.winner = game.current_thrower;
			}
			res.jsonp(game);
		}
	});
};

/**
 * Show the current Game
 */
exports.read = function(req, res) {
	res.jsonp(req.game);
};

/**
 * Update a Game
 */
exports.update = function(req, res) {
	var game = req.game ;

	var adapter = getGameAdapter(game.game_type.toLowerCase());
	game = adapter.updateGameWithRound(req.body.round, game);

    game.current_thrower = (game.current_thrower.id === game.player1.id) ? game.player2 : game.player1;

	//game = _.extend(game , updated_game);

	game.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(game);
		}
	});
};

/**
 * Delete an Game
 */
exports.delete = function(req, res) {
	var game = req.game ;

	game.remove(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(game);
		}
	});
};

/**
 * List of Games
 */
exports.list = function(req, res) { Game.find().sort('-created').
	populate('player1', 'displayName').
	populate('player2', 'displayName').
	populate('current_thrower', 'displayName').
	populate('winner', 'displayName').
	exec(function(err, games) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(games);
		}
	});
};

/**
 * Game middleware
 */
exports.gameByID = function(req, res, next, id) { Game.findById(id).
	populate('player1', 'displayName').
	populate('player2', 'displayName').
	populate('current_thrower', 'displayName').
	populate('winner', 'displayName').
	exec(function(err, game) {
		if (err) return next(err);
		if (! game) return next(new Error('Failed to load Game ' + id));
		console.log(game);
		req.game = game ;
		next();
	});
};

/**
 * Game authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
	if (req.game.player1.id !== req.user.id && req.game.player2.id !== req.user.id) {
		return res.status(403).send('User is not authorized');
	}
	next();
};<|endoftext|>


<commit_before>/* eslint-env mocha */
/* global expect, testContext */
/* eslint-disable prefer-arrow-callback, no-unused-expressions, max-nested-callbacks */

import getProfiler from 'src/server/services/projectFormationService/profile'
import {teamFormationPlanToString} from 'src/server/services/projectFormationService/teamFormationPlan'
import {buildTestPool} from 'src/server/services/projectFormationService/__tests__/testHelpers'

import getTeamFormationPlan from '../getTeamFormationPlan'

describe(testContext(__filename), function () {
  it('works when everyone votes for the same goal', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},
        {playerId: 'p5', votes: ['g1', 'g2']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 4},
        {goalDescriptor: 'g2', teamSize: 4},
        {goalDescriptor: 'g3', teamSize: 4},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 3}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    teams.forEach(team => {
      expect(team.goalDescriptor).to.eq('g1')
      expect(team.playerIds).to.have.length(4)
      expect(
        team.playerIds.includes('A0') ||
        team.playerIds.includes('A1'),
        'team includes an advanced player'
      ).to.be.ok
    })
  })

  it('respects the advancedPlayers maxTeams if present', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p2', votes: ['g2', 'g2']},
        {playerId: 'p3', votes: ['g2', 'g1']},
        {playerId: 'p4', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 3},
        {goalDescriptor: 'g2', teamSize: 3},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(
      _teamCountFor('A0', teams),
      'Advanced Player 0 is given the appropriate number of teams'
    ).to.eq(2)

    expect(
      _teamCountFor('A1', teams),
      'Advanced Player 0 is given the appropriate number of teams'
    ).to.eq(1)
  })

  it('works when two goals tie for most popular', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p3', votes: ['g2', 'g1']},
        {playerId: 'p4', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 4},
        {goalDescriptor: 'g2', teamSize: 4},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 3}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    for (const goalDescriptor of ['g1', 'g2']) {
      const team = teams.find(_ => _.goalDescriptor === goalDescriptor)

      expect(team, 'a team is formed for each most popular goal').to.be
      expect(team.playerIds, 'each team has an advanced player').to.match(/A/)
      expect(team.playerIds, 'each team has an advanced player').to.match(/A/)
      expect(team.playerIds, 'each team gets half the players').to.have.length(4)
    }
  })

  it('will put an advanced player on multiple teams if needed', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},

        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p7', votes: ['g2', 'g1']},
        {playerId: 'p8', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 3},
        {goalDescriptor: 'g2', teamSize: 3},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(3)

    const [team1, team2] = teams.filter(_ => _.goalDescriptor === 'g1')
    const [team3] = teams.filter(_ => _.goalDescriptor === 'g2')

    expect(team1, 'two teams are formed with goal g1').to.be.ok
    expect(team2, 'two teams are formed with goal g1').to.be.ok
    expect(team3, 'one team is formed with goal g2').to.be.ok
    expect(team1.playerIds).to.include('A0')
    expect(team2.playerIds).to.include('A0')
    expect(team3.playerIds).to.include('A1')
  })

  it.skip('will put multiple advanced players on a team if needed', function () {
    // TODO: make this pass
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},

        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'A2', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
        {playerId: 'p6', votes: ['g2', 'g1']},
        {playerId: 'p7', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 5},
        {goalDescriptor: 'g2', teamSize: 5},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}, {id: 'A2', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    const [team1] = teams.filter(_ => _.goalDescriptor === 'g1')
    const [team2] = teams.filter(_ => _.goalDescriptor === 'g2')

    expect(team1, 'one team is formed with goal g1').to.be.ok
    expect(team2, 'one team is formed with goal g2').to.be.ok
    expect(team1.playerIds).to.include('A0')
    expect(team2.playerIds).to.include('A1')
    expect(team2.playerIds).to.include('A2')
  })

  describe.skip('performance tests', function () {
    beforeEach(function () {
      getProfiler().reset()
      console.log(new Date())
    })

    const minutes = n => n * 60000
    const scenarios = [
      // 0
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 15, teamSize: 4, goalCount: 5}),
        expectedRuntime: minutes(0.5),
        minResultScore: 0.95,
      },
      // 1
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 2
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 30, teamSize: 4, goalCount: 5}),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 3
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 30, teamSize: 4, goalCount: 12}),
        expectedRuntime: minutes(6),
        minResultScore: 0.925,
      },
      // 4
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 5
      {
        pool: buildTestPool({
          advancedPlayerCount: 9,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 28,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.85,
      },
      // 6
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 12,
        }),
        expectedRuntime: minutes(11),
        minResultScore: 0.95,
      },
    ]

    scenarios.forEach(({pool, minResultScore, expectedRuntime}, i) => {
      it(`completes scenatio [${i}] in the expected time`, function () {
        this.timeout(expectedRuntime + minutes(1))
        const start = Date.now()

        const teamFormationPlan = getTeamFormationPlan(pool)
        console.log('result:', teamFormationPlanToString(teamFormationPlan), teamFormationPlan.score)

        const elapsedMilliseconds = Date.now() - start
        console.log('scenario', i, 'completed in', (elapsedMilliseconds / 60000).toFixed(2), 'minutes')

        expect(elapsedMilliseconds).to.be.lt(expectedRuntime)
        expect(teamFormationPlan.score).to.be.gt(minResultScore)
      })
    })

    afterEach(function () {
      getProfiler().report()
    })
  })
})

function _teamCountFor(playerId, teams) {
  return teams.reduce(
    (result, {playerIds}) => playerIds.includes(playerId) ? result + 1 : result, 0
  )
}
<commit_msg>tweak performance test expected runtimes<commit_after>/* eslint-env mocha */
/* global expect, testContext */
/* eslint-disable prefer-arrow-callback, no-unused-expressions, max-nested-callbacks */

import getProfiler from 'src/server/services/projectFormationService/profile'
import {teamFormationPlanToString} from 'src/server/services/projectFormationService/teamFormationPlan'
import {buildTestPool} from 'src/server/services/projectFormationService/__tests__/testHelpers'

import getTeamFormationPlan from '../getTeamFormationPlan'

describe(testContext(__filename), function () {
  it('works when everyone votes for the same goal', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},
        {playerId: 'p5', votes: ['g1', 'g2']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 4},
        {goalDescriptor: 'g2', teamSize: 4},
        {goalDescriptor: 'g3', teamSize: 4},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 3}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    teams.forEach(team => {
      expect(team.goalDescriptor).to.eq('g1')
      expect(team.playerIds).to.have.length(4)
      expect(
        team.playerIds.includes('A0') ||
        team.playerIds.includes('A1'),
        'team includes an advanced player'
      ).to.be.ok
    })
  })

  it('respects the advancedPlayers maxTeams if present', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p2', votes: ['g2', 'g2']},
        {playerId: 'p3', votes: ['g2', 'g1']},
        {playerId: 'p4', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 3},
        {goalDescriptor: 'g2', teamSize: 3},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(
      _teamCountFor('A0', teams),
      'Advanced Player 0 is given the appropriate number of teams'
    ).to.eq(2)

    expect(
      _teamCountFor('A1', teams),
      'Advanced Player 0 is given the appropriate number of teams'
    ).to.eq(1)
  })

  it('works when two goals tie for most popular', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p3', votes: ['g2', 'g1']},
        {playerId: 'p4', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 4},
        {goalDescriptor: 'g2', teamSize: 4},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 3}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    for (const goalDescriptor of ['g1', 'g2']) {
      const team = teams.find(_ => _.goalDescriptor === goalDescriptor)

      expect(team, 'a team is formed for each most popular goal').to.be
      expect(team.playerIds, 'each team has an advanced player').to.match(/A/)
      expect(team.playerIds, 'each team has an advanced player').to.match(/A/)
      expect(team.playerIds, 'each team gets half the players').to.have.length(4)
    }
  })

  it('will put an advanced player on multiple teams if needed', function () {
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},

        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'p7', votes: ['g2', 'g1']},
        {playerId: 'p8', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 3},
        {goalDescriptor: 'g2', teamSize: 3},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(3)

    const [team1, team2] = teams.filter(_ => _.goalDescriptor === 'g1')
    const [team3] = teams.filter(_ => _.goalDescriptor === 'g2')

    expect(team1, 'two teams are formed with goal g1').to.be.ok
    expect(team2, 'two teams are formed with goal g1').to.be.ok
    expect(team3, 'one team is formed with goal g2').to.be.ok
    expect(team1.playerIds).to.include('A0')
    expect(team2.playerIds).to.include('A0')
    expect(team3.playerIds).to.include('A1')
  })

  it.skip('will put multiple advanced players on a team if needed', function () {
    // TODO: make this pass
    const input = {
      votes: [
        {playerId: 'A0', votes: ['g1', 'g2']},
        {playerId: 'p1', votes: ['g1', 'g2']},
        {playerId: 'p2', votes: ['g1', 'g2']},
        {playerId: 'p3', votes: ['g1', 'g2']},
        {playerId: 'p4', votes: ['g1', 'g2']},

        {playerId: 'A1', votes: ['g2', 'g1']},
        {playerId: 'A2', votes: ['g2', 'g1']},
        {playerId: 'p5', votes: ['g2', 'g1']},
        {playerId: 'p6', votes: ['g2', 'g1']},
        {playerId: 'p7', votes: ['g2', 'g1']},
      ],
      goals: [
        {goalDescriptor: 'g1', teamSize: 5},
        {goalDescriptor: 'g2', teamSize: 5},
      ],
      advancedPlayers: [{id: 'A0', maxTeams: 3}, {id: 'A1', maxTeams: 1}, {id: 'A2', maxTeams: 1}],
    }

    const {teams} = getTeamFormationPlan(input)

    expect(teams).to.have.length(2)

    const [team1] = teams.filter(_ => _.goalDescriptor === 'g1')
    const [team2] = teams.filter(_ => _.goalDescriptor === 'g2')

    expect(team1, 'one team is formed with goal g1').to.be.ok
    expect(team2, 'one team is formed with goal g2').to.be.ok
    expect(team1.playerIds).to.include('A0')
    expect(team2.playerIds).to.include('A1')
    expect(team2.playerIds).to.include('A2')
  })

  describe.skip('performance tests', function () {
    beforeEach(function () {
      getProfiler().reset()
      console.log(new Date())
    })

    const minutes = n => n * 60000
    const scenarios = [
      // 0
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 15, teamSize: 4, goalCount: 5}),
        expectedRuntime: minutes(0.5),
        minResultScore: 0.95,
      },
      // 1
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 2
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 30, teamSize: 4, goalCount: 5}),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 3
      {
        pool: buildTestPool({advancedPlayerCount: 4, playerCount: 30, teamSize: 4, goalCount: 12}),
        expectedRuntime: minutes(6),
        minResultScore: 0.925,
      },
      // 4
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(0.75),
        minResultScore: 0.95,
      },
      // 5
      {
        pool: buildTestPool({
          advancedPlayerCount: 9,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 28,
          teamSize: 4,
          goalCount: 5,
        }),
        expectedRuntime: minutes(1.00),
        minResultScore: 0.85,
      },
      // 6
      {
        pool: buildTestPool({
          advancedPlayerCount: 10,
          advancedPlayerMaxTeams: [3, 3, 1, 1, 1, 1, 1, 1, 1, 1],
          playerCount: 30,
          teamSize: 4,
          goalCount: 12,
        }),
        expectedRuntime: minutes(12),
        minResultScore: 0.95,
      },
    ]

    scenarios.forEach(({pool, minResultScore, expectedRuntime}, i) => {
      it(`completes scenatio [${i}] in the expected time`, function () {
        this.timeout(expectedRuntime + minutes(1))
        const start = Date.now()

        const teamFormationPlan = getTeamFormationPlan(pool)
        console.log('result:', teamFormationPlanToString(teamFormationPlan), teamFormationPlan.score)

        const elapsedMilliseconds = Date.now() - start
        console.log('scenario', i, 'completed in', (elapsedMilliseconds / 60000).toFixed(2), 'minutes')

        expect(elapsedMilliseconds).to.be.lt(expectedRuntime)
        expect(teamFormationPlan.score).to.be.gt(minResultScore)
      })
    })

    afterEach(function () {
      getProfiler().report()
    })
  })
})

function _teamCountFor(playerId, teams) {
  return teams.reduce(
    (result, {playerIds}) => playerIds.includes(playerId) ? result + 1 : result, 0
  )
}
<|endoftext|>


<commit_before>// import opn from 'opn';
import {
    saveConfigToSafe,
    readConfigFromSafe
} from './manageBrowserConfig';
import { initialiseApp } from '@maidsafe/safe-node-app';
import {
    APP_INFO,
    CONFIG,
    PROTOCOLS,
    isCI,
    isRunningSpectronTestProcessingPackagedApp
} from 'appConstants';
import { SAFE } from 'extensions/safe/constants';
import * as peruseAppActions from 'extensions/safe/actions/peruse_actions';
import * as bookmarksActions from 'actions/bookmarks_actions';
import * as tabsActions from 'actions/tabs_actions';
import * as notificationActions from 'actions/notification_actions';
import handleLogoutActions from 'extensions/safe/network/handleLogoutActions';

import logger from 'logger';

const authingStates = [
    SAFE.APP_STATUS.TO_AUTH,
    SAFE.APP_STATUS.AUTHORISING,
    SAFE.APP_STATUS.AUTHORISATION_FAILED,
    SAFE.APP_STATUS.AUTHORISATION_DENIED
];

let peruseAppObj;
let peruseAppState;
// TODO: HACK for store for now... dont resave store on each change...
let savedStore;

// TODO: Refactor away this and use aliased actions for less... sloppy
// flow and make this more reasonable.
let isAuthing = false;
let isReading = false;
let isSaving = false;

/**
 * Setup actions to be triggered in response to store state changes.
 * @param  { ReduxStore } store [description]
 */
const handlePeruseStoreChanges = ( store ) =>
{
    savedStore = store;
    // lets set state for all funcs to have the same reference.
    const state = store.getState();
    peruseAppState = state.peruseApp;

    manageSaveStateActions( store );
    manageReadStateActions( store );
    manageAuthorisationActions( store );
    handleLogoutActions(store);
}

let cachedRemoteCallArray = [];
// things underway
let callingArray = [];

/**
 * requests Safe network access for the Peruse application.
 * @param  {Boolean} isMock is the browser being run on a mock network
 * @return {Promise}        Peruse SafeApp object
 */
const requestPeruseAppAuthentication = async ( peruseStateObject ) =>
{
    logger.verbose('Requesting PeruseApp auth.')
    try
    {
        const isMock = peruseStateObject.isMock;
        peruseAppObj = await initialiseApp( APP_INFO.info, null, {
            libPath: CONFIG.SAFE_NODE_LIB_PATH,
            forceUseMock   : isMock
        } );

        const authReq = await peruseAppObj.auth.genAuthUri( APP_INFO.permissions, APP_INFO.opts );

        logger.verbose('generated auth uri:', authReq);
        global.browserAuthReqUri = authReq.uri;
        // opn( authReq.uri );
        await peruseAppObj.auth.openUri( authReq.uri );

        return peruseAppObj;
    }
    catch ( err )
    {
        logger.error( err );
        throw err;
    }
};

export const getWebIds = async( ) =>
{
    const peruseApp = getPeruseAppObj();
    logger.verbose('getWebIds');

    if( !peruseApp ) throw new Error('PeruseApp should be initiated.');

    if( !peruseIsAuthed() ) throw new Error('PeruseApp is not authorised');

    let webIds = []
    webIds = await peruseApp.web.getWebIds();

    savedStore.dispatch( peruseAppActions.setAvailableWebIds( webIds ) );

    return webIds;
}

// TODO: Watch out, this is duped in network.js for funcs over there.
export const getPeruseAppObj = () =>
    peruseAppObj;

const urisUnderAuth = [];
const authFromStoreResponse = async ( res, store ) =>
{
    logger.verbose('Authing from a store-passed response.', Date.now() );

    if( !res.startsWith('safe') )
    {
        // it's an error!
        logger.error( res )
        store.dispatch( notificationActions.addNotification(
            {
                text: `Unable to connect to the network. ${res}`,
                type: 'error'
            } ) );

        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISATION_FAILED ) );

        return;
    }

    if( urisUnderAuth.includes( res ) )
    {
        return;
    }

    //TODO: This logic shuld be in BG process for peruse.
    try
    {
        urisUnderAuth.push(res);
        peruseAppObj = await peruseAppObj.auth.loginFromUri( res );

        if ( store )
        {
            store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISED ) );
        }
    }
    catch ( err )
    {
        if ( store )
        {
            let message = err.message;

            if( err.message.startsWith( 'Unexpected (probably a logic') )
            {
                message = `Check your current IP address matches your registered address at invite.maidsafe.net`;
            }

            if ( isRunningSpectronTestProcessingPackagedApp || isCI ) return;

            store.dispatch( notificationActions.addNotification( { text: message, onDismiss: notificationActions.clearNotification } ) );
        }

        logger.error( err.message || err );
        logger.error( '>>>>>>>>>>>>>' );
    }
};

/**
 * Handle triggering actions and related functionality for Authorising on the SAFE netowrk
 * based upon the application auth state
 * @param  {Object} state Application state (from redux)
 */
const manageAuthorisationActions = async ( store ) =>
{
    // TODO: Do this via aliased action.
    const peruse = peruseAppState;

    if ( peruse.appStatus === SAFE.APP_STATUS.TO_AUTH && !isAuthing )
    {
        // cannot rely solely on store as can change in other ways
        // before this is updated properly. This prevents that.
        isAuthing = true;

        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISING ) );

        await requestPeruseAppAuthentication( peruse );
    }

    if( peruse.authResponseUri && peruse.authResponseUri.length )
    {
        // TODO: This should 'clear' or somesuch....
        // OR: Only run if not authed?
        store.dispatch( peruseAppActions.receivedAuthResponse( '' ) );
        authFromStoreResponse( peruse.authResponseUri, store );
        isAuthing = false;
    }
};

const peruseIsAuthing = ( ) =>
{
    const pendingAuthStates = [
        SAFE.APP_STATUS.TO_AUTH,
        SAFE.APP_STATUS.AUTHORISING
    ];

    return isAuthing || pendingAuthStates.includes( peruseAppState.appStatus )
}

const peruseIsAuthed = (  ) =>
{
    return peruseAppState.appStatus === SAFE.APP_STATUS.AUTHORISED;
}

const peruseIsConnected = (  ) =>
{
    // Q: why do we have a loggedin state?
    return peruseAppState.networkStatus === SAFE.NETWORK_STATE.CONNECTED ||
            peruseAppState.networkStatus === SAFE.NETWORK_STATE.LOGGED_IN;
}

const peruseAuthFailed = ( state ) =>
{
    peruseAppState.appStatus === SAFE.APP_STATUS.AUTHORISATION_FAILED
}

/**
 * Handle triggering actions and related functionality for saving to SAFE netowrk
 * based upon the application stateToSave
 * @param  {Object} state Application state (from redux)
 */
const manageReadStateActions = async ( store ) =>
{
    //Hack as store is actually unreliable.
    //TODO: Rework this to use aliased funcs.
    if( isReading )
        return;

    // if its not to save, or isnt authed yet...
    if ( peruseAppState.readStatus !== SAFE.READ_STATUS.TO_READ ||
       peruseIsAuthing( ) || peruseAuthFailed( ) )
    {
        // do nothing
        return;
    }

    if( !peruseIsAuthed( ) )
    {
        // come back when authed.
        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.TO_AUTH ) );
        return;
    }

    if( !peruseIsConnected() )
    {
        return;
    }

    isReading = true;

    logger.verbose('Attempting to READ PeruseApp state from network')
    store.dispatch( peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.READING ) );

    readConfigFromSafe( store )
        .then( savedState =>
        {
            // store.dispatch( peruseAppActions.receivedConfig( savedState ) );
            store.dispatch( bookmarksActions.updateBookmarks( savedState ));
            store.dispatch( tabsActions.updateTabs( savedState ));
            store.dispatch(
                peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.READ_SUCCESSFULLY )
            );

            isReading = false;
            return null;
        } )
        .catch( e =>
        {
            isReading = false;
            logger.error( e );
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.FAILED_TO_READ )
            );
            throw new Error( e );
        } );

};

/**
 * Handle triggering actions and related functionality for saving to SAFE netowrk
 * based upon the application stateToSave
 * @param  {Object} state Application state (from redux)
 */
const manageSaveStateActions = async ( store ) =>
{
    //Hack as store is actually unreliable.
    //TODO: Rework this to use aliased funcs.
    if( isSaving )
        return;

    const peruseApp = peruseAppState;

    // if its not to save, or isnt authed yet...
    if ( peruseApp.saveStatus !== SAFE.SAVE_STATUS.TO_SAVE ||
       peruseIsAuthing( ) || peruseAuthFailed( ) )
    {
        // do nothing
        return;
    }

    //if it auth didnt happen, and hasnt failed... previously... we can try again (we're in TO SAVE, not SAVING.)
    if( !peruseIsAuthed( ) )
    {
        // come back when authed.
        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.TO_AUTH ) );
        return;
    }

    if( !peruseIsConnected() )
    {
        return;
    }

    //lets scrap read for now.
    if ( peruseApp.readStatus !== SAFE.READ_STATUS.READ_SUCCESSFULLY &&
        peruseApp.readStatus !== SAFE.READ_STATUS.READ_BUT_NONEXISTANT &&
        peruseApp.readStatus !== SAFE.READ_STATUS.TO_READ &&
        peruseApp.readStatus !== SAFE.READ_STATUS.READING )
    {
            logger.verbose( 'Can\'t save state, not read yet... Triggering a read.' );
            store.dispatch( peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.TO_READ ) );

        return;
    }

    isSaving = true;

    logger.verbose('Attempting to SAVE PeruseApp state to network')
    store.dispatch( peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.SAVING ) );
    saveConfigToSafe( store )
        .then( () =>
        {
            isSaving = false;
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.SAVED_SUCCESSFULLY )
            );

            return null;
        } )
        .catch( e =>
        {
            isSaving = false;
            logger.error( e );

            // TODO: Handle errors across the store in a separate error watcher?
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.FAILED_TO_SAVE )
            );
            throw new Error( e );
        } );
};

export default handlePeruseStoreChanges;
<commit_msg>possible-fix/auth: debounce auth passing to store from uri responses<commit_after>// import opn from 'opn';
import _ from 'lodash';
import {
    saveConfigToSafe,
    readConfigFromSafe
} from './manageBrowserConfig';
import { initialiseApp } from '@maidsafe/safe-node-app';
import {
    APP_INFO,
    CONFIG,
    PROTOCOLS,
    isCI,
    isRunningSpectronTestProcessingPackagedApp
} from 'appConstants';
import { SAFE } from 'extensions/safe/constants';
import * as peruseAppActions from 'extensions/safe/actions/peruse_actions';
import * as bookmarksActions from 'actions/bookmarks_actions';
import * as tabsActions from 'actions/tabs_actions';
import * as notificationActions from 'actions/notification_actions';
import handleLogoutActions from 'extensions/safe/network/handleLogoutActions';
import logger from 'logger';

const authingStates = [
    SAFE.APP_STATUS.TO_AUTH,
    SAFE.APP_STATUS.AUTHORISING,
    SAFE.APP_STATUS.AUTHORISATION_FAILED,
    SAFE.APP_STATUS.AUTHORISATION_DENIED
];

let peruseAppObj;
let peruseAppState;
// TODO: HACK for store for now... dont resave store on each change...
let savedStore;

// TODO: Refactor away this and use aliased actions for less... sloppy
// flow and make this more reasonable.
let isAuthing = false;
let isReading = false;
let isSaving = false;

/**
 * Setup actions to be triggered in response to store state changes.
 * @param  { ReduxStore } store [description]
 */
const handlePeruseStoreChanges = ( store ) =>
{
    savedStore = store;
    // lets set state for all funcs to have the same reference.
    const state = store.getState();
    peruseAppState = state.peruseApp;

    manageSaveStateActions( store );
    manageReadStateActions( store );
    manageAuthorisationActions( store );
    handleLogoutActions(store);
}

let cachedRemoteCallArray = [];
// things underway
let callingArray = [];

/**
 * requests Safe network access for the Peruse application.
 * @param  {Boolean} isMock is the browser being run on a mock network
 * @return {Promise}        Peruse SafeApp object
 */
const requestPeruseAppAuthentication = async ( peruseStateObject ) =>
{
    logger.verbose('Requesting PeruseApp auth.')
    try
    {
        const isMock = peruseStateObject.isMock;
        peruseAppObj = await initialiseApp( APP_INFO.info, null, {
            libPath: CONFIG.SAFE_NODE_LIB_PATH,
            forceUseMock   : isMock
        } );

        const authReq = await peruseAppObj.auth.genAuthUri( APP_INFO.permissions, APP_INFO.opts );

        logger.verbose('generated auth uri:', authReq);
        global.browserAuthReqUri = authReq.uri;
        // opn( authReq.uri );
        await peruseAppObj.auth.openUri( authReq.uri );

        return peruseAppObj;
    }
    catch ( err )
    {
        logger.error( err );
        throw err;
    }
};

export const getWebIds = async( ) =>
{
    const peruseApp = getPeruseAppObj();
    logger.verbose('getWebIds');

    if( !peruseApp ) throw new Error('PeruseApp should be initiated.');

    if( !peruseIsAuthed() ) throw new Error('PeruseApp is not authorised');

    let webIds = []
    webIds = await peruseApp.web.getWebIds();

    savedStore.dispatch( peruseAppActions.setAvailableWebIds( webIds ) );

    return webIds;
}

// TODO: Watch out, this is duped in network.js for funcs over there.
export const getPeruseAppObj = () =>
    peruseAppObj;

const urisUnderAuth = [];
const authFromStoreResponse = async ( res, store ) =>
{
    logger.verbose('Authing from a store-passed response.', Date.now() );

    if( !res.startsWith('safe') )
    {
        // it's an error!
        logger.error( res )
        store.dispatch( notificationActions.addNotification(
            {
                text: `Unable to connect to the network. ${res}`,
                type: 'error'
            } ) );

        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISATION_FAILED ) );

        return;
    }

    if( urisUnderAuth.includes( res ) )
    {
        return;
    }

    //TODO: This logic shuld be in BG process for peruse.
    try
    {
        urisUnderAuth.push(res);
        peruseAppObj = await peruseAppObj.auth.loginFromUri( res );

        if ( store )
        {
            store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISED ) );
        }
    }
    catch ( err )
    {
        if ( store )
        {
            let message = err.message;

            if( err.message.startsWith( 'Unexpected (probably a logic') )
            {
                message = `Check your current IP address matches your registered address at invite.maidsafe.net`;
            }

            if ( isRunningSpectronTestProcessingPackagedApp || isCI ) return;

            store.dispatch( notificationActions.addNotification( { text: message, onDismiss: notificationActions.clearNotification } ) );
        }

        logger.error( err.message || err );
        logger.error( '>>>>>>>>>>>>>' );
    }
};

/**
 * Handle triggering actions and related functionality for Authorising on the SAFE netowrk
 * based upon the application auth state
 * @param  {Object} state Application state (from redux)
 */
const manageAuthorisationActions = async ( store ) =>
{
    // TODO: Do this via aliased action.
    const peruse = peruseAppState;

    if ( peruse.appStatus === SAFE.APP_STATUS.TO_AUTH && !isAuthing )
    {
        // cannot rely solely on store as can change in other ways
        // before this is updated properly. This prevents that.
        isAuthing = true;

        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.AUTHORISING ) );

        await requestPeruseAppAuthentication( peruse );
    }

    if( peruse.authResponseUri && peruse.authResponseUri.length )
    {
        // TODO: This should 'clear' or somesuch....
        // OR: Only run if not authed?
        const debouncedPassAuthUriToStore = _.debounce( () => {
            store.dispatch( peruseAppActions.receivedAuthResponse( '' ) );
            authFromStoreResponse( peruse.authResponseUri, store );
            isAuthing = false;
        }, 500 )

        debouncedPassAuthUriToStore()
    }
};

const peruseIsAuthing = ( ) =>
{
    const pendingAuthStates = [
        SAFE.APP_STATUS.TO_AUTH,
        SAFE.APP_STATUS.AUTHORISING
    ];

    return isAuthing || pendingAuthStates.includes( peruseAppState.appStatus )
}

const peruseIsAuthed = (  ) =>
{
    return peruseAppState.appStatus === SAFE.APP_STATUS.AUTHORISED;
}

const peruseIsConnected = (  ) =>
{
    // Q: why do we have a loggedin state?
    return peruseAppState.networkStatus === SAFE.NETWORK_STATE.CONNECTED ||
            peruseAppState.networkStatus === SAFE.NETWORK_STATE.LOGGED_IN;
}

const peruseAuthFailed = ( state ) =>
{
    peruseAppState.appStatus === SAFE.APP_STATUS.AUTHORISATION_FAILED
}

/**
 * Handle triggering actions and related functionality for saving to SAFE netowrk
 * based upon the application stateToSave
 * @param  {Object} state Application state (from redux)
 */
const manageReadStateActions = async ( store ) =>
{
    //Hack as store is actually unreliable.
    //TODO: Rework this to use aliased funcs.
    if( isReading )
        return;

    // if its not to save, or isnt authed yet...
    if ( peruseAppState.readStatus !== SAFE.READ_STATUS.TO_READ ||
       peruseIsAuthing( ) || peruseAuthFailed( ) )
    {
        // do nothing
        return;
    }

    if( !peruseIsAuthed( ) )
    {
        // come back when authed.
        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.TO_AUTH ) );
        return;
    }

    if( !peruseIsConnected() )
    {
        return;
    }

    isReading = true;

    logger.verbose('Attempting to READ PeruseApp state from network')
    store.dispatch( peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.READING ) );

    readConfigFromSafe( store )
        .then( savedState =>
        {
            // store.dispatch( peruseAppActions.receivedConfig( savedState ) );
            store.dispatch( bookmarksActions.updateBookmarks( savedState ));
            store.dispatch( tabsActions.updateTabs( savedState ));
            store.dispatch(
                peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.READ_SUCCESSFULLY )
            );

            isReading = false;
            return null;
        } )
        .catch( e =>
        {
            isReading = false;
            logger.error( e );
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.FAILED_TO_READ )
            );
            throw new Error( e );
        } );

};

/**
 * Handle triggering actions and related functionality for saving to SAFE netowrk
 * based upon the application stateToSave
 * @param  {Object} state Application state (from redux)
 */
const manageSaveStateActions = async ( store ) =>
{
    //Hack as store is actually unreliable.
    //TODO: Rework this to use aliased funcs.
    if( isSaving )
        return;

    const peruseApp = peruseAppState;

    // if its not to save, or isnt authed yet...
    if ( peruseApp.saveStatus !== SAFE.SAVE_STATUS.TO_SAVE ||
       peruseIsAuthing( ) || peruseAuthFailed( ) )
    {
        // do nothing
        return;
    }

    //if it auth didnt happen, and hasnt failed... previously... we can try again (we're in TO SAVE, not SAVING.)
    if( !peruseIsAuthed( ) )
    {
        // come back when authed.
        store.dispatch( peruseAppActions.setAppStatus( SAFE.APP_STATUS.TO_AUTH ) );
        return;
    }

    if( !peruseIsConnected() )
    {
        return;
    }

    //lets scrap read for now.
    if ( peruseApp.readStatus !== SAFE.READ_STATUS.READ_SUCCESSFULLY &&
        peruseApp.readStatus !== SAFE.READ_STATUS.READ_BUT_NONEXISTANT &&
        peruseApp.readStatus !== SAFE.READ_STATUS.TO_READ &&
        peruseApp.readStatus !== SAFE.READ_STATUS.READING )
    {
            logger.verbose( 'Can\'t save state, not read yet... Triggering a read.' );
            store.dispatch( peruseAppActions.setReadConfigStatus( SAFE.READ_STATUS.TO_READ ) );

        return;
    }

    isSaving = true;

    logger.verbose('Attempting to SAVE PeruseApp state to network')
    store.dispatch( peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.SAVING ) );
    saveConfigToSafe( store )
        .then( () =>
        {
            isSaving = false;
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.SAVED_SUCCESSFULLY )
            );

            return null;
        } )
        .catch( e =>
        {
            isSaving = false;
            logger.error( e );

            // TODO: Handle errors across the store in a separate error watcher?
            store.dispatch(
                peruseAppActions.setSaveConfigStatus( SAFE.SAVE_STATUS.FAILED_TO_SAVE )
            );
            throw new Error( e );
        } );
};

export default handlePeruseStoreChanges;
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';

class Area extends React.PureComponent {

  static propTypes = {
    account: ImmutablePropTypes.map.isRequired
  };

  constructor (props, context) {
    super(props, context);
    var area_data = require("../../area_settings.json");
    var areas = area_data['areas'];
    this.instances = area_data['instances'];
    this.config = [];
    areas.forEach(function(data, index, arr) {
      this.config[data['area-id']] = data;
    }, this);
    this.get_area_eng_name = this.get_area_eng_name.bind(this);
    this.get_area_short_name = this.get_area_short_name.bind(this);
    this.get_local_area_eng_name = this.get_local_area_eng_name.bind(this);
    this.get_remote_area_eng_name = this.get_remote_area_eng_name.bind(this);
    this.get_local_area_short_name = this.get_local_area_short_name.bind(this);
    this.get_remote_area_short_name = this.get_remote_area_short_name.bind(this);
    this.is_local = this.is_local.bind(this);
  }

  get_area_eng_name(account){
    if (this.is_local(account)){
      return (this.get_local_area_eng_name(account.get('area')));
    }else{
      return (this.get_remote_area_eng_name(account));
    }
  }

  get_local_area_eng_name(area_id){
    if (isNaN(area_id)) {
      var area_id = 0;
    };
    try{
      var area_eng_name = this.config[area_id]["area-eng-name"];
    } catch (e) {
      var area_eng_name = this.config[0]["area-eng-name"];
    }
    return (area_eng_name);
  }

  get_remote_area_eng_name(account){
    var splittedName = account.get('acct').split('@');
    var domain = splittedName[splittedName.length - 1];
    try{
      var instanceSetting = this.instances[domain];
      var area_eng_name = instanceSetting["instance-eng-name"];
    }catch (e) {
      return this.get_local_area_eng_name(0);
    }
    return (area_eng_name);
  }

  get_area_short_name(account){
    if (this.is_local(account)){
      return (this.get_local_area_short_name(account.get('area')));
    }else{
      return (this.get_remote_area_short_name(account));
    }
  }

  get_local_area_short_name(area_id){
    if (isNaN(area_id)) {
      var area_id = 0;
    };
    try{
      var area_short_name = this.config[area_id]["area-short-name"];
    } catch (e) {
      var area_short_name = this.config[0]["area-short-name"];
    }
    return (area_short_name);
  }

  get_remote_area_short_name(account){
    var splittedName = account.get('acct').split('@');
    var domain = splittedName[splittedName.length - 1];
    try{
      var instanceSetting = this.instances[domain];
      var area_short_name = instanceSetting["instance-short-name"];
    }catch (e) {
      return this.get_local_area_short_name(0);
    }
    return (area_short_name);
  }

  is_local(account){
    return (account.get('username') === account.get('acct'));
  }
}

export default Area;
<commit_msg>Revert "Revert "import react""<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';

class Area extends React.PureComponent {

  static propTypes = {
    account: ImmutablePropTypes.map.isRequired
  };

  constructor (props, context) {
    super(props, context);
    var area_data = require("../../area_settings.json");
    var areas = area_data['areas'];
    this.instances = area_data['instances'];
    this.config = [];
    areas.forEach(function(data, index, arr) {
      this.config[data['area-id']] = data;
    }, this);
    this.get_area_eng_name = this.get_area_eng_name.bind(this);
    this.get_area_short_name = this.get_area_short_name.bind(this);
    this.get_local_area_eng_name = this.get_local_area_eng_name.bind(this);
    this.get_remote_area_eng_name = this.get_remote_area_eng_name.bind(this);
    this.get_local_area_short_name = this.get_local_area_short_name.bind(this);
    this.get_remote_area_short_name = this.get_remote_area_short_name.bind(this);
    this.is_local = this.is_local.bind(this);
  }

  get_area_eng_name(account){
    if (this.is_local(account)){
      return (this.get_local_area_eng_name(account.get('area')));
    }else{
      return (this.get_remote_area_eng_name(account));
    }
  }

  get_local_area_eng_name(area_id){
    if (isNaN(area_id)) {
      var area_id = 0;
    };
    try{
      var area_eng_name = this.config[area_id]["area-eng-name"];
    } catch (e) {
      var area_eng_name = this.config[0]["area-eng-name"];
    }
    return (area_eng_name);
  }

  get_remote_area_eng_name(account){
    var splittedName = account.get('acct').split('@');
    var domain = splittedName[splittedName.length - 1];
    try{
      var instanceSetting = this.instances[domain];
      var area_eng_name = instanceSetting["instance-eng-name"];
    }catch (e) {
      return this.get_local_area_eng_name(0);
    }
    return (area_eng_name);
  }

  get_area_short_name(account){
    if (this.is_local(account)){
      return (this.get_local_area_short_name(account.get('area')));
    }else{
      return (this.get_remote_area_short_name(account));
    }
  }

  get_local_area_short_name(area_id){
    if (isNaN(area_id)) {
      var area_id = 0;
    };
    try{
      var area_short_name = this.config[area_id]["area-short-name"];
    } catch (e) {
      var area_short_name = this.config[0]["area-short-name"];
    }
    return (area_short_name);
  }

  get_remote_area_short_name(account){
    var splittedName = account.get('acct').split('@');
    var domain = splittedName[splittedName.length - 1];
    try{
      var instanceSetting = this.instances[domain];
      var area_short_name = instanceSetting["instance-short-name"];
    }catch (e) {
      return this.get_local_area_short_name(0);
    }
    return (area_short_name);
  }

  is_local(account){
    return (account.get('username') === account.get('acct'));
  }
}

export default Area;
<|endoftext|>


<commit_before><commit_msg>Added another spec to tests for writeFolder<commit_after><|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc service
 * @name svgChartsApp.service:svgChartsPopover
 * @description
 * # svgChartsPopover
 * Factory that contains all the properties and methods for a svgChartsPopover
 *
 */
angular.module('svgChartsApp')
  .factory('SvgChartsPopover', function (SvgChartsScene) {

    var SvgChartsPopover = {};

    SvgChartsPopover.open = function(_d) {

      var popoverTextBoxWidth = 135;
      var popoverTextBoxHeight = 120;
      var popoverTextBoxX;
      var popoverTextBoxY;

        var m = d3.mouse(SvgChartsScene.svg.node());

        popoverTextBoxX = m[0];
        popoverTextBoxY = m[1];

        if(popoverTextBoxY + popoverTextBoxHeight > SvgChartsScene.height) {
          popoverTextBoxY = popoverTextBoxY - popoverTextBoxHeight;
        }

        if(popoverTextBoxX + popoverTextBoxWidth > SvgChartsScene.width) {
          popoverTextBoxX = popoverTextBoxX - popoverTextBoxWidth;
        }

        SvgChartsScene.popoverTextBox
          .attr('x', popoverTextBoxX)
          .attr('y', popoverTextBoxY)
          .attr('rx', 6)
          .attr('ry', 6)
          .attr('width', popoverTextBoxWidth)
          .attr('height', popoverTextBoxHeight)
          .attr('fill', 'rgba(0,0,0,0.34)')
          .attr({
            'stroke': 'black',
            'stroke-width': '1px'
          });

        SvgChartsScene.popoverText
          .attr('x', popoverTextBoxX)
          .attr('y', popoverTextBoxY + 12)
          .attr('class', 'popover-text')
          .attr('name', 'popover-text')
          .text( function () {
            return  (_d.date.getMonth() + 1) + '/' + _d.date.getDate() + '/' + _d.date.getFullYear() +
              ' O: ' + parseFloat(Math.round(_d.open * 100) / 100).toFixed(2) +
              ' H: ' + parseFloat(Math.round(_d.high * 100) / 100).toFixed(2) +
              ' L: ' + parseFloat(Math.round(_d.low * 100) / 100).toFixed(2) +
              ' C: ' + parseFloat(Math.round(_d.close * 100) / 100).toFixed(2); })
          .attr('font-family', 'sans-serif')
          .attr('font-size', '20px')
          .attr('fill', 'white')
          .call(wrap, 90, popoverTextBoxX + 4);

        SvgChartsScene.popoverTextBoxCloseText
          .attr('x', popoverTextBoxX + popoverTextBoxWidth - 20 + 5)
          .attr('y', popoverTextBoxY + 20 - 4)
          .text('X');

        SvgChartsScene.popoverTextBoxClose
          .attr('x', popoverTextBoxX + popoverTextBoxWidth - 20 + 2)
          .attr('y', popoverTextBoxY + 2)
          .attr('rx', 6)
          .attr('ry', 6)
          .attr('width', 16)
          .attr('height', 16)
          .attr('fill', 'red')
          .attr({
            'stroke': 'black',
            'stroke-width': '1px'
          })
          .attr('style', 'fill-opacity:0.3; cursor: pointer;')
          .on('click', function() {
            SvgChartsPopover.cleanUpPopovers(true);
          });
    };

    SvgChartsPopover.cleanUpPopovers = function(override) {

      if(!override &&
        SvgChartsScene.chartData &&
        SvgChartsScene.$previousChartData &&
        (SvgChartsScene.$previousChartData.length === SvgChartsScene.chartData.length) &&
        (angular.equals(SvgChartsScene.$previousChartData[0], SvgChartsScene.chartData[0]))) {
        return false;
      }

      SvgChartsScene.popoverTextBox.attr('x', -500);
      SvgChartsScene.popoverTextBoxClose.attr('x', -500);

      SvgChartsScene.popoverText.text('');
      SvgChartsScene.popoverTextBoxCloseText.text('');

      SvgChartsScene.$previousChartData = SvgChartsScene.chartData;

    };

    var wrap = function(text, width, popoverTextBoxX) {
      text.each(function() {
        var text = d3.select(this),
          words = text.text().split(/\s+/).reverse(),
          word,
          line = [],
          lineNumber = 0,
          lineHeight = 1.1, // ems
          y = text.attr('y'),
          dy = parseFloat(text.attr('dy')),
          tspan = text.text(null).append('tspan').attr('x', popoverTextBoxX + 3).attr('y', parseInt(y) + 20).attr('dy', 0 + 'em');
        while (word = words.pop()) {
          line.push(word);
          tspan.text(line.join(' '));
          if (tspan.node().getComputedTextLength() > width) {
            line.pop();
            tspan.text(line.join(' '));
            line = [word];
            tspan = text.append('tspan').attr('x', popoverTextBoxX + 3).attr('y', parseInt(y) + 20).attr('dy', ++lineNumber + 'em').text(word);
          }
        }
      });
    };

    /**
     * @ngdoc function
     * @name svgChartsPopover.init
     * @methodOf svgChartsApp.service:svgChartsPopover
     *
     * @description
     * Initiate the popover svg elements
     *
     */
    SvgChartsPopover.init = function() {
      SvgChartsScene.popoverTextBox = SvgChartsScene.svg.append('rect');
      SvgChartsScene.popoverText = SvgChartsScene.svg.append('text');
      SvgChartsScene.popoverTextBoxCloseText = SvgChartsScene.svg.append('text');
      SvgChartsScene.popoverTextBoxClose = SvgChartsScene.svg.append('rect');
    };

    return SvgChartsPopover;

  });
<commit_msg>Fixing spacing on popup date.<commit_after>'use strict';

/**
 * @ngdoc service
 * @name svgChartsApp.service:svgChartsPopover
 * @description
 * # svgChartsPopover
 * Factory that contains all the properties and methods for a svgChartsPopover
 *
 */
angular.module('svgChartsApp')
  .factory('SvgChartsPopover', function (SvgChartsScene) {

    var SvgChartsPopover = {};

    SvgChartsPopover.open = function(_d) {

      var popoverTextBoxWidth = 135;
      var popoverTextBoxHeight = 120;
      var popoverTextBoxX;
      var popoverTextBoxY;

        var m = d3.mouse(SvgChartsScene.svg.node());

        popoverTextBoxX = m[0];
        popoverTextBoxY = m[1];

        if(popoverTextBoxY + popoverTextBoxHeight > SvgChartsScene.height) {
          popoverTextBoxY = popoverTextBoxY - popoverTextBoxHeight;
        }

        if(popoverTextBoxX + popoverTextBoxWidth > SvgChartsScene.width) {
          popoverTextBoxX = popoverTextBoxX - popoverTextBoxWidth;
        }

        SvgChartsScene.popoverTextBox
          .attr('x', popoverTextBoxX)
          .attr('y', popoverTextBoxY)
          .attr('rx', 6)
          .attr('ry', 6)
          .attr('width', popoverTextBoxWidth)
          .attr('height', popoverTextBoxHeight)
          .attr('fill', 'rgba(0,0,0,0.34)')
          .attr({
            'stroke': 'black',
            'stroke-width': '1px'
          });

        SvgChartsScene.popoverText
          .attr('x', popoverTextBoxX)
          .attr('y', popoverTextBoxY + 12)
          .attr('class', 'popover-text')
          .attr('name', 'popover-text')
          .text( function () {
            return  ('0' + (_d.date.getMonth() + 1)).slice(-2) + '/' + ('0' + (_d.date.getDate())).slice(-2) + '/' + _d.date.getFullYear() +
              ' O: ' + parseFloat(Math.round(_d.open * 100) / 100).toFixed(3) +
              ' H: ' + parseFloat(Math.round(_d.high * 100) / 100).toFixed(3) +
              ' L: ' + parseFloat(Math.round(_d.low * 100) / 100).toFixed(3) +
              ' C: ' + parseFloat(Math.round(_d.close * 100) / 100).toFixed(3); })
          .attr('font-family', 'sans-serif')
          .attr('font-size', '20px')
          .attr('fill', 'white')
          .call(wrap, 105, popoverTextBoxX + 4);

        SvgChartsScene.popoverTextBoxCloseText
          .attr('x', popoverTextBoxX + popoverTextBoxWidth - 20 + 5)
          .attr('y', popoverTextBoxY + 20 - 4)
          .text('X');

        SvgChartsScene.popoverTextBoxClose
          .attr('x', popoverTextBoxX + popoverTextBoxWidth - 20 + 2)
          .attr('y', popoverTextBoxY + 2)
          .attr('rx', 6)
          .attr('ry', 6)
          .attr('width', 16)
          .attr('height', 16)
          .attr('fill', 'red')
          .attr({
            'stroke': 'black',
            'stroke-width': '1px'
          })
          .attr('style', 'fill-opacity:0.3; cursor: pointer;')
          .on('click', function() {
            SvgChartsPopover.cleanUpPopovers(true);
          });
    };

    SvgChartsPopover.cleanUpPopovers = function(override) {

      if(!override &&
        SvgChartsScene.chartData &&
        SvgChartsScene.$previousChartData &&
        (SvgChartsScene.$previousChartData.length === SvgChartsScene.chartData.length) &&
        (angular.equals(SvgChartsScene.$previousChartData[0], SvgChartsScene.chartData[0]))) {
        return false;
      }

      SvgChartsScene.popoverTextBox.attr('x', -500);
      SvgChartsScene.popoverTextBoxClose.attr('x', -500);

      SvgChartsScene.popoverText.text('');
      SvgChartsScene.popoverTextBoxCloseText.text('');

      SvgChartsScene.$previousChartData = SvgChartsScene.chartData;

    };

    var wrap = function(text, width, popoverTextBoxX) {
      text.each(function() {
        var text = d3.select(this),
          words = text.text().split(/\s+/).reverse(),
          word,
          line = [],
          lineNumber = 0,
          lineHeight = 1.1, // ems
          y = text.attr('y'),
          dy = parseFloat(text.attr('dy')),
          tspan = text.text(null).append('tspan').attr('x', popoverTextBoxX + 3).attr('y', parseInt(y) + 20).attr('dy', 0 + 'em');
        while (word = words.pop()) {
          line.push(word);
          tspan.text(line.join(' '));
          if (tspan.node().getComputedTextLength() > width) {
            line.pop();
            tspan.text(line.join(' '));
            line = [word];
            tspan = text.append('tspan').attr('x', popoverTextBoxX + 3).attr('y', parseInt(y) + 20).attr('dy', ++lineNumber + 'em').text(word);
          }
        }
      });
    };

    /**
     * @ngdoc function
     * @name svgChartsPopover.init
     * @methodOf svgChartsApp.service:svgChartsPopover
     *
     * @description
     * Initiate the popover svg elements
     *
     */
    SvgChartsPopover.init = function() {
      SvgChartsScene.popoverTextBox = SvgChartsScene.svg.append('rect');
      SvgChartsScene.popoverText = SvgChartsScene.svg.append('text');
      SvgChartsScene.popoverTextBoxCloseText = SvgChartsScene.svg.append('text');
      SvgChartsScene.popoverTextBoxClose = SvgChartsScene.svg.append('rect');
    };

    return SvgChartsPopover;

  });
<|endoftext|>


<commit_before>angular
  .module('confRegistrationWebApp')
  .service('validateRegistrant', function validateRegistrant(
    $window,
    ruleTypeConstants,
    $filter,
  ) {
    const blockVisibleRuleCheck = (block, registrant, ruleType) => {
      let answers = registrant.answers;
      let ruleOperand = '';
      let validRuleCount = 0;
      let blockTypeSpecificRules = [];

      switch (ruleType) {
        case ruleTypeConstants.SHOW_QUESTION: {
          blockTypeSpecificRules = $filter('showQuestionFilter')(block.rules);
          ruleOperand =
            block.content && block.content.ruleoperand
              ? block.content.ruleoperand
              : 'AND';
          break;
        }
        case ruleTypeConstants.FORCE_SELECTION: {
          blockTypeSpecificRules = _.filter(block.rules, {
            ruleType: ruleType,
          });
          ruleOperand =
            block.content && block.content.forceSelectionRuleOperand
              ? block.content.forceSelectionRuleOperand
              : 'AND';
          break;
        }
        default: {
          if ($window.Rollbar) {
            $window.Rollbar.error(
              'blockVisibleRuleCheck was called with an unknown rule type: ',
              ruleType,
            );
          }
          ruleOperand = 'AND';
          break;
        }
      }

      if ($window.Rollbar && !_.isArray(blockTypeSpecificRules)) {
        $window.Rollbar.info(
          'Block rules value in blockVisibleRuleCheck, ruleType: ' +
            ruleType +
            ', typeof: ' +
            typeof blockTypeSpecificRules +
            ', JSON.stringify: ' +
            JSON.stringify(blockTypeSpecificRules),
        );
      }

      _.forEach(blockTypeSpecificRules, (rule, i) => {
        let answer = _.find(answers, { blockId: rule.parentBlockId });
        if (angular.isDefined(answer) && answer.value !== '') {
          let answerValue;
          let ruleValue;
          if (angular.isObject(answer.value)) {
            //answer of checkboxquestion will be an object
            answerValue = angular.isDefined(answer.value[rule.value])
              ? answer.value[rule.value]
              : false;
            ruleValue = true;
          } else {
            //If string is a number, parse it as a float for numerical comparison
            answerValue = !isNaN(answer.value)
              ? parseFloat(answer.value)
              : answer.value;
            ruleValue = !isNaN(rule.value)
              ? parseFloat(rule.value)
              : rule.value;
          }

          if (rule.operator === '=' && answerValue === ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '!=' && answerValue !== ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '>' && answerValue > ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '<' && answerValue < ruleValue) {
            validRuleCount++;
          }
        }
        if (
          (ruleOperand === 'OR' && validRuleCount > 0) ||
          (ruleOperand === 'AND' && validRuleCount <= i)
        ) {
          return false; // Exit lodash foreach as we found a case which determines the whole outcome of this function
        }
      });

      return (
        !blockTypeSpecificRules ||
        blockTypeSpecificRules.length === 0 || // If no rules are set
        (ruleOperand === 'OR' && validRuleCount > 0) ||
        (ruleOperand === 'AND' &&
          validRuleCount === blockTypeSpecificRules.length)
      );
    };

    const choiceVisibleRuleCheck = (block, choice, registrant, ruleType) => {
      let answers = registrant.answers;
      let ruleOperand = '';
      let validRuleCount = 0;
      let blockTypeSpecificRules = [];

      switch (ruleType) {
        case ruleTypeConstants.SHOW_OPTION: {
          blockTypeSpecificRules = _.filter(block.rules, {
            ruleType: ruleType,
            blockEntityOption: choice.value,
          });

          ruleOperand = choice.operand ? choice.operand : 'OR';
          break;
        }
        default: {
          if ($window.Rollbar) {
            $window.Rollbar.error(
              'choiceVisibleRuleCheck was called with an unknown rule type: ',
              ruleType,
            );
          }
          ruleOperand = 'AND';
          break;
        }
      }

      if ($window.Rollbar && !_.isArray(blockTypeSpecificRules)) {
        $window.Rollbar.info(
          'Block rules value in blockVisibleRuleCheck, ruleType: ' +
            ruleType +
            ', typeof: ' +
            typeof blockTypeSpecificRules +
            ', JSON.stringify: ' +
            JSON.stringify(blockTypeSpecificRules),
        );
      }

      _.forEach(blockTypeSpecificRules, (rule, i) => {
        let answer = _.find(answers, { blockId: rule.parentBlockId });
        if (angular.isDefined(answer) && answer.value !== '') {
          let answerValue;
          let ruleValue;
          if (angular.isObject(answer.value)) {
            //answer of checkboxquestion will be an object
            answerValue = angular.isDefined(answer.value[rule.value])
              ? answer.value[rule.value]
              : false;
            ruleValue = true;
          } else {
            //If string is a number, parse it as a float for numerical comparison
            answerValue = !isNaN(answer.value)
              ? parseFloat(answer.value)
              : answer.value;
            ruleValue = !isNaN(rule.value)
              ? parseFloat(rule.value)
              : rule.value;
          }

          if (rule.operator === '=' && answerValue === ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '!=' && answerValue !== ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '>' && answerValue > ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '<' && answerValue < ruleValue) {
            validRuleCount++;
          }
        }
        if (
          (ruleOperand === 'OR' && validRuleCount > 0) ||
          (ruleOperand === 'AND' && validRuleCount <= i)
        ) {
          return false; // Exit lodash foreach as we found a case which determines the whole outcome of this function
        }
      });

      return (
        !blockTypeSpecificRules ||
        blockTypeSpecificRules.length === 0 || // If no rules are set
        (ruleOperand === 'OR' && validRuleCount > 0) ||
        (ruleOperand === 'AND' &&
          validRuleCount === blockTypeSpecificRules.length)
      );
    };

    const blockInRegistrantType = (block, registrant) => {
      return !_.includes(block.registrantTypes, registrant.registrantTypeId);
    };

    this.isAnyChoiceVisible = (block, registrant) => {
      if (
        block.type !== 'checkboxQuestion' &&
        block.type !== 'selectQuestion' &&
        block.type !== 'radioQuestion'
      ) {
        return true;
      }

      // If a block has no content choices because the user forgot to add them, automatically return false.
      if (!block.content.choices) {
        return false;
      }

      for (let i = 0, len = block.content.choices.length; i < len; i++) {
        if (this.choiceVisible(block, block.content.choices[i], registrant)) {
          return true;
        }
      }
      return false;
    };

    this.blockVisible = (block, registrant, isAdmin) => {
      const visible =
        angular.isDefined(registrant) &&
        blockVisibleRuleCheck(
          block,
          registrant,
          ruleTypeConstants.SHOW_QUESTION,
        ) &&
        blockInRegistrantType(block, registrant) &&
        this.isAnyChoiceVisible(block, registrant);

      return block.adminOnly && !isAdmin ? false : visible;
    };

    this.choiceVisible = (block, choice, registrant) => {
      return (
        angular.isDefined(registrant) &&
        choiceVisibleRuleCheck(
          block,
          choice,
          registrant,
          ruleTypeConstants.SHOW_OPTION,
        )
      );
    };

    this.checkboxDisable = (block, registrant) => {
      return blockVisibleRuleCheck(
        block,
        registrant,
        ruleTypeConstants.FORCE_SELECTION,
      );
    };

    this.validate = (conference, registrant, page) => {
      let invalidBlocks = [];

      conference = angular.copy(conference);
      const blocks = page
        ? _.find(conference.registrationPages, { id: page }).blocks
        : _.flatten(_.map(conference.registrationPages, 'blocks'));

      _.forEach(blocks, block => {
        if (
          !block.required ||
          block.adminOnly ||
          !blockVisibleRuleCheck(
            block,
            registrant,
            ruleTypeConstants.SHOW_QUESTION,
          ) ||
          !blockInRegistrantType(block, registrant)
        ) {
          return;
        }

        let answer = _.find(registrant.answers, { blockId: block.id });
        if (angular.isUndefined(answer)) {
          invalidBlocks.push(block.id);
          return;
        }
        answer = answer.value;

        switch (block.type) {
          case 'nameQuestion':
            if (_.isEmpty(answer.firstName) || _.isEmpty(answer.lastName)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'emailQuestion':
            if (_.isEmpty(answer) || _.isNull(answer.match(/^.+@.+$/))) {
              //Contains an @ sign surrounded by at least one character
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'numberQuestion':
            if (!_.isNumber(answer)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'phoneQuestion':
            if (
              _.isNull(answer) ||
              _.isNull(answer.match(/^(\D*\d\D*){10,15}$/))
            ) {
              //Contains 10-15 digits
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'checkboxQuestion':
            if (
              this.isAnyChoiceVisible(block, registrant) &&
              (_.isEmpty(answer) || _.isEmpty(_.pickBy(answer)))
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'selectQuestion':
          case 'radioQuestion':
            if (
              this.isAnyChoiceVisible(block, registrant) &&
              _.isEmpty(answer)
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'addressQuestion':
            if (
              _.isEmpty(answer.address1) ||
              _.isEmpty(answer.state) ||
              _.isEmpty(answer.city) ||
              _.isEmpty(answer.zip)
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          default:
            if (_.isEmpty(answer)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
        }
      });

      return invalidBlocks;
    };
  });
<commit_msg>Update regex to catch invalid emails<commit_after>angular
  .module('confRegistrationWebApp')
  .service('validateRegistrant', function validateRegistrant(
    $window,
    ruleTypeConstants,
    $filter,
  ) {
    const blockVisibleRuleCheck = (block, registrant, ruleType) => {
      let answers = registrant.answers;
      let ruleOperand = '';
      let validRuleCount = 0;
      let blockTypeSpecificRules = [];

      switch (ruleType) {
        case ruleTypeConstants.SHOW_QUESTION: {
          blockTypeSpecificRules = $filter('showQuestionFilter')(block.rules);
          ruleOperand =
            block.content && block.content.ruleoperand
              ? block.content.ruleoperand
              : 'AND';
          break;
        }
        case ruleTypeConstants.FORCE_SELECTION: {
          blockTypeSpecificRules = _.filter(block.rules, {
            ruleType: ruleType,
          });
          ruleOperand =
            block.content && block.content.forceSelectionRuleOperand
              ? block.content.forceSelectionRuleOperand
              : 'AND';
          break;
        }
        default: {
          if ($window.Rollbar) {
            $window.Rollbar.error(
              'blockVisibleRuleCheck was called with an unknown rule type: ',
              ruleType,
            );
          }
          ruleOperand = 'AND';
          break;
        }
      }

      if ($window.Rollbar && !_.isArray(blockTypeSpecificRules)) {
        $window.Rollbar.info(
          'Block rules value in blockVisibleRuleCheck, ruleType: ' +
            ruleType +
            ', typeof: ' +
            typeof blockTypeSpecificRules +
            ', JSON.stringify: ' +
            JSON.stringify(blockTypeSpecificRules),
        );
      }

      _.forEach(blockTypeSpecificRules, (rule, i) => {
        let answer = _.find(answers, { blockId: rule.parentBlockId });
        if (angular.isDefined(answer) && answer.value !== '') {
          let answerValue;
          let ruleValue;
          if (angular.isObject(answer.value)) {
            //answer of checkboxquestion will be an object
            answerValue = angular.isDefined(answer.value[rule.value])
              ? answer.value[rule.value]
              : false;
            ruleValue = true;
          } else {
            //If string is a number, parse it as a float for numerical comparison
            answerValue = !isNaN(answer.value)
              ? parseFloat(answer.value)
              : answer.value;
            ruleValue = !isNaN(rule.value)
              ? parseFloat(rule.value)
              : rule.value;
          }

          if (rule.operator === '=' && answerValue === ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '!=' && answerValue !== ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '>' && answerValue > ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '<' && answerValue < ruleValue) {
            validRuleCount++;
          }
        }
        if (
          (ruleOperand === 'OR' && validRuleCount > 0) ||
          (ruleOperand === 'AND' && validRuleCount <= i)
        ) {
          return false; // Exit lodash foreach as we found a case which determines the whole outcome of this function
        }
      });

      return (
        !blockTypeSpecificRules ||
        blockTypeSpecificRules.length === 0 || // If no rules are set
        (ruleOperand === 'OR' && validRuleCount > 0) ||
        (ruleOperand === 'AND' &&
          validRuleCount === blockTypeSpecificRules.length)
      );
    };

    const choiceVisibleRuleCheck = (block, choice, registrant, ruleType) => {
      let answers = registrant.answers;
      let ruleOperand = '';
      let validRuleCount = 0;
      let blockTypeSpecificRules = [];

      switch (ruleType) {
        case ruleTypeConstants.SHOW_OPTION: {
          blockTypeSpecificRules = _.filter(block.rules, {
            ruleType: ruleType,
            blockEntityOption: choice.value,
          });

          ruleOperand = choice.operand ? choice.operand : 'OR';
          break;
        }
        default: {
          if ($window.Rollbar) {
            $window.Rollbar.error(
              'choiceVisibleRuleCheck was called with an unknown rule type: ',
              ruleType,
            );
          }
          ruleOperand = 'AND';
          break;
        }
      }

      if ($window.Rollbar && !_.isArray(blockTypeSpecificRules)) {
        $window.Rollbar.info(
          'Block rules value in blockVisibleRuleCheck, ruleType: ' +
            ruleType +
            ', typeof: ' +
            typeof blockTypeSpecificRules +
            ', JSON.stringify: ' +
            JSON.stringify(blockTypeSpecificRules),
        );
      }

      _.forEach(blockTypeSpecificRules, (rule, i) => {
        let answer = _.find(answers, { blockId: rule.parentBlockId });
        if (angular.isDefined(answer) && answer.value !== '') {
          let answerValue;
          let ruleValue;
          if (angular.isObject(answer.value)) {
            //answer of checkboxquestion will be an object
            answerValue = angular.isDefined(answer.value[rule.value])
              ? answer.value[rule.value]
              : false;
            ruleValue = true;
          } else {
            //If string is a number, parse it as a float for numerical comparison
            answerValue = !isNaN(answer.value)
              ? parseFloat(answer.value)
              : answer.value;
            ruleValue = !isNaN(rule.value)
              ? parseFloat(rule.value)
              : rule.value;
          }

          if (rule.operator === '=' && answerValue === ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '!=' && answerValue !== ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '>' && answerValue > ruleValue) {
            validRuleCount++;
          } else if (rule.operator === '<' && answerValue < ruleValue) {
            validRuleCount++;
          }
        }
        if (
          (ruleOperand === 'OR' && validRuleCount > 0) ||
          (ruleOperand === 'AND' && validRuleCount <= i)
        ) {
          return false; // Exit lodash foreach as we found a case which determines the whole outcome of this function
        }
      });

      return (
        !blockTypeSpecificRules ||
        blockTypeSpecificRules.length === 0 || // If no rules are set
        (ruleOperand === 'OR' && validRuleCount > 0) ||
        (ruleOperand === 'AND' &&
          validRuleCount === blockTypeSpecificRules.length)
      );
    };

    const blockInRegistrantType = (block, registrant) => {
      return !_.includes(block.registrantTypes, registrant.registrantTypeId);
    };

    this.isAnyChoiceVisible = (block, registrant) => {
      if (
        block.type !== 'checkboxQuestion' &&
        block.type !== 'selectQuestion' &&
        block.type !== 'radioQuestion'
      ) {
        return true;
      }

      // If a block has no content choices because the user forgot to add them, automatically return false.
      if (!block.content.choices) {
        return false;
      }

      for (let i = 0, len = block.content.choices.length; i < len; i++) {
        if (this.choiceVisible(block, block.content.choices[i], registrant)) {
          return true;
        }
      }
      return false;
    };

    this.blockVisible = (block, registrant, isAdmin) => {
      const visible =
        angular.isDefined(registrant) &&
        blockVisibleRuleCheck(
          block,
          registrant,
          ruleTypeConstants.SHOW_QUESTION,
        ) &&
        blockInRegistrantType(block, registrant) &&
        this.isAnyChoiceVisible(block, registrant);

      return block.adminOnly && !isAdmin ? false : visible;
    };

    this.choiceVisible = (block, choice, registrant) => {
      return (
        angular.isDefined(registrant) &&
        choiceVisibleRuleCheck(
          block,
          choice,
          registrant,
          ruleTypeConstants.SHOW_OPTION,
        )
      );
    };

    this.checkboxDisable = (block, registrant) => {
      return blockVisibleRuleCheck(
        block,
        registrant,
        ruleTypeConstants.FORCE_SELECTION,
      );
    };

    this.validate = (conference, registrant, page) => {
      let invalidBlocks = [];

      conference = angular.copy(conference);
      const blocks = page
        ? _.find(conference.registrationPages, { id: page }).blocks
        : _.flatten(_.map(conference.registrationPages, 'blocks'));

      _.forEach(blocks, block => {
        if (
          !block.required ||
          block.adminOnly ||
          !blockVisibleRuleCheck(
            block,
            registrant,
            ruleTypeConstants.SHOW_QUESTION,
          ) ||
          !blockInRegistrantType(block, registrant)
        ) {
          return;
        }

        let answer = _.find(registrant.answers, { blockId: block.id });
        if (angular.isUndefined(answer)) {
          invalidBlocks.push(block.id);
          return;
        }
        answer = answer.value;

        switch (block.type) {
          case 'nameQuestion':
            if (_.isEmpty(answer.firstName) || _.isEmpty(answer.lastName)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'emailQuestion':
            if (
              _.isEmpty(answer) ||
              _.isNull(
                answer.match(
                  // pulled from https://github.com/jquense/yup/blob/acbb8b4f3c24ceaf65eab09abaf8e086a9f11a73/src/string.ts#L9
                  // eslint-disable-next-line no-control-regex
                  /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/,
                ),
              )
            ) {
              //Contains an @ sign surrounded by at least one character
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'numberQuestion':
            if (!_.isNumber(answer)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'phoneQuestion':
            if (
              _.isNull(answer) ||
              _.isNull(answer.match(/^(\D*\d\D*){10,15}$/))
            ) {
              //Contains 10-15 digits
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'checkboxQuestion':
            if (
              this.isAnyChoiceVisible(block, registrant) &&
              (_.isEmpty(answer) || _.isEmpty(_.pickBy(answer)))
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'selectQuestion':
          case 'radioQuestion':
            if (
              this.isAnyChoiceVisible(block, registrant) &&
              _.isEmpty(answer)
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          case 'addressQuestion':
            if (
              _.isEmpty(answer.address1) ||
              _.isEmpty(answer.state) ||
              _.isEmpty(answer.city) ||
              _.isEmpty(answer.zip)
            ) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
          default:
            if (_.isEmpty(answer)) {
              invalidBlocks.push(block.id);
              return;
            }
            break;
        }
      });

      return invalidBlocks;
    };
  });
<|endoftext|>