<commit_before>'use strict';
/*----------------------------------------------------------------*/
/* Gulp Tasks
/*----------------------------------------------------------------*/
var utils = require('./utils');
var display = utils.display();
var gulp = require('gulp');
var path = require('path');
var Promise = require('promise');
var del = require('del');
var g = require('gulp-load-plugins')();
var proxy = require('proxy-middleware');
var server = require('browser-sync');
var url = require('url');
var runSeq = require('run-sequence').use(gulp);
var config = require('./config');
var shell = utils.shell();

gulp.task('compile:scripts', [], function() {

    var webpackConf = utils.deepMerge(require(path.resolve(__dirname, './configs/webpack.conf.js')), {
        output: {
            filename: config.main,
            library: config.name
        }
    });
    webpackConf.externals.unshift(/^[a-z\-0-9]+$/);

    return gulp.src( path.resolve(config.main) )
        .pipe(g.webpack(webpackConf))
        .pipe(g.ngAnnotate())
        .pipe(gulp.dest(config.paths.target));
});

gulp.task('compile:styles', [], function() {
    return gulp.src(config.paths.styles + '/base.less')
        // Use less compiler
        .pipe(g.less()).on('error', g.notify.onError())
        // Use sass compiler
        .pipe(server.reload({
            stream: true
        }))
        .pipe(gulp.dest(config.paths.styles));
});

gulp.task('server', [], function() {
    var proxies = config.proxies;
    var paths = config.paths;
    var middleWares = [];
    Object.keys(proxies).forEach(function(k){
        var proxyOptions = url.parse(proxies[k]);
        proxyOptions.route = k;
        middleWares.push(proxy(proxyOptions));
    });
    server({
        open: false,
        //files: [ paths.scripts ],
        server: {
            baseDir: ['./', paths.target],
            minify: true,
            index: paths.index,
            // proxy: 'localhost:7777',
            middleware: middleWares
        }
    });
});
gulp.task('server:reload', function() {
    server.reload();
});

gulp.task('test:lint', function() {
    var rules = require(path.resolve(__dirname, './configs/eslint.conf.json'));
    var src = [
        config.paths.scripts + '/**/*.js',
        '!**/_migration/**/*.js'
    ];
    gulp.src(src)
        .pipe(g.eslint(rules))
        .pipe(g.eslint.format())
        .pipe(g.eslint.failOnError())
        .on('error', g.notify.onError());
});

gulp.task('start', ['test:lint', 'compile:scripts', 'compile:styles', 'server'], function() {
    // watch scripts
    gulp.watch([
        config.paths.scripts + '/**/*.js'
    ], ['test:lint', 'compile:scripts', 'server:reload']);

    gulp.watch([
        config.paths.styles + '/**/*.less',
        config.paths.scripts + '/components/**/*.less'
    ], ['compile:styles']);

    gulp.watch([
        config.paths.index,
        config.paths.templates + '/**/*.html'
    ], ['server:reload']);

});

gulp.task('build:html', function () {

});

gulp.task('build:scripts', ['compile:scripts'], function() {
    return gulp.src([
            path.resolve(config.paths.target, config.main)
        ])
        .pipe(g.uglify({
            mangle: {
                except: ['require']
            }
        }))
        // .pipe(g.concat('main.min.js'))
        .pipe(gulp.dest(config.paths.target + '/scripts'));
});

// Build
gulp.task('build:styles', ['compile:styles'], function() {

    return gulp.src(config.paths.styles + '/base.css')
        .pipe(g.less({
            compress: true
        }))
        .pipe(gulp.dest(config.paths.target + '/styles'));

});

exports.build = function() {
    return new Promise(function(resolve) {
        runSeq(['build:scripts', 'build:styles'], 'build:html', resolve);
    });
};

exports.start = function() {
    display.info('Using config:', utils.logObj(config));
    gulp.start('start');
};

exports.template = function(src, data) {
    // # TODO exclude some paths
    var paths = [
        src + '/**'
    ];
    return new Promise(function(resolve, reject) {
        gulp.src(paths)
            .pipe(g.template(data))
            .pipe(gulp.dest(src))
            .on('end', resolve)
            .on('error', reject);
        });
};

/*----------------------------------------------------------------*/
/* Clean up folders
/*----------------------------------------------------------------*/
exports.clean = function() {
    var paths = [
        config.paths.target
    ];
    return new Promise(function(resolve) {
        del( paths, resolve);
    });
};
/*----------------------------------------------------------------*/
/* Test
/*----------------------------------------------------------------*/
exports.test = function(watch) {
    var karma = require('karma').server;

    var karmaConf = path.resolve(__dirname, './configs/karma.conf.js');
    var reporters = ['mocha'];
    if(/^darwin/.test(process.platform)) {
        reporters.push('osx');
    }
    var opts = {
        configFile: karmaConf,
        singleRun: !watch,
        reporters: reporters
    };
    // display.info('Using options:' + utils.logObj(opts) );
    return new Promise(function(resolve, reject) {
        karma.start(opts, function(code){
            if(code > 0) { reject(new Error('Testing failed!')); }
            else { resolve(); }
        });
    });
};

/*----------------------------------------------------------------*/
/* Lint
/*----------------------------------------------------------------*/
exports.lint = function() {
    return new Promise(function(resolve) {
        gulp.start('test:lint', resolve);
    });
};

/*----------------------------------------------------------------*/
/* Register
/*----------------------------------------------------------------*/

var setPackage = function(registry, flag) {
    var endpoint;
    switch(flag) {
        case 'register':
            endpoint = '/registerPackage';
        break;
        case 'remove':
            endpoint = '/removePackage';
        break;
    }

    var url = utils.trimRight(registry, '/') + endpoint;

    var repo = config.repository.url || undefined;
    var name = config.name;
    var error;

    var tpl = utils.template('curl --request POST "${url}" -d name="${name}" -d repo="${repo}"');
    var cmd = tpl({
        url: url,
        name: name,
        repo: repo
    });
    return new Promise(function(resolve, reject) {
        if ( ! shell.which('curl')) {
            error = 'Sorry, this script requires curl';
            reject(error);
            throw new Error(error);
        }
        // # TODO test for valid repos
        if( utils.isUndefined(repo) ) {
            error = 'Repository url is missing.';
            reject(error);
            throw new Error(error);
        }
        if(utils.isUndefined(registry) || !utils.isString(registry)) {
            error = 'Invalid registry format.';
            reject(error);
            throw new Error(error);
        }

        shell.exec(cmd, {},function(code, output) {
            if(code > 0) {
                reject(output);
                throw new Error(output);
            }
            resolve(cmd);
            return cmd;
        });

    });
}

exports.register = function(registry) {
    return setPackage(registry, 'register');
};
exports.unregister = function(registry) {
    return setPackage(registry, 'remove');
};
<commit_msg>update tasks<commit_after>'use strict';
/*----------------------------------------------------------------*/
/* Gulp Tasks
/*----------------------------------------------------------------*/
var utils = require('./utils');
var display = utils.display();
var gulp = require('gulp');
var path = require('path');
var Promise = require('promise');
var del = require('del');
var g = require('gulp-load-plugins')();
var proxy = require('proxy-middleware');
var server = require('browser-sync');
var url = require('url');
var runSeq = require('run-sequence').use(gulp);
var config = require('./config');
var shell = utils.shell();

gulp.task('compile:scripts', [], function() {

    var webpackConf = utils.deepMerge(require(path.resolve(__dirname, './configs/webpack.conf.js')), {
        output: {
            filename: config.main,
            library: config.name
        }
    });
    webpackConf.externals.unshift(/^[a-z\-0-9]+$/);
    return gulp.src( path.resolve(config.main) )
        .pipe(g.webpack(webpackConf))
        .pipe(g.ngAnnotate())
        .pipe(gulp.dest(config.paths.target));
});

gulp.task('compile:styles', [], function() {
    return gulp.src(config.paths.styles + '/base.less')
        // Use less compiler
        .pipe(g.less()).on('error', g.notify.onError())
        // Use sass compiler
        .pipe(server.reload({
            stream: true
        }))
        .pipe(gulp.dest(config.paths.styles));
});

gulp.task('server', [], function() {
    var proxies = config.proxies;
    var paths = config.paths;
    var middleWares = [];
    Object.keys(proxies).forEach(function(k){
        var proxyOptions = url.parse(proxies[k]);
        proxyOptions.route = k;
        middleWares.push(proxy(proxyOptions));
    });
    server({
        open: false,
        //files: [ paths.scripts ],
        server: {
            baseDir: ['./', paths.target],
            minify: true,
            index: paths.index,
            // proxy: 'localhost:7777',
            middleware: middleWares
        }
    });
});
gulp.task('server:reload', function() {
    server.reload();
});

gulp.task('test:lint', function() {
    var rules = require(path.resolve(__dirname, './configs/eslint.conf.json'));
    var src = [
        config.paths.scripts + '/**/*.js',
        '!**/_migration/**/*.js'
    ];
    gulp.src(src)
        .pipe(g.eslint(rules))
        .pipe(g.eslint.format())
        .pipe(g.eslint.failOnError())
        .on('error', g.notify.onError());
});

gulp.task('start', ['test:lint', 'build:scripts', 'compile:styles', 'server'], function() {
    // watch scripts
    gulp.watch([
        config.paths.scripts + '/**/*.js'
    ], ['test:lint', 'build:scripts', 'server:reload']);

    gulp.watch([
        config.paths.styles + '/**/*.less',
        config.paths.scripts + '/components/**/*.less'
    ], ['compile:styles']);

    gulp.watch([
        config.paths.index,
        config.paths.templates + '/**/*.html'
    ], ['server:reload']);

});

gulp.task('build:html', function () {

});

gulp.task('build:scripts', ['compile:scripts'], function() {
    return gulp.src([
            path.resolve(config.paths.target, config.main)
        ])
        .pipe(g.uglify({
            mangle: {
                except: ['require']
            }
        }))
        // .pipe(g.concat('main.min.js'))
        .pipe(gulp.dest(config.paths.target + '/scripts'));
});

// Build styles
gulp.task('build:styles', ['compile:styles'], function() {
    var src = path.resolve(config.paths.target, 'base.css');
    var target = path.resolve(config.paths.target, 'styles');

    return gulp.src(src)
        .pipe(g.changed(target))
        .pipe(g.less({
            compress: true
        }))
        .pipe(gulp.dest(target));

});

// Build images
gulp.task('build:images', [], function() {
    var src = path.resolve(config.paths.media, '*');
    var target = path.resolve(config.paths.target);
    return gulp.src(src,{base: '.'})
        .pipe(g.changed(target))
        .pipe(gulp.dest(target));

});
// Build all
gulp.task('build', ['build:images', 'build:scripts', 'build:styles', 'build:html'], function(done) {
    done();
});

exports.build = function() {
    return new Promise(function(resolve) {
        runSeq(['build'], resolve);
    });
};

exports.start = function() {
    display.info('Using config:', utils.logObj(config));
    gulp.start('start');
};

exports.template = function(src, data) {
    // # TODO exclude some paths
    var paths = [
        src + '/**'
    ];
    return new Promise(function(resolve, reject) {
        gulp.src(paths)
            .pipe(g.template(data))
            .pipe(gulp.dest(src))
            .on('end', resolve)
            .on('error', reject);
        });
};

/*----------------------------------------------------------------*/
/* Clean up folders
/*----------------------------------------------------------------*/
exports.clean = function() {
    var paths = [
        config.paths.target
    ];
    return new Promise(function(resolve) {
        del( paths, resolve);
    });
};
/*----------------------------------------------------------------*/
/* Test
/*----------------------------------------------------------------*/
exports.test = function(watch) {
    var karma = require('karma').server;

    var karmaConf = path.resolve(__dirname, './configs/karma.conf.js');
    var reporters = ['mocha'];
    if(/^darwin/.test(process.platform)) {
        reporters.push('osx');
    }
    var opts = {
        configFile: karmaConf,
        singleRun: !watch,
        reporters: reporters
    };
    // display.info('Using options:' + utils.logObj(opts) );
    return new Promise(function(resolve, reject) {
        karma.start(opts, function(code){
            if(code > 0) { reject(new Error('Testing failed!')); }
            else { resolve(); }
        });
    });
};

/*----------------------------------------------------------------*/
/* Lint
/*----------------------------------------------------------------*/
exports.lint = function() {
    return new Promise(function(resolve) {
        gulp.start('test:lint', resolve);
    });
};

/*----------------------------------------------------------------*/
/* Register
/*----------------------------------------------------------------*/

var setPackage = function(registry, flag) {
    var endpoint;
    switch(flag) {
        case 'register':
            endpoint = '/registerPackage';
        break;
        case 'remove':
            endpoint = '/removePackage';
        break;
    }

    var url = utils.trimRight(registry, '/') + endpoint;

    var repo = config.repository.url || undefined;
    var name = config.name;
    var error;

    var tpl = utils.template('curl --request POST "${url}" -d name="${name}" -d repo="${repo}"');
    var cmd = tpl({
        url: url,
        name: name,
        repo: repo
    });
    return new Promise(function(resolve, reject) {
        if ( ! shell.which('curl')) {
            error = 'Sorry, this script requires curl';
            reject(error);
            throw new Error(error);
        }
        // # TODO test for valid repos
        if( utils.isUndefined(repo) ) {
            error = 'Repository url is missing.';
            reject(error);
            throw new Error(error);
        }
        if(utils.isUndefined(registry) || !utils.isString(registry)) {
            error = 'Invalid registry format.';
            reject(error);
            throw new Error(error);
        }

        shell.exec(cmd, {},function(code, output) {
            if(code > 0) {
                reject(output);
                throw new Error(output);
            }
            resolve(cmd);
            return cmd;
        });

    });
}

exports.register = function(registry) {
    return setPackage(registry, 'register');
};
exports.unregister = function(registry) {
    return setPackage(registry, 'remove');
};
<|endoftext|>


<commit_before>import buildUrl from 'build-url';
import moment from 'moment';

import { BaseScraperWithBrowser, LOGIN_RESULT } from './base-scraper-with-browser';
import { waitForNavigationAndDomLoad, waitForRedirect } from '../helpers/navigation';
import { waitUntilElementFound } from '../helpers/elements-interactions';
import { NORMAL_TXN_TYPE, INSTALLMENTS_TXN_TYPE, SHEKEL_CURRENCY_SYMBOL, SHEKEL_CURRENCY } from '../constants';
import getAllMonthMoments from '../helpers/dates';
import { fixInstallments, sortTransactionsByDate, filterOldTransactions } from '../helpers/transactions';

const BASE_URL = 'https://online.leumi-card.co.il';
const DATE_FORMAT = 'DD/MM/YYYY';
const NORMAL_TYPE_NAME = 'רגילה';
const ATM_TYPE_NAME = 'חיוב עסקות מיידי';
const INTERNET_SHOPPING_TYPE_NAME = 'אינטרנט/חו"ל';
const INSTALLMENTS_TYPE_NAME = 'תשלומים';
const MONTHLY_CHARGE_TYPE_NAME = 'חיוב חודשי';
const ONE_MONTH_POSTPONED_TYPE_NAME = 'דחוי חודש';
const TWO_MONTHS_POSTPONED_TYPE_NAME = 'דחוי חודשיים';

function redirectOrDialog(page) {
  return Promise.race([
    waitForRedirect(page),
    waitUntilElementFound(page, '#popupWrongDetails', true),
  ]);
}

function getTransactionsUrl(monthMoment) {
  let monthCharge = null;
  let actionType = 1;
  if (monthMoment) {
    const month = monthMoment.month() + 1;
    const monthStr = month < 10 ? `0${month}` : month.toString();
    const year = monthMoment.year();
    monthCharge = `${year}${monthStr}`;
    actionType = 2;
  }
  return buildUrl(BASE_URL, {
    path: 'Registred/Transactions/ChargesDeals.aspx',
    queryParams: {
      ActionType: actionType,
      MonthCharge: monthCharge,
      Index: -2,
    },
  });
}

function getTransactionType(txnTypeStr) {
  switch (txnTypeStr.trim()) {
    case ATM_TYPE_NAME:
    case NORMAL_TYPE_NAME:
    case MONTHLY_CHARGE_TYPE_NAME:
    case ONE_MONTH_POSTPONED_TYPE_NAME:
    case TWO_MONTHS_POSTPONED_TYPE_NAME:
    case INTERNET_SHOPPING_TYPE_NAME:
      return NORMAL_TXN_TYPE;
    case INSTALLMENTS_TYPE_NAME:
      return INSTALLMENTS_TXN_TYPE;
    default:
      throw new Error(`unknown transaction type ${txnTypeStr}`);
  }
}

function getAmountData(amountStr) {
  const amountStrCopy = amountStr.replace(',', '');
  let currency = null;
  let amount = null;
  if (amountStrCopy.includes(SHEKEL_CURRENCY_SYMBOL)) {
    amount = parseFloat(amountStrCopy.replace(SHEKEL_CURRENCY_SYMBOL, ''));
    currency = SHEKEL_CURRENCY;
  } else {
    const parts = amountStrCopy.split(' ');
    amount = parseFloat(parts[0]);
    [, currency] = parts;
  }

  return {
    amount,
    currency,
  };
}

function getInstallmentsInfo(comments) {
  if (!comments) {
    return null;
  }
  const matches = comments.match(/\d+/g);
  if (!matches || matches.length < 2) {
    return null;
  }

  return {
    number: parseInt(matches[0], 10),
    total: parseInt(matches[1], 10),
  };
}

function convertTransactions(rawTxns) {
  return rawTxns.map((txn) => {
    const originalAmountData = getAmountData(txn.originalAmountStr);
    const chargedAmountData = getAmountData(txn.chargedAmountStr);
    return {
      type: getTransactionType(txn.typeStr),
      date: moment(txn.dateStr, DATE_FORMAT).toISOString(),
      processedDate: moment(txn.processedDateStr, DATE_FORMAT).toISOString(),
      originalAmount: -originalAmountData.amount,
      originalCurrency: originalAmountData.currency,
      chargedAmount: -chargedAmountData.amount,
      description: txn.description.trim(),
      installments: getInstallmentsInfo(txn.comments),
    };
  });
}

async function getCardContainers(page) {
  return page.$$('.infoList_holder');
}

async function getCardContainer(page, cardIndex) {
  const cardContainers = await getCardContainers(page);
  const cardContainer = cardContainers[cardIndex];
  return cardContainer;
}

async function getCardSections(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const cardSections = await cardContainer.$$('.NotPaddingTable');
  return cardSections;
}

async function getAccountNumber(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const infoContainer = await cardContainer.$('.creditCard_name');
  const numberListItems = await infoContainer.$$('li');
  const numberListItem = numberListItems[1];
  const accountNumberStr = await page.evaluate((li) => {
    return li.innerText;
  }, numberListItem);
  const accountNumber = accountNumberStr.replace('(', '').replace(')', '');

  return accountNumber;
}

async function getTransactionsForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  const txnsRows = await cardSections[sectionIndex].$$('.jobs_regular');
  const txns = [];
  for (let txnIndex = 0; txnIndex < txnsRows.length; txnIndex += 1) {
    const txnColumns = await txnsRows[txnIndex].$$('td');

    const typeStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[4]);

    const dateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[1]);

    const processedDateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[2]);

    const originalAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[5]);

    const chargedAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[6]);

    const description = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[3]);

    const comments = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[7]);

    const txn = {
      typeStr,
      dateStr,
      processedDateStr,
      originalAmountStr,
      chargedAmountStr,
      description,
      comments,
    };
    txns.push(txn);
  }

  return txns;
}

async function getNextPageButtonForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  return cardSections[sectionIndex].$('.difdufLeft a');
}

async function getCurrentTransactions(page) {
  const result = {};
  const cardContainers = await getCardContainers(page);

  for (let cardIndex = 0; cardIndex < cardContainers.length; cardIndex += 1) {
    const txns = [];
    const cardSections = await getCardSections(page, cardIndex);
    for (let sectionIndex = 0; sectionIndex < cardSections.length; sectionIndex += 1) {
      let hasNext = true;
      while (hasNext) {
        const sectionTxns = await getTransactionsForSection(page, cardIndex, sectionIndex);
        txns.push(...sectionTxns);

        const nextPageBtn = await getNextPageButtonForSection(page, cardIndex, sectionIndex);
        if (nextPageBtn) {
          await nextPageBtn.click();
          await waitForNavigationAndDomLoad(page);
        } else {
          hasNext = false;
        }
      }
    }

    const accountNumber = await getAccountNumber(page, cardIndex);
    result[accountNumber] = convertTransactions(txns);
  }

  return result;
}

async function fetchTransactionsForMonth(page, monthMoment) {
  const url = getTransactionsUrl(monthMoment);
  await page.goto(url);

  return getCurrentTransactions(page);
}

function addResult(allResults, result) {
  const clonedResults = Object.assign({}, allResults);
  Object.keys(result).forEach((accountNumber) => {
    if (!clonedResults[accountNumber]) {
      clonedResults[accountNumber] = [];
    }
    clonedResults[accountNumber].push(...result[accountNumber]);
  });
  return clonedResults;
}

function prepareTransactions(txns, startMoment, combineInstallments) {
  let clonedTxns = Array.from(txns);
  if (!combineInstallments) {
    clonedTxns = fixInstallments(clonedTxns);
  }
  clonedTxns = sortTransactionsByDate(clonedTxns);
  clonedTxns = filterOldTransactions(clonedTxns, startMoment, combineInstallments);
  return clonedTxns;
}

async function fetchTransactions(page, options) {
  const defaultStartMoment = moment().subtract(1, 'years');
  const startDate = options.startDate || defaultStartMoment.toDate();
  const startMoment = moment.max(defaultStartMoment, moment(startDate));
  const allMonths = getAllMonthMoments(startMoment, false);

  let allResults = {};
  for (let i = 0; i < allMonths.length; i += 1) {
    const result = await fetchTransactionsForMonth(page, allMonths[i]);
    allResults = addResult(allResults, result);
  }

  const result = await fetchTransactionsForMonth(page);
  allResults = addResult(allResults, result);

  Object.keys(allResults).forEach((accountNumber) => {
    let txns = allResults[accountNumber];
    txns = prepareTransactions(txns, startMoment, options.combineInstallments);
    allResults[accountNumber] = txns;
  });

  return allResults;
}

async function getAccountData(page, options) {
  const accountsPage = `${BASE_URL}/Registred/Transactions/ChargesDeals.aspx`;
  await page.goto(accountsPage);

  const results = await fetchTransactions(page, options);
  const accounts = Object.keys(results).map((accountNumber) => {
    return {
      accountNumber,
      txns: results[accountNumber],
    };
  });

  return {
    success: true,
    accounts,
  };
}

function getPossibleLoginResults() {
  const urls = {};
  urls[LOGIN_RESULT.SUCCESS] = [`${BASE_URL}/Registred/HomePage.aspx`];
  urls[LOGIN_RESULT.INVALID_PASSWORD] = [`${BASE_URL}/Anonymous/Login/CardHoldersLogin.aspx`];
  return urls;
}

function createLoginFields(inputGroupName, credentials) {
  return [
    { selector: `#${inputGroupName}_txtUserName`, value: credentials.username },
    { selector: `#${inputGroupName}_txtPassword`, value: credentials.password },
  ];
}

class LeumiCardScraper extends BaseScraperWithBrowser {
  getLoginOptions(credentials) {
    const inputGroupName = 'PlaceHolderMain_CardHoldersLogin1';
    return {
      loginUrl: `${BASE_URL}/Anonymous/Login/CardHoldersLogin.aspx`,
      fields: createLoginFields(inputGroupName, credentials),
      submitButtonSelector: `#${inputGroupName}_btnLogin`,
      postAction: async () => redirectOrDialog(this.page),
      possibleResults: getPossibleLoginResults(),
    };
  }

  async fetchData() {
    return getAccountData(this.page, this.options);
  }
}

export default LeumiCardScraper;
<commit_msg>remove initial ununsed navigation in leumicard<commit_after>import buildUrl from 'build-url';
import moment from 'moment';

import { BaseScraperWithBrowser, LOGIN_RESULT } from './base-scraper-with-browser';
import { waitForNavigationAndDomLoad, waitForRedirect } from '../helpers/navigation';
import { waitUntilElementFound } from '../helpers/elements-interactions';
import { NORMAL_TXN_TYPE, INSTALLMENTS_TXN_TYPE, SHEKEL_CURRENCY_SYMBOL, SHEKEL_CURRENCY } from '../constants';
import getAllMonthMoments from '../helpers/dates';
import { fixInstallments, sortTransactionsByDate, filterOldTransactions } from '../helpers/transactions';

const BASE_URL = 'https://online.leumi-card.co.il';
const DATE_FORMAT = 'DD/MM/YYYY';
const NORMAL_TYPE_NAME = 'רגילה';
const ATM_TYPE_NAME = 'חיוב עסקות מיידי';
const INTERNET_SHOPPING_TYPE_NAME = 'אינטרנט/חו"ל';
const INSTALLMENTS_TYPE_NAME = 'תשלומים';
const MONTHLY_CHARGE_TYPE_NAME = 'חיוב חודשי';
const ONE_MONTH_POSTPONED_TYPE_NAME = 'דחוי חודש';
const TWO_MONTHS_POSTPONED_TYPE_NAME = 'דחוי חודשיים';

function redirectOrDialog(page) {
  return Promise.race([
    waitForRedirect(page),
    waitUntilElementFound(page, '#popupWrongDetails', true),
  ]);
}

function getTransactionsUrl(monthMoment) {
  let monthCharge = null;
  let actionType = 1;
  if (monthMoment) {
    const month = monthMoment.month() + 1;
    const monthStr = month < 10 ? `0${month}` : month.toString();
    const year = monthMoment.year();
    monthCharge = `${year}${monthStr}`;
    actionType = 2;
  }
  return buildUrl(BASE_URL, {
    path: 'Registred/Transactions/ChargesDeals.aspx',
    queryParams: {
      ActionType: actionType,
      MonthCharge: monthCharge,
      Index: -2,
    },
  });
}

function getTransactionType(txnTypeStr) {
  switch (txnTypeStr.trim()) {
    case ATM_TYPE_NAME:
    case NORMAL_TYPE_NAME:
    case MONTHLY_CHARGE_TYPE_NAME:
    case ONE_MONTH_POSTPONED_TYPE_NAME:
    case TWO_MONTHS_POSTPONED_TYPE_NAME:
    case INTERNET_SHOPPING_TYPE_NAME:
      return NORMAL_TXN_TYPE;
    case INSTALLMENTS_TYPE_NAME:
      return INSTALLMENTS_TXN_TYPE;
    default:
      throw new Error(`unknown transaction type ${txnTypeStr}`);
  }
}

function getAmountData(amountStr) {
  const amountStrCopy = amountStr.replace(',', '');
  let currency = null;
  let amount = null;
  if (amountStrCopy.includes(SHEKEL_CURRENCY_SYMBOL)) {
    amount = parseFloat(amountStrCopy.replace(SHEKEL_CURRENCY_SYMBOL, ''));
    currency = SHEKEL_CURRENCY;
  } else {
    const parts = amountStrCopy.split(' ');
    amount = parseFloat(parts[0]);
    [, currency] = parts;
  }

  return {
    amount,
    currency,
  };
}

function getInstallmentsInfo(comments) {
  if (!comments) {
    return null;
  }
  const matches = comments.match(/\d+/g);
  if (!matches || matches.length < 2) {
    return null;
  }

  return {
    number: parseInt(matches[0], 10),
    total: parseInt(matches[1], 10),
  };
}

function convertTransactions(rawTxns) {
  return rawTxns.map((txn) => {
    const originalAmountData = getAmountData(txn.originalAmountStr);
    const chargedAmountData = getAmountData(txn.chargedAmountStr);
    return {
      type: getTransactionType(txn.typeStr),
      date: moment(txn.dateStr, DATE_FORMAT).toISOString(),
      processedDate: moment(txn.processedDateStr, DATE_FORMAT).toISOString(),
      originalAmount: -originalAmountData.amount,
      originalCurrency: originalAmountData.currency,
      chargedAmount: -chargedAmountData.amount,
      description: txn.description.trim(),
      installments: getInstallmentsInfo(txn.comments),
    };
  });
}

async function getCardContainers(page) {
  return page.$$('.infoList_holder');
}

async function getCardContainer(page, cardIndex) {
  const cardContainers = await getCardContainers(page);
  const cardContainer = cardContainers[cardIndex];
  return cardContainer;
}

async function getCardSections(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const cardSections = await cardContainer.$$('.NotPaddingTable');
  return cardSections;
}

async function getAccountNumber(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const infoContainer = await cardContainer.$('.creditCard_name');
  const numberListItems = await infoContainer.$$('li');
  const numberListItem = numberListItems[1];
  const accountNumberStr = await page.evaluate((li) => {
    return li.innerText;
  }, numberListItem);
  const accountNumber = accountNumberStr.replace('(', '').replace(')', '');

  return accountNumber;
}

async function getTransactionsForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  const txnsRows = await cardSections[sectionIndex].$$('.jobs_regular');
  const txns = [];
  for (let txnIndex = 0; txnIndex < txnsRows.length; txnIndex += 1) {
    const txnColumns = await txnsRows[txnIndex].$$('td');

    const typeStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[4]);

    const dateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[1]);

    const processedDateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[2]);

    const originalAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[5]);

    const chargedAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[6]);

    const description = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[3]);

    const comments = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[7]);

    const txn = {
      typeStr,
      dateStr,
      processedDateStr,
      originalAmountStr,
      chargedAmountStr,
      description,
      comments,
    };
    txns.push(txn);
  }

  return txns;
}

async function getNextPageButtonForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  return cardSections[sectionIndex].$('.difdufLeft a');
}

async function getCurrentTransactions(page) {
  const result = {};
  const cardContainers = await getCardContainers(page);

  for (let cardIndex = 0; cardIndex < cardContainers.length; cardIndex += 1) {
    const txns = [];
    const cardSections = await getCardSections(page, cardIndex);
    for (let sectionIndex = 0; sectionIndex < cardSections.length; sectionIndex += 1) {
      let hasNext = true;
      while (hasNext) {
        const sectionTxns = await getTransactionsForSection(page, cardIndex, sectionIndex);
        txns.push(...sectionTxns);

        const nextPageBtn = await getNextPageButtonForSection(page, cardIndex, sectionIndex);
        if (nextPageBtn) {
          await nextPageBtn.click();
          await waitForNavigationAndDomLoad(page);
        } else {
          hasNext = false;
        }
      }
    }

    const accountNumber = await getAccountNumber(page, cardIndex);
    result[accountNumber] = convertTransactions(txns);
  }

  return result;
}

async function fetchTransactionsForMonth(page, monthMoment) {
  const url = getTransactionsUrl(monthMoment);
  await page.goto(url);

  return getCurrentTransactions(page);
}

function addResult(allResults, result) {
  const clonedResults = Object.assign({}, allResults);
  Object.keys(result).forEach((accountNumber) => {
    if (!clonedResults[accountNumber]) {
      clonedResults[accountNumber] = [];
    }
    clonedResults[accountNumber].push(...result[accountNumber]);
  });
  return clonedResults;
}

function prepareTransactions(txns, startMoment, combineInstallments) {
  let clonedTxns = Array.from(txns);
  if (!combineInstallments) {
    clonedTxns = fixInstallments(clonedTxns);
  }
  clonedTxns = sortTransactionsByDate(clonedTxns);
  clonedTxns = filterOldTransactions(clonedTxns, startMoment, combineInstallments);
  return clonedTxns;
}

async function fetchTransactions(page, options) {
  const defaultStartMoment = moment().subtract(1, 'years');
  const startDate = options.startDate || defaultStartMoment.toDate();
  const startMoment = moment.max(defaultStartMoment, moment(startDate));
  const allMonths = getAllMonthMoments(startMoment, false);

  let allResults = {};
  for (let i = 0; i < allMonths.length; i += 1) {
    const result = await fetchTransactionsForMonth(page, allMonths[i]);
    allResults = addResult(allResults, result);
  }

  const result = await fetchTransactionsForMonth(page);
  allResults = addResult(allResults, result);

  Object.keys(allResults).forEach((accountNumber) => {
    let txns = allResults[accountNumber];
    txns = prepareTransactions(txns, startMoment, options.combineInstallments);
    allResults[accountNumber] = txns;
  });

  return allResults;
}

async function getAccountData(page, options) {
  const results = await fetchTransactions(page, options);
  const accounts = Object.keys(results).map((accountNumber) => {
    return {
      accountNumber,
      txns: results[accountNumber],
    };
  });

  return {
    success: true,
    accounts,
  };
}

function getPossibleLoginResults() {
  const urls = {};
  urls[LOGIN_RESULT.SUCCESS] = [`${BASE_URL}/Registred/HomePage.aspx`];
  urls[LOGIN_RESULT.INVALID_PASSWORD] = [`${BASE_URL}/Anonymous/Login/CardHoldersLogin.aspx`];
  return urls;
}

function createLoginFields(inputGroupName, credentials) {
  return [
    { selector: `#${inputGroupName}_txtUserName`, value: credentials.username },
    { selector: `#${inputGroupName}_txtPassword`, value: credentials.password },
  ];
}

class LeumiCardScraper extends BaseScraperWithBrowser {
  getLoginOptions(credentials) {
    const inputGroupName = 'PlaceHolderMain_CardHoldersLogin1';
    return {
      loginUrl: `${BASE_URL}/Anonymous/Login/CardHoldersLogin.aspx`,
      fields: createLoginFields(inputGroupName, credentials),
      submitButtonSelector: `#${inputGroupName}_btnLogin`,
      postAction: async () => redirectOrDialog(this.page),
      possibleResults: getPossibleLoginResults(),
    };
  }

  async fetchData() {
    return getAccountData(this.page, this.options);
  }
}

export default LeumiCardScraper;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Button, Clipboard, Text, TouchableOpacity, View } from 'react-native';

import { FluidCard, FluidHeader, FluidButton } from '../components';

import { height, width } from '../global';

class ActiveTrip extends Component {
  state = { id: '', results: {} };

  constructor(props) {
    super(props);
  }

  componentWillMount() {
    this.fetchID();
    console.log('height: ' + height + ' width: ' + width);
  }

  copyID = id => {
    Clipboard.setString(id);
  };

  fetchID = () => {
    fetch('http://localhost:8000/v1/keygen')
      .then(response => response.json())
      .then(responseJSON => {
        this.setState({ id: responseJSON.id });
      })
      .catch(error => {
        console.log('Failed to fetch response.', error);
      });
  };

  fetchResults = () => {
    fetch('http://localhost:8000/v1/results?id=' + this.state.id)
      .then(response => response.json())
      .then(responseJSON => {
        this.setState({ results: responseJSON });
      })
      .catch(error => {
        console.log('Failed to fetch response.', error);
      });
  };

  // TODO: Disable card button, until animation is complete.
  // TODO: Flip card into the new trip view.
  // TODO: Figure out 'edit-trip'

  render() {
    return (
      <View>
        <FluidHeader>Ongoing</FluidHeader>
        <TouchableOpacity>
          <FluidCard height={height * 0.52}>
            <Text style={styles.textStyle}>Tap to add a new trip.</Text>
          </FluidCard>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = {
  textStyle: {
    fontFamily: 'Rubik',
    fontSize: 18,
    fontWeight: '200',
    textAlign: 'center',
  },
};

export default ActiveTrip;
<commit_msg>remove log statements<commit_after>import React, { Component } from 'react';
import { Button, Clipboard, Text, TouchableOpacity, View } from 'react-native';

import { FluidCard, FluidHeader, FluidButton } from '../components';

import { height, width } from '../global';

class ActiveTrip extends Component {
  state = { id: '', results: {} };

  constructor(props) {
    super(props);
  }

  componentWillMount() {
    this.fetchID();
  }

  copyID = id => {
    Clipboard.setString(id);
  };

  fetchID = () => {
    fetch('http://localhost:8000/v1/keygen')
      .then(response => response.json())
      .then(responseJSON => {
        this.setState({ id: responseJSON.id });
      })
      .catch(error => {
        console.log('Failed to fetch response.', error);
      });
  };

  fetchResults = () => {
    fetch('http://localhost:8000/v1/results?id=' + this.state.id)
      .then(response => response.json())
      .then(responseJSON => {
        this.setState({ results: responseJSON });
      })
      .catch(error => {
        console.log('Failed to fetch response.', error);
      });
  };

  // TODO: Disable card button, until animation is complete.
  // TODO: Flip card into the new trip view.
  // TODO: Figure out 'edit-trip'

  render() {
    return (
      <View>
        <FluidHeader>Ongoing</FluidHeader>
        <TouchableOpacity>
          <FluidCard height={height * 0.52}>
            <Text style={styles.textStyle}>Tap to add a new trip.</Text>
          </FluidCard>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = {
  textStyle: {
    fontFamily: 'Rubik',
    fontSize: 18,
    fontWeight: '200',
    textAlign: 'center',
  },
};

export default ActiveTrip;
<|endoftext|>


<commit_before>$(document).ready(function() {
    _init('CA')
});
function _init(defaultState) {
    if (defaultState) { _generateDashboards(defaultState); }

    $('select').change(function(){
        var stateAbbrev = this.value;
        _generateDashboards(stateAbbrev);
    });
}

function _generateDashboards (state) {
    dashboardAreaChart(state);
    appendStateDescription(state);
    dashboardHomocideRate(state);
    dashboardRapeRate(state);
    dashboardRobberyRate(state);
    dashboardAssaultRate(state);
    // dashboardPopAreaBarCharts(state);
}

// function dashboardPopAreaBarCharts (state) {
//     var rawCSV = 'data/prison-rearrest.csv';
//     var stateData = [];
//     $.get(rawCSV, function (csv) {
//         var data = _csvToArray(csv);
//         for (var i = 0; i < data.length; i++) {
//             if (data[i][1] == state) {
//                 stateData.push(data[i]);
//             }
//         }
//         _generateMetroBarChart(state, stateData[0]);
//         _generateMicroBarChart(state, stateData[1]);
//         _generateNonMetroBarChart(state, stateData[2]);
//     });
// }

function dashboardHomocideRate (state) {
    _generateLineChartData(state, 'Homocide', 'homocide');
}

function dashboardRapeRate (state) {
    _generateLineChartData (state, 'Rape', 'rape');
}

function dashboardRobberyRate (state)  {
    _generateLineChartData (state, 'Robbery', 'robbery');
}

function dashboardAssaultRate (state)  {
    _generateLineChartData (state, 'Aggravated Assault', 'assault');
}

//function _appendPercentChange (id, percentChange) {
 //   $(id).text(percentChange);
//}

// Area chart
function dashboardAreaChart (state) {
    var stateCSV = 'data/prison-rearrest.csv';
    var stateData, stateName;
    $.get(stateCSV, function(csv) {
        var data = _csvToArray(csv);
        for (var i = 0; i < data.length; i++) {
            if (data[i][1] == state) {
                stateName = data[i][0];
//                percentChange = data[i][data[i].length - 1];
                // increaseOrDecline = parseFloat(data[i][data[i].length - 1]) > 0 ? 'increased' : 'declined';
                stateData = _formatStateData(data[i]);
            }
        }
//        _appendPercentChange('#crime-rate-chart-percent-change', percentChange);
        _appendPageTitles(stateName);
        _createAreaChart(stateName, stateData);
    });
}

function _appendPageTitles (stateName) {
    $('#title').text(stateName + '\'s violent crime rate overall between 2006 and 2016.');
    $('#category-title').text('Change in ' + stateName + '\'s Violent Crime Rate (Incidents per 100,000 Residents) by Category, 2006–2016')
    $('#bar-chart-title').text('Change in ' + stateName + '\'s Overall Violent Crime Rate (Incidents per 100,000 Residents) by Population Area, 2006–2016');
}

function appendStateDescription (state) {
    var content = $('#state-description');
    var lormIpsum = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur';
    switch (state) {
      case 'AL':
        content.text(lormIpsum + state)
        break;
      case 'AK':
        content.text(lormIpsum + state)
        break;
      case 'AZ':
        content.text(lormIpsum + state)
        break;
      case 'AR':
        content.text(lormIpsum + state)
        break;
      case 'CA':
        content.text(lormIpsum + state)
        break;
      case 'CO':
        content.text(lormIpsum + state)
        break;
      case 'CT':
        content.text(lormIpsum + state)
        break;
      case 'DE':
        content.text(lormIpsum + state)
        break;
      case 'FL':
        content.text(lormIpsum + state)
        break;
      case 'GA':
        content.text(lormIpsum + state)
        break;
      case 'HI':
        content.text(lormIpsum + state)
        break;
      case 'ID':
        content.text(lormIpsum + state)
        break;
      case 'IL':
        content.text(lormIpsum + state)
        break;
      case 'IN':
        content.text(lormIpsum + state)
        break;
      case 'IA':
        content.text(lormIpsum + state)
        break;
      case 'KS':
        content.text(lormIpsum + state)
        break;
      case 'KY':
        content.text(lormIpsum + state)
        break;
      case 'LA':
        content.text(lormIpsum + state)
        break;
      case 'ME':
        content.text(lormIpsum + state)
        break;
      case 'MD':
        content.text(lormIpsum + state)
        break;
      case 'MA':
        content.text(lormIpsum + state)
        break;
      case 'MI':
        content.text(lormIpsum + state)
        break;
      case 'MN':
        content.text(lormIpsum + state)
        break;
      case 'MS':
        content.text(lormIpsum + state)
        break;
      case 'MO':
        content.text(lormIpsum + state)
        break;
      case 'MT':
        content.text(lormIpsum + state)
        break;
      case 'NE':
        content.text(lormIpsum + state)
        break;
      case 'NV':
        content.text(lormIpsum + state)
        break;
      case 'NH':
        content.text(lormIpsum + state)
        break;
      case 'NJ':
        content.text(lormIpsum + state)
        break;
      case 'NM':
        content.text(lormIpsum + state)
        break;
      case 'NY':
        content.text(lormIpsum + state)
        break;
      case 'NC':
        content.text(lormIpsum + state)
        break;
      case 'ND':
        content.text(lormIpsum + state)
        break;
      case 'OH':
        content.text(lormIpsum + state)
        break;
      case 'OK':
        content.text(lormIpsum + state)
        break;
      case 'OR':
        content.text(lormIpsum + state)
        break;
      case 'PA':
        content.text(lormIpsum + state)
        break;
      case 'RI':
        content.text(lormIpsum + state)
        break;
      case 'SC':
        content.text(lormIpsum + state)
        break;
      case 'SD':
        content.text(lormIpsum + state)
        break;
      case 'TN':
        content.text(lormIpsum + state)
        break;
      case 'TX':
        content.text(lormIpsum + state)
        break;
      case 'UT':
        content.text(lormIpsum + state)
        break;
      case 'VT':
        content.text(lormIpsum + state)
        break;
      case 'VA':
        content.text(lormIpsum + state)
        break;
      case 'WA':
        content.text(lormIpsum + state)
        break;
      case 'WV':
        content.text(lormIpsum + state)
        break;
      case 'WI':
        content.text(lormIpsum + state)
        break;
      case 'WY':
        content.text(lormIpsum + state)
        break;
    }
}
// end area chart

// Bar chart functions
// function _generateMetroBarChart (state, data) {
//     var id = 'metro';
//     var name = 'Metropolitan Areas';
//     var subtitle = '(population of 50,000+)'
// //    var percentChange = data[data.length - 1];
// //    _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _generateMicroBarChart (state, data) {
//     var id = 'micro';
//     var name = 'Micropolitan Areas';
//     var subtitle = '(population of 10,000–49,999)';
// //    var percentChange = data[data.length - 1];
//  //   _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _generateNonMetroBarChart (state, data) {
//     var id = 'non-metro';
//     var name = 'Non-Metropolitan Areas';
//     var subtitle = '(population of fewer than 10,000)';
// //    var percentChange = data[data.length - 1];
// //    _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _cutBarChartData (data) {
//     var cutData = [];
//     cutData.push(Number(data.slice(4, 5)[0]));
//     cutData.push(Number(data.slice(6, 7)[0]));
//     return cutData;
// }

// function _createBarChart (data, id, name, subtitle) {
//     var myChart = Highcharts.chart(id, {
//         chart: {
//             type: 'bar'
//         },
//         title: {
//             text: name
//         },
//         subtitle: {
//             text: subtitle
//         },
//         xAxis: {
//             categories: ['', ''],
//             labels: {
//                 enabled: false
//             }
//         },
//         yAxis: {
//             title: {
//                 text: ''
//             },
//             labels: {
//                 enabled: false
//             }
//         },
//         legend: {
//             enabled: false
//         },
//         colors: [
//             '#E76946',
//             '#FBB731'
//         ],
//         plotOptions: {
//                series: {
//                    pointPadding: 0,
//                    groupPadding: 0,
//                    colorByPoint: true
//                }
//            },
//         series: [{
//             data: data
//         }]
//     });
// }
// end bar chart

// Line chart functions
function _createAreaChart (stateName, stateData) {
    Highcharts.setOptions({lang: {noData: "No Data Available"}})
    var myChart = Highcharts.chart('crime-rate-chart', {
        chart: {
            type: 'column'
        },
        title: {
            text: 'Change in ' + stateName + '\'s Overall Violent Crime Rate (Incidents per 100,000 Residents), 2004–2013'
        },
        xAxis: {
            categories: ['2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013']
        },
        legend: {
            enabled: false
        },
        yAxis: {
            title: {
                text: ''
            }
        },
        series: [{
            name: stateName,
            data: stateData,
            color: '#66ccff',
            fillOpacity: 1
        }],
    });
}

function _generateLineChartData (state, title, type) {
   // var stateCSV = 'data/' + type + '-rate.csv';
   var stateCSV = 'data/prison-rearrest.csv';
    var stateData, stateName;
    $.get(stateCSV, function (csv) {
        var data = _csvToArray(csv);
        for (var i = 0; i < data.length; i++) {
            if (data[i][1] == state) {
                stateName = data[i][0];
//                percentChange = data[i][data[i].length - 1];
                stateData = _formatStateData(data[i]);
            }
        }
        var chartId = type + '-chart';
//        _appendPercentChange('#' + type + '-percent-change', percentChange);
        _createLineChart(stateName, stateData, title, chartId);
    });
}

function _createLineChart (stateName, stateData, title, chartId) {
    var myChart = Highcharts.chart(chartId, {
        chart: {
            type: 'line'
        },
        title: {
            text: title
        },
        xAxis: {
            categories: ['2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016']
        },
        legend: {
            enabled: false
        },
        yAxis: {
            title: {
                text: ''
            }
        },
        series: [{
            name: stateName,
            data: stateData
        }]
    });
}

function _csvToArray (csv) {
    rows  = csv.split("\n");
    return rows.map(function (row) {
        return row.split(",");
    });
};

function arraysIdentical(a, b) {
    var i = a.length;
    if (i != b.length) return false;
    while (i--) {
        if (a[i] !== b[i]) return false;
    }
    return true;
};

function _formatStateData (data) {
    debugger;
    data.splice(0, 2);
    // data.splice(-1, 1);
    data = data.map(Number);
    for (var i = 0; i < data.length; i++) {
            if (data[i] == 0) {
               data[i] = 'Data not available'
            }
        }
    if (arraysIdentical(data, ["Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available"]){
        data = []
    }

    return data;
}
// end line chart functions<commit_msg>Syntax in arraysIdentical<commit_after>$(document).ready(function() {
    _init('CA')
});
function _init(defaultState) {
    if (defaultState) { _generateDashboards(defaultState); }

    $('select').change(function(){
        var stateAbbrev = this.value;
        _generateDashboards(stateAbbrev);
    });
}

function _generateDashboards (state) {
    dashboardAreaChart(state);
    appendStateDescription(state);
    dashboardHomocideRate(state);
    dashboardRapeRate(state);
    dashboardRobberyRate(state);
    dashboardAssaultRate(state);
    // dashboardPopAreaBarCharts(state);
}

// function dashboardPopAreaBarCharts (state) {
//     var rawCSV = 'data/prison-rearrest.csv';
//     var stateData = [];
//     $.get(rawCSV, function (csv) {
//         var data = _csvToArray(csv);
//         for (var i = 0; i < data.length; i++) {
//             if (data[i][1] == state) {
//                 stateData.push(data[i]);
//             }
//         }
//         _generateMetroBarChart(state, stateData[0]);
//         _generateMicroBarChart(state, stateData[1]);
//         _generateNonMetroBarChart(state, stateData[2]);
//     });
// }

function dashboardHomocideRate (state) {
    _generateLineChartData(state, 'Homocide', 'homocide');
}

function dashboardRapeRate (state) {
    _generateLineChartData (state, 'Rape', 'rape');
}

function dashboardRobberyRate (state)  {
    _generateLineChartData (state, 'Robbery', 'robbery');
}

function dashboardAssaultRate (state)  {
    _generateLineChartData (state, 'Aggravated Assault', 'assault');
}

//function _appendPercentChange (id, percentChange) {
 //   $(id).text(percentChange);
//}

// Area chart
function dashboardAreaChart (state) {
    var stateCSV = 'data/prison-rearrest.csv';
    var stateData, stateName;
    $.get(stateCSV, function(csv) {
        var data = _csvToArray(csv);
        for (var i = 0; i < data.length; i++) {
            if (data[i][1] == state) {
                stateName = data[i][0];
//                percentChange = data[i][data[i].length - 1];
                // increaseOrDecline = parseFloat(data[i][data[i].length - 1]) > 0 ? 'increased' : 'declined';
                stateData = _formatStateData(data[i]);
            }
        }
//        _appendPercentChange('#crime-rate-chart-percent-change', percentChange);
        _appendPageTitles(stateName);
        _createAreaChart(stateName, stateData);
    });
}

function _appendPageTitles (stateName) {
    $('#title').text(stateName + '\'s violent crime rate overall between 2006 and 2016.');
    $('#category-title').text('Change in ' + stateName + '\'s Violent Crime Rate (Incidents per 100,000 Residents) by Category, 2006–2016')
    $('#bar-chart-title').text('Change in ' + stateName + '\'s Overall Violent Crime Rate (Incidents per 100,000 Residents) by Population Area, 2006–2016');
}

function appendStateDescription (state) {
    var content = $('#state-description');
    var lormIpsum = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur';
    switch (state) {
      case 'AL':
        content.text(lormIpsum + state)
        break;
      case 'AK':
        content.text(lormIpsum + state)
        break;
      case 'AZ':
        content.text(lormIpsum + state)
        break;
      case 'AR':
        content.text(lormIpsum + state)
        break;
      case 'CA':
        content.text(lormIpsum + state)
        break;
      case 'CO':
        content.text(lormIpsum + state)
        break;
      case 'CT':
        content.text(lormIpsum + state)
        break;
      case 'DE':
        content.text(lormIpsum + state)
        break;
      case 'FL':
        content.text(lormIpsum + state)
        break;
      case 'GA':
        content.text(lormIpsum + state)
        break;
      case 'HI':
        content.text(lormIpsum + state)
        break;
      case 'ID':
        content.text(lormIpsum + state)
        break;
      case 'IL':
        content.text(lormIpsum + state)
        break;
      case 'IN':
        content.text(lormIpsum + state)
        break;
      case 'IA':
        content.text(lormIpsum + state)
        break;
      case 'KS':
        content.text(lormIpsum + state)
        break;
      case 'KY':
        content.text(lormIpsum + state)
        break;
      case 'LA':
        content.text(lormIpsum + state)
        break;
      case 'ME':
        content.text(lormIpsum + state)
        break;
      case 'MD':
        content.text(lormIpsum + state)
        break;
      case 'MA':
        content.text(lormIpsum + state)
        break;
      case 'MI':
        content.text(lormIpsum + state)
        break;
      case 'MN':
        content.text(lormIpsum + state)
        break;
      case 'MS':
        content.text(lormIpsum + state)
        break;
      case 'MO':
        content.text(lormIpsum + state)
        break;
      case 'MT':
        content.text(lormIpsum + state)
        break;
      case 'NE':
        content.text(lormIpsum + state)
        break;
      case 'NV':
        content.text(lormIpsum + state)
        break;
      case 'NH':
        content.text(lormIpsum + state)
        break;
      case 'NJ':
        content.text(lormIpsum + state)
        break;
      case 'NM':
        content.text(lormIpsum + state)
        break;
      case 'NY':
        content.text(lormIpsum + state)
        break;
      case 'NC':
        content.text(lormIpsum + state)
        break;
      case 'ND':
        content.text(lormIpsum + state)
        break;
      case 'OH':
        content.text(lormIpsum + state)
        break;
      case 'OK':
        content.text(lormIpsum + state)
        break;
      case 'OR':
        content.text(lormIpsum + state)
        break;
      case 'PA':
        content.text(lormIpsum + state)
        break;
      case 'RI':
        content.text(lormIpsum + state)
        break;
      case 'SC':
        content.text(lormIpsum + state)
        break;
      case 'SD':
        content.text(lormIpsum + state)
        break;
      case 'TN':
        content.text(lormIpsum + state)
        break;
      case 'TX':
        content.text(lormIpsum + state)
        break;
      case 'UT':
        content.text(lormIpsum + state)
        break;
      case 'VT':
        content.text(lormIpsum + state)
        break;
      case 'VA':
        content.text(lormIpsum + state)
        break;
      case 'WA':
        content.text(lormIpsum + state)
        break;
      case 'WV':
        content.text(lormIpsum + state)
        break;
      case 'WI':
        content.text(lormIpsum + state)
        break;
      case 'WY':
        content.text(lormIpsum + state)
        break;
    }
}
// end area chart

// Bar chart functions
// function _generateMetroBarChart (state, data) {
//     var id = 'metro';
//     var name = 'Metropolitan Areas';
//     var subtitle = '(population of 50,000+)'
// //    var percentChange = data[data.length - 1];
// //    _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _generateMicroBarChart (state, data) {
//     var id = 'micro';
//     var name = 'Micropolitan Areas';
//     var subtitle = '(population of 10,000–49,999)';
// //    var percentChange = data[data.length - 1];
//  //   _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _generateNonMetroBarChart (state, data) {
//     var id = 'non-metro';
//     var name = 'Non-Metropolitan Areas';
//     var subtitle = '(population of fewer than 10,000)';
// //    var percentChange = data[data.length - 1];
// //    _appendPercentChange('#' + id + '-percent-change', percentChange);
//     var cutData = _cutBarChartData(data);
//     _createBarChart(cutData, id, name, subtitle);
// }

// function _cutBarChartData (data) {
//     var cutData = [];
//     cutData.push(Number(data.slice(4, 5)[0]));
//     cutData.push(Number(data.slice(6, 7)[0]));
//     return cutData;
// }

// function _createBarChart (data, id, name, subtitle) {
//     var myChart = Highcharts.chart(id, {
//         chart: {
//             type: 'bar'
//         },
//         title: {
//             text: name
//         },
//         subtitle: {
//             text: subtitle
//         },
//         xAxis: {
//             categories: ['', ''],
//             labels: {
//                 enabled: false
//             }
//         },
//         yAxis: {
//             title: {
//                 text: ''
//             },
//             labels: {
//                 enabled: false
//             }
//         },
//         legend: {
//             enabled: false
//         },
//         colors: [
//             '#E76946',
//             '#FBB731'
//         ],
//         plotOptions: {
//                series: {
//                    pointPadding: 0,
//                    groupPadding: 0,
//                    colorByPoint: true
//                }
//            },
//         series: [{
//             data: data
//         }]
//     });
// }
// end bar chart

// Line chart functions
function _createAreaChart (stateName, stateData) {
    Highcharts.setOptions({lang: {noData: "No Data Available"}})
    var myChart = Highcharts.chart('crime-rate-chart', {
        chart: {
            type: 'column'
        },
        title: {
            text: 'Change in ' + stateName + '\'s Overall Violent Crime Rate (Incidents per 100,000 Residents), 2004–2013'
        },
        xAxis: {
            categories: ['2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013']
        },
        legend: {
            enabled: false
        },
        yAxis: {
            title: {
                text: ''
            }
        },
        series: [{
            name: stateName,
            data: stateData,
            color: '#66ccff',
            fillOpacity: 1
        }],
    });
}

function _generateLineChartData (state, title, type) {
   // var stateCSV = 'data/' + type + '-rate.csv';
   var stateCSV = 'data/prison-rearrest.csv';
    var stateData, stateName;
    $.get(stateCSV, function (csv) {
        var data = _csvToArray(csv);
        for (var i = 0; i < data.length; i++) {
            if (data[i][1] == state) {
                stateName = data[i][0];
//                percentChange = data[i][data[i].length - 1];
                stateData = _formatStateData(data[i]);
            }
        }
        var chartId = type + '-chart';
//        _appendPercentChange('#' + type + '-percent-change', percentChange);
        _createLineChart(stateName, stateData, title, chartId);
    });
}

function _createLineChart (stateName, stateData, title, chartId) {
    var myChart = Highcharts.chart(chartId, {
        chart: {
            type: 'line'
        },
        title: {
            text: title
        },
        xAxis: {
            categories: ['2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016']
        },
        legend: {
            enabled: false
        },
        yAxis: {
            title: {
                text: ''
            }
        },
        series: [{
            name: stateName,
            data: stateData
        }]
    });
}

function _csvToArray (csv) {
    rows  = csv.split("\n");
    return rows.map(function (row) {
        return row.split(",");
    });
};

function arraysIdentical(a, b) {
    var i = a.length;
    if (i != b.length) {
        return false};
    while (i--) {
        if (a[i] !== b[i]) return false;
    }
    return true;
};

function _formatStateData (data) {
    debugger;
    data.splice(0, 2);
    // data.splice(-1, 1);
    data = data.map(Number);
    for (var i = 0; i < data.length; i++) {
            if (data[i] == 0) {
               data[i] = "Data not available"
            }
        }
    if (arraysIdentical(data, ["Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available", "Data not available"]){
        data = []
    }

    return data;
}
// end line chart functions<|endoftext|>


<commit_before>import page  from 'page';
import React from 'react/addons';
import User  from '../models/user';
import Board from '../models/board';

import UserStore     from '../stores/user';
import BoardStore    from '../stores/board';
import SettingsStore from '../stores/settings';

import BoardAction     from '../actions/board';
import TicketAction    from '../actions/ticket';
import SettingsAction  from '../actions/settings';
import BroadcastAction from '../actions/broadcast';

import listener from '../mixins/listener';

import Control         from '../components/control';
import Scrollable      from '../components/scrollable';
import Navigation      from '../components/navigation';
import Broadcaster     from '../components/broadcaster';
import BoardComponent  from '../components/board';

import EditBoardDialog   from '../components/dialog/edit-board';
import ExportBoardDialog from '../components/dialog/export-board.js';
import ShareBoardDialog  from '../components/dialog/share-board';
import ReviewView        from '../components/dialog/review-view';

/**
 * Fix issues with iOS and IScroll not working together too well...
 */
function preventDefault(event) {
	return event.preventDefault();
}

/**
 *
 */
export default React.createClass({
	propTypes: {
		id: React.PropTypes.string.isRequired,
		user: (props) => {
			if(!props.user instanceof User) throw new Error();
		}
	},

	mixins: [
		listener(UserStore, BoardStore, SettingsStore),
	],

	onChange() {
		return this.setState(this.getState());
	},

	getState() {
		return {
			board:       BoardStore.getBoard(this.props.id) || new Board(),
			snapToGrid:  SettingsStore.getSetting('snap-to-grid'),
			showMinimap: SettingsStore.getSetting('show-minimap')
		}
	},

	getInitialState() {
		return Object.assign(this.getState(), {
			showEditBoardDialog:   false,
			showExportBoardDialog: false,
			showShareBoardDialog:  false,
			reviewActive:          false,

		});
	},

	componentDidMount() {
		BoardAction.load(this.props.id);
		document.addEventListener('touchmove', preventDefault);
	},

	componentWillUnmount() {
		document.removeEventListener('touchmove', preventDefault);
	},

	toggleEditBoardDialog() {
		this.setState({
			showEditBoardDialog: !this.state.showEditBoardDialog
		});
	},

	toggleExportBoardDialog() {
		this.setState({
			showExportBoardDialog: !this.state.showExportBoardDialog
		});
	},

	toggleReview() {
		if(BoardStore.getTickets(this.props.id).toJS().length !== 0){
			this.setState({ reviewActive: !this.state.reviewActive });
		}
		else {
			BroadcastAction.add({
				type:    'broadcast',
				content: 'You do not have any tickets to review!'
			});
		}
	},

	toggleShareBoardDialog() {
		this.setState({
			showShareBoardDialog: !this.state.showShareBoardDialog
		});
	},

	setReviewClosingButton(mode) {
		this.setState({
			reviewActive: mode
		})
	},

	sendTicketsForReview() {
		return BoardStore.getTickets(this.props.id).toJS().filter ((item) => {
			return item.content !== "" && item.heading !== "" || item.comments.length !== 0
		});
	},

	render() {
		let boardDialog = null;
		let reviewDialog = null;

		if(this.state.showEditBoardDialog) {
			boardDialog = <EditBoardDialog board={this.state.board}
                                    onDismiss={this.toggleEditBoardDialog} />
		} else if(this.state.showExportBoardDialog) {
			boardDialog = <ExportBoardDialog board={this.state.board}
                                    onDismiss={this.toggleExportBoardDialog} />

		} else if(this.state.showShareBoardDialog) {
			boardDialog = <ShareBoardDialog board={this.state.board}
                                    onDismiss={this.toggleShareBoardDialog} />
		}

		if(!this.state.reviewActive) {
			reviewDialog = null;
		} else {
			reviewDialog = <ReviewView tickets = {this.sendTicketsForReview()}
			onDismiss = { this.toggleReview } />;
		}

		return (

				<Broadcaster />
				<Navigation reviewActive={this.state.reviewActive}
					killReview={this.setReviewClosingButton}
					showHelp={true} title={this.state.board.name} />

					<Scrollable board={this.state.board}
							minimap={this.state.showMinimap}>
						<BoardComponent board={this.state.board}
							snap={this.state.snapToGrid} />
					</Scrollable>

				{boardDialog}
				{reviewDialog}
				{this.renderControls()}

		);
	},

	/**
	 * TODO This is a mess, clean up the controls somehow?
	 * TODO Use immutable data to prevent constantly re-rendering the controls.
	 */
	renderControls() {
		let controls = [
			{
				icon:    'eye',
				active:  this.state.reviewActive,
				onClick: this.toggleReview
			},
			{
				icon:    'download',
				active:  this.state.showExportBoardDialog,
				onClick: this.toggleExportBoardDialog
			},
			{
				onClick: () => {
					SettingsAction.setSetting('snap-to-grid',
						!this.state.snapToGrid);
				},
				icon:   'magnet',
				active: this.state.snapToGrid
			},
			{
				onClick: () => {
					SettingsAction.setSetting('show-minimap',
						!this.state.showMinimap);
				},
				icon:   'globe',
				active: this.state.showMinimap
			}
		];
		let userOnlyControls = [
			{
				onClick: () => {
					return page.show('/boards')
				},
				icon: 'arrow-left'
			},
			{
				icon:    'pencil',
				active:  this.state.showEditBoardDialog,
				onClick: this.toggleEditBoardDialog
			},
			{
				icon:    'share-alt',
				active:  this.state.showShareBoardDialog,
				onClick: this.toggleShareBoardDialog
			}

		];
		if(this.props.user.type === User.Type.User) {
			controls = userOnlyControls.concat(controls);
		}

		return (

				{controls.map((control) => {
					return <Control key={control.icon} {...control} />;
				})}

		);
	}
});
<commit_msg>Improved ticketfiltration further<commit_after>import page  from 'page';
import React from 'react/addons';
import User  from '../models/user';
import Board from '../models/board';

import UserStore     from '../stores/user';
import BoardStore    from '../stores/board';
import SettingsStore from '../stores/settings';

import BoardAction     from '../actions/board';
import TicketAction    from '../actions/ticket';
import SettingsAction  from '../actions/settings';
import BroadcastAction from '../actions/broadcast';

import listener from '../mixins/listener';

import Control         from '../components/control';
import Scrollable      from '../components/scrollable';
import Navigation      from '../components/navigation';
import Broadcaster     from '../components/broadcaster';
import BoardComponent  from '../components/board';

import EditBoardDialog   from '../components/dialog/edit-board';
import ExportBoardDialog from '../components/dialog/export-board.js';
import ShareBoardDialog  from '../components/dialog/share-board';
import ReviewView        from '../components/dialog/review-view';

/**
 * Fix issues with iOS and IScroll not working together too well...
 */
function preventDefault(event) {
	return event.preventDefault();
}

/**
 *
 */
export default React.createClass({
	propTypes: {
		id: React.PropTypes.string.isRequired,
		user: (props) => {
			if(!props.user instanceof User) throw new Error();
		}
	},

	mixins: [
		listener(UserStore, BoardStore, SettingsStore),
	],

	onChange() {
		return this.setState(this.getState());
	},

	getState() {
		return {
			board:       BoardStore.getBoard(this.props.id) || new Board(),
			snapToGrid:  SettingsStore.getSetting('snap-to-grid'),
			showMinimap: SettingsStore.getSetting('show-minimap')
		}
	},

	getInitialState() {
		return Object.assign(this.getState(), {
			showEditBoardDialog:   false,
			showExportBoardDialog: false,
			showShareBoardDialog:  false,
			reviewActive:          false,

		});
	},

	componentDidMount() {
		BoardAction.load(this.props.id);
		document.addEventListener('touchmove', preventDefault);
	},

	componentWillUnmount() {
		document.removeEventListener('touchmove', preventDefault);
	},

	toggleEditBoardDialog() {
		this.setState({
			showEditBoardDialog: !this.state.showEditBoardDialog
		});
	},

	toggleExportBoardDialog() {
		this.setState({
			showExportBoardDialog: !this.state.showExportBoardDialog
		});
	},

	toggleReview() {
		if(this.sendTicketsForReview().length !== 0){
			console.log(this.sendTicketsForReview().length)
			this.setState({ reviewActive: !this.state.reviewActive });
		}
		else {
			BroadcastAction.add({
				type:    'broadcast',
				content: 'You do not have any tickets to review!'
			});
		}
	},

	toggleShareBoardDialog() {
		this.setState({
			showShareBoardDialog: !this.state.showShareBoardDialog
		});
	},

	setReviewClosingButton(mode) {
		this.setState({
			reviewActive: mode
		})
	},

	sendTicketsForReview() {
		return BoardStore.getTickets(this.props.id).toJS().filter ((item) => {
			return item.content !== "" && item.heading !== "" || item.comments.length !== 0
		});
	},

	render() {
		console.log(this.sendTicketsForReview())
		let boardDialog = null;
		let reviewDialog = null;

		if(this.state.showEditBoardDialog) {
			boardDialog = <EditBoardDialog board={this.state.board}
                                    onDismiss={this.toggleEditBoardDialog} />
		} else if(this.state.showExportBoardDialog) {
			boardDialog = <ExportBoardDialog board={this.state.board}
                                    onDismiss={this.toggleExportBoardDialog} />

		} else if(this.state.showShareBoardDialog) {
			boardDialog = <ShareBoardDialog board={this.state.board}
                                    onDismiss={this.toggleShareBoardDialog} />
		}

		if(!this.state.reviewActive) {
			reviewDialog = null;
		} else {
			reviewDialog = <ReviewView tickets = {this.sendTicketsForReview()}
			onDismiss = { this.toggleReview } />;
		}

		return (

				<Broadcaster />
				<Navigation reviewActive={this.state.reviewActive}
					killReview={this.setReviewClosingButton}
					showHelp={true} title={this.state.board.name} />

					<Scrollable board={this.state.board}
							minimap={this.state.showMinimap}>
						<BoardComponent board={this.state.board}
							snap={this.state.snapToGrid} />
					</Scrollable>

				{boardDialog}
				{reviewDialog}
				{this.renderControls()}

		);
	},

	/**
	 * TODO This is a mess, clean up the controls somehow?
	 * TODO Use immutable data to prevent constantly re-rendering the controls.
	 */
	renderControls() {
		let controls = [
			{
				icon:    'eye',
				active:  this.state.reviewActive,
				onClick: this.toggleReview
			},
			{
				icon:    'download',
				active:  this.state.showExportBoardDialog,
				onClick: this.toggleExportBoardDialog
			},
			{
				onClick: () => {
					SettingsAction.setSetting('snap-to-grid',
						!this.state.snapToGrid);
				},
				icon:   'magnet',
				active: this.state.snapToGrid
			},
			{
				onClick: () => {
					SettingsAction.setSetting('show-minimap',
						!this.state.showMinimap);
				},
				icon:   'globe',
				active: this.state.showMinimap
			}
		];
		let userOnlyControls = [
			{
				onClick: () => {
					return page.show('/boards')
				},
				icon: 'arrow-left'
			},
			{
				icon:    'pencil',
				active:  this.state.showEditBoardDialog,
				onClick: this.toggleEditBoardDialog
			},
			{
				icon:    'share-alt',
				active:  this.state.showShareBoardDialog,
				onClick: this.toggleShareBoardDialog
			}

		];
		if(this.props.user.type === User.Type.User) {
			controls = userOnlyControls.concat(controls);
		}

		return (

				{controls.map((control) => {
					return <Control key={control.icon} {...control} />;
				})}

		);
	}
});
<|endoftext|>


<commit_before>/**
 * Client tests
 */
/* global sinon */
/*eslint-disable max-nested-callbacks */
/* eslint no-unused-expressions: 0 */

import React from "react";
import { shallow, mount } from "enzyme";
import VictoryContainer from "src/victory-container/victory-container";

describe("components/victory-container", () => {
  it("renders an svg with a role of img", () => {
    const wrapper = shallow(
      <VictoryContainer />
    );
    const output = wrapper.find("svg");
    expect(output.prop("role")).to.contain("img");
  });

  it("renders an svg with a default title node", () => {
    const wrapper = shallow(
      <VictoryContainer />
    );
    const output = wrapper.find("title");
    expect(output.html()).to.contain("Victory Chart");
  });

  it("renders an svg with a desc node", () => {
    const wrapper = shallow(
      <VictoryContainer desc="description"/>
    );
    const output = wrapper.find("desc");
    expect(output.html()).to.contain("description");
  });

  it("renders an svg with the correct viewbox", () => {
    const width = 300;
    const height = 300;
    const wrapper = shallow(
      <VictoryContainer width={width} height={height}/>
    );
    const svg = wrapper.find("svg");
    const viewBoxValue =
      `0 0 ${width} ${height}`;
    expect(svg.prop("viewBox")).to.equal(viewBoxValue);
  });

  it("attaches an event to the container", () => {
    const clickHandler = sinon.spy();
    const wrapper = mount(
      <VictoryContainer events={{onClick: clickHandler}}/>
    );
    const svg = wrapper.find("svg");
    svg.simulate("click");
    expect(clickHandler).called;
  });
});
<commit_msg>update container spec<commit_after>/**
 * Client tests
 */
/* global sinon */
/*eslint-disable max-nested-callbacks */
/* eslint no-unused-expressions: 0 */

import React from "react";
import { shallow, mount } from "enzyme";
import VictoryContainer from "src/victory-container/victory-container";

describe("components/victory-container", () => {
  it("renders an svg with a role of img", () => {
    const wrapper = shallow(
      <VictoryContainer />
    );
    const output = wrapper.find("svg");
    expect(output.prop("role")).to.contain("img");
  });

  it("renders an svg with a title node", () => {
    const wrapper = shallow(
      <VictoryContainer title="Victory Chart"/>
    );
    const output = wrapper.find("title");
    expect(output.html()).to.contain("Victory Chart");
  });

  it("renders an svg with a desc node", () => {
    const wrapper = shallow(
      <VictoryContainer desc="description"/>
    );
    const output = wrapper.find("desc");
    expect(output.html()).to.contain("description");
  });

  it("renders an svg with the correct viewbox", () => {
    const width = 300;
    const height = 300;
    const wrapper = shallow(
      <VictoryContainer width={width} height={height}/>
    );
    const svg = wrapper.find("svg");
    const viewBoxValue =
      `0 0 ${width} ${height}`;
    expect(svg.prop("viewBox")).to.equal(viewBoxValue);
  });

  it("attaches an event to the container", () => {
    const clickHandler = sinon.spy();
    const wrapper = mount(
      <VictoryContainer events={{onClick: clickHandler}}/>
    );
    const svg = wrapper.find("svg");
    svg.simulate("click");
    expect(clickHandler).called;
  });
});
<|endoftext|>


<commit_before>/**
 * Fetch results from ThingM IFTTT service
 *
 * It needs to do:
 * - Load iftt configuration from global config
 * - Fetch IFTTT results periodically from ThingM IFTTT service
 * - When a new event happens
 * -- log the event
 * -- trigger the pattern (hmm, this means this needs access to PatternsApi)
 * --
 *
 *
 *
 *
 *
 * api.thingm.com response looks like:
	{
		"events":[
			{
			"blink1_id":"ABCD1234ABCD1234",
			"date":"1443034643",
			"name":"bobby",
			"source":"bob"
			},
			{
			"blink1_id":"ABCD1234ABCD1234",
			"date":"1394064858",
			"name":"testtest2",
			"source":"testme2"
			}
		],
		"event_count":2,
		"status":"events saved"
	}

*
* Config
*/

'use strict';

// var request = require('request');
var needle = require('needle'); // a better 'request' (or at least, works w/ webpack)

var conf = require('../configuration');
var log = require('../logger');

var PatternsService = require('./patternsService');
var Blink1Service = require('./blink1Service');

var baseUrl = 'http://api.thingm.com/blink1/eventsall/';

var IftttService = {
	config: {},
	rules: {},
	iftttKey: '',
	intervalTimer: null,
    lastTime: 0,
	lastEvents: {},

	setIftttKey: function(k) { this.iftttKey = k; },
	getIftttKey: function() { return this.iftttKey; },

	reloadConfig: function() {
		this.config = conf.readSettings('eventServices:iftttService');
		if( !this.config ) {
			this.config = { intervalSecs: 10, enabled: true };
			conf.saveSettings('iftttService', this.config);
		}
		var allrules = conf.readSettings('eventRules') || [];
		this.rules = allrules.filter( function(r){return r.type === 'ifttt';} );
		this.iftttKey = Blink1Service.iftttKey();
		log.msg("IftttService.reloadConfig. rules=", this.rules);
	},
	// getRules: function() {
	// 	log.msg("IftttService.getRules, rules:",this.rules);
	// 	return (this.rules) ? this.rules : [];
	// },
	start: function() {
		this.reloadConfig();
		log.msg("IftttService.start: rules", this.rules);
		if( ! this.config.intervalSecs ) { this.config.intervalSecs = 15; }
		this.lastTime = Date.now();
		if( !this.config.enabled ) { return; }
		this.fetch();
		this.intervalTimer = setInterval(this.fetch.bind(this), this.config.intervalSecs * 1000);
	},
	stop: function() {
		clearInterval( this.intervalTimer );
	},

	handleResults: function(rule,event) {
		var self = this;
		log.msg("IftttService.handleResults: *** TRIGGERED!!! ***", event,rule);
		self.lastEvents[rule.name] = event.eventDate;
		PatternsService.playPatternByRule( rule );
		// if( rule.enabled ) {
		// 	// if( Blink1Service.rule.blink1Id ) {
		// }
	},
	makeProxyUrl() {
		var proxy = conf.readSettings('proxy');
		var proxyurl  = '';
		if( proxy && proxy.enable ) {
			if( proxy.username ) {
				proxyurl += proxy.username +':'+ proxy.password + '@';
			}
			if( proxy.host ) {
				proxyurl = 'http://' + proxyurl + proxy.host + ':' + proxy.port;
			}
		}
		return proxyurl;
	},

    fetch: function() {
		var self = this;
		var rules = self.rules; //self.getRules();
		var defaultId = '-default-'; // FIXME: see ToolTable.render.makeLastValue

		//if( rules.length === 0 ) { return; } // no rules, don't waste effort
		var url = baseUrl + self.iftttKey;

		var opts = {};
		// var proxyurl = self.makeProxyUrl();
		// if( proxyurl ) {
		// 	opts.proxy = proxyurl;
		// }
		// log.msg("iftttService.fetch: opts:",opts);

		// log.msg("IftttService.fetch:", url, self.lastTime, "defaultId:",defaultId);
        // request(baseUrl + this.iftttKey, function(error, response, body) {
		needle.get( url, opts, function(error, response) {
			// FIXME: do error handling like: net error, bad response, etc.
			if( error ) {
				var errmsg = error.message;
				if( errmsg.indexOf('ENOTFOUND') !== -1 ) { errmsg = 'connection offline'; }
				log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:errmsg} );
				return;
			}
			if( response.statusCode !== 200 ) {
				log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:response.statusMessage} );
				return;
			}
			// otherwise continue as normal
			var respobj = response.body; //JSON.parse(response.body);
			// var shouldSave = false;
			if( respobj.events ) {
				respobj.events.map( function(evt) {

					// FIXME
					// log.addEvent( {type:'info', source:'ifttt', id:defaultId, text:'no new events' } );

					evt.eventDate = new Date(parseInt(1000 * evt.date));
					if (evt.eventDate > self.lastTime ) { // only notice newer than our startup
						// log.msg('IftttService.fetch: new event name:"'+ evt.name+'"');
						log.addEvent( {date:evt.eventDate, type:'trigger', source:'ifttt', id:evt.name, text:evt.source  } );
						rules.map( function(r) {
							log.msg('IftttService.fetch: rule:', JSON.stringify(r));
							if( evt.name.trim() === r.name.trim()) {
								if( !self.lastEvents[r.name] ) { self.lastEvents[r.name] = new Date(0); }
								log.msg("IftttService.fetch: *** RULE MATCH:", evt.name, '--', evt.eventDate, '--', self.lastEvents[r.name]);
								// this.lastEvents[r.name] = this.lastEvents[r.name] || 0;
								if( evt.eventDate > self.lastEvents[r.name] ) {
									self.handleResults( r, evt );
								}
							}
						});
					}
				});
				self.lastTime = new Date(); // == now
				// if( shouldSave ) {
				// 	log.msg("iftttFetcher: saving rules");
				// 	self.config.rules = rules;
				// 	conf.saveSettings("iftttService", self.config);
				// }
			}
			else {
				log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:'bad response'} );
				log.msg("IftttFetcher: bad response: ", response);
			}
        });

    }
};

module.exports = IftttService;
<commit_msg>make IFTTT Service only log, not addEvent on offline state<commit_after>/**
 * Fetch results from ThingM IFTTT service
 *
 * It needs to do:
 * - Load iftt configuration from global config
 * - Fetch IFTTT results periodically from ThingM IFTTT service
 * - When a new event happens
 * -- log the event
 * -- trigger the pattern (hmm, this means this needs access to PatternsApi)
 * --
 *
 *
 *
 *
 *
 * api.thingm.com response looks like:
	{
		"events":[
			{
			"blink1_id":"ABCD1234ABCD1234",
			"date":"1443034643",
			"name":"bobby",
			"source":"bob"
			},
			{
			"blink1_id":"ABCD1234ABCD1234",
			"date":"1394064858",
			"name":"testtest2",
			"source":"testme2"
			}
		],
		"event_count":2,
		"status":"events saved"
	}

*
* Config
*/

'use strict';

// var request = require('request');
var needle = require('needle'); // a better 'request' (or at least, works w/ webpack)

var conf = require('../configuration');
var log = require('../logger');

var PatternsService = require('./patternsService');
var Blink1Service = require('./blink1Service');

var baseUrl = 'http://api.thingm.com/blink1/eventsall/';

var IftttService = {
	config: {},
	rules: {},
	iftttKey: '',
	intervalTimer: null,
    lastTime: 0,
	lastEvents: {},

	setIftttKey: function(k) { this.iftttKey = k; },
	getIftttKey: function() { return this.iftttKey; },

	reloadConfig: function() {
		this.config = conf.readSettings('eventServices:iftttService');
		if( !this.config ) {
			this.config = { intervalSecs: 10, enabled: true };
			conf.saveSettings('iftttService', this.config);
		}
		var allrules = conf.readSettings('eventRules') || [];
		this.rules = allrules.filter( function(r){return r.type === 'ifttt';} );
		this.iftttKey = Blink1Service.iftttKey();
		log.msg("IftttService.reloadConfig. rules=", this.rules);
	},
	// getRules: function() {
	// 	log.msg("IftttService.getRules, rules:",this.rules);
	// 	return (this.rules) ? this.rules : [];
	// },
	start: function() {
		this.reloadConfig();
		log.msg("IftttService.start: rules", this.rules);
		if( ! this.config.intervalSecs ) { this.config.intervalSecs = 15; }
		this.lastTime = Date.now();
		if( !this.config.enabled ) { return; }
		this.fetch();
		this.intervalTimer = setInterval(this.fetch.bind(this), this.config.intervalSecs * 1000);
	},
	stop: function() {
		clearInterval( this.intervalTimer );
	},

	handleResults: function(rule,event) {
		var self = this;
		log.msg("IftttService.handleResults: *** TRIGGERED!!! ***", event,rule);
		self.lastEvents[rule.name] = event.eventDate;
		PatternsService.playPatternByRule( rule );
		// if( rule.enabled ) {
		// 	// if( Blink1Service.rule.blink1Id ) {
		// }
	},
	makeProxyUrl() {
		var proxy = conf.readSettings('proxy');
		var proxyurl  = '';
		if( proxy && proxy.enable ) {
			if( proxy.username ) {
				proxyurl += proxy.username +':'+ proxy.password + '@';
			}
			if( proxy.host ) {
				proxyurl = 'http://' + proxyurl + proxy.host + ':' + proxy.port;
			}
		}
		return proxyurl;
	},

    fetch: function() {
		var self = this;
		var rules = self.rules; //self.getRules();
		var defaultId = '-default-'; // FIXME: see ToolTable.render.makeLastValue

		//if( rules.length === 0 ) { return; } // no rules, don't waste effort
		var url = baseUrl + self.iftttKey;

		var opts = {};
		// var proxyurl = self.makeProxyUrl();
		// if( proxyurl ) {
		// 	opts.proxy = proxyurl;
		// }
		// log.msg("iftttService.fetch: opts:",opts);

		// log.msg("IftttService.fetch:", url, self.lastTime, "defaultId:",defaultId);
        // request(baseUrl + this.iftttKey, function(error, response, body) {
		needle.get( url, opts, function(error, response) {
			// FIXME: do error handling like: net error, bad response, etc.
			if( error ) {
				var errmsg = error.message;
				if( errmsg.indexOf('ENOTFOUND') !== -1 ) { errmsg = 'connection offline'; }
				// log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:errmsg} );
				log.msg('IFTTT connection offline'); // FIXME:
				return;
			}
			if( response.statusCode !== 200 ) {
				log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:response.statusMessage} );
				return;
			}
			// otherwise continue as normal
			var respobj = response.body; //JSON.parse(response.body);
			// var shouldSave = false;
			if( respobj.events ) {
				respobj.events.map( function(evt) {

					// FIXME
					// log.addEvent( {type:'info', source:'ifttt', id:defaultId, text:'no new events' } );

					evt.eventDate = new Date(parseInt(1000 * evt.date));
					if (evt.eventDate > self.lastTime ) { // only notice newer than our startup
						// log.msg('IftttService.fetch: new event name:"'+ evt.name+'"');
						log.addEvent( {date:evt.eventDate, type:'trigger', source:'ifttt', id:evt.name, text:evt.source  } );
						rules.map( function(r) {
							log.msg('IftttService.fetch: rule:', JSON.stringify(r));
							if( evt.name.trim() === r.name.trim()) {
								if( !self.lastEvents[r.name] ) { self.lastEvents[r.name] = new Date(0); }
								log.msg("IftttService.fetch: *** RULE MATCH:", evt.name, '--', evt.eventDate, '--', self.lastEvents[r.name]);
								// this.lastEvents[r.name] = this.lastEvents[r.name] || 0;
								if( evt.eventDate > self.lastEvents[r.name] ) {
									self.handleResults( r, evt );
								}
							}
						});
					}
				});
				self.lastTime = new Date(); // == now
				// if( shouldSave ) {
				// 	log.msg("iftttFetcher: saving rules");
				// 	self.config.rules = rules;
				// 	conf.saveSettings("iftttService", self.config);
				// }
			}
			else {
				log.addEvent( {type:'error', source:'ifttt', id:defaultId, text:'bad response'} );
				log.msg("IftttFetcher: bad response: ", response);
			}
        });

    }
};

module.exports = IftttService;
<|endoftext|>


<commit_before>import { Document } from 'camo';

export class Door extends Document {
  constructor() {
    super();

    this.schema({
      id: String,
      name: String,
      actionGpioPin: Number,
      statusGpioPin: Number,
    });
  }
}
<commit_msg>Remove id from model Door<commit_after>import { Document } from 'camo';

export default class Door extends Document {
  constructor() {
    super();

    this.schema({
      name: String,
      actionGpioPin: Number,
      statusGpioPin: Number,
    });
  }
}
<|endoftext|>