<commit_before>"use strict";

let EventEmitter = require("events");
let http = require("http");
let https = require("https");
let urllib = require("url");
let proxy = require("./proxy");

class Download {
  constructor(url) {
    this.url = url;
    this.events = new EventEmitter;
    this.aborted = false;
  }

  abort() {
    this.aborted = true;
    this.request.abort();
  }

  on(event, callback) {
    this.events.on(event, callback);
    return this;
  }

  off(event, callback) {
    this.events.off(event, callback);
    return this;
  }

  start() {
    let download = this;
    return new Promise((resolve, reject) => {
      let get = function(url) {
        let info = download.parseUrl(url);
        let client = info.protocol == "https:" ? https : http;
        let request = download.request = client.get(info);

        request.on("response", response => {
          if ("location" in response.headers) {
            request.abort();
            let redirect = urllib.resolve(info.href, response.headers.location);
            return get(redirect);
          }

          download.events.emit("response", response);
          let chunks = [];

          response.on("data", proxy(chunks.push, chunks));

          response.on("end", () => {
            if (!download.aborted) {
              let result = {
                data: Buffer.concat(chunks),
                headers: response.headers,
              };
              download.events.emit("end", result);
              resolve(result);
            }
          });
        });

        request.on("error", error => {
          console.error("ERROR", error);
          reject(error);
        });
      };

      get(download.url);
    });
  }

  parseUrl(url) {
    if (url.indexOf("://") == -1) {
      url = "http://" + url;
    }
    return urllib.parse(url);
  }

  static get(url) {
    let download = new Download(url);
    return download.start();
  }
}

/**
 * Returns a Promise that will resolve when download is finished.
 */
exports.download = function(url) {
  return Download.get(url);
};

/**
 * Return a Download object that can be connected to for advanced usage.
 */
exports.request = function(url) {
  return new Download(url);
};
<commit_msg>Modularize library further<commit_after>"use strict";

let EventEmitter = require("events");
let http = require("http");
let https = require("https");
let urllib = require("url");
let proxy = require("./proxy");

class Download {
  constructor(url) {
    this.url = typeof url == "string" ? url : null;
    this.events = new EventEmitter;
    this.aborted = false;
  }

  abort() {
    this.aborted = true;
    this.request.abort();
  }

  on(event, callback) {
    this.events.on(event, callback);
    return this;
  }

  off(event, callback) {
    this.events.off(event, callback);
    return this;
  }

  start() {
    let download = this;
    return new Promise((resolve, reject) => {
      this.resolve(download.url).then(response => {
        let chunks = [];

        response.on("data", proxy(chunks.push, chunks));
        response.on("end", () => {
          if (!download.aborted) {
            let result = {
              data: Buffer.concat(chunks),
              headers: response.headers,
            };
            download.events.emit("end", result);
            resolve(result);
          }
        });
      }, reject);
    });
  }

  static parseUrl(url) {
    if (url.indexOf("://") == -1) {
      url = "http://" + url;
    }
    return urllib.parse(url);
  }

  static get(url) {
    let download = new Download(url);
    return download.start();
  }

  /**
   * Parse the URL and initiate a request.
   */
  static request(url) {
    let info = Download.parseUrl(url);
    let client = info.protocol == "https:" ? https : http;
    let request = client.get(info);
    return request;
  }

  /**
   * Resolve passed request or URL to a "final response" i.e. handle redirect loops.
   *
   * Promise will reject if there is an error.
   */
  resolve(request) {
    let download = this;
    return new Promise((resolve, reject) => {
      let get = function(request) {
        download.request = request;
        request.on("response", response => {
          if ("location" in response.headers) {
            request.abort();
            let redirect = urllib.resolve(download.url, response.headers.location);
            return get(Download.request(redirect));
          } else {
            download.response = response
            resolve(response);
          }
        });
        request.on("error", reject);
      };
      get(typeof request == "string" ? Download.request(request) : request);
    });
  }
}

/**
 * Returns a Promise that will resolve when download is finished.
 */
exports.download = function(url) {
  return Download.get(url);
};

/**
 * Download up to a defined number of bytes.
 */
exports.downloadBytes = function(url, max_size) {
  return new Promise((resolve, reject) => {
    let request = Download.request(url);
    let download = new Download;

    download.resolve(request).then(response => {
      let chunks = [];
      let size = 0;

      response.on("data", chunk => {
        chunks.push(chunk);
        size += chunk.length;

        if (size >= max_size) {
          request.abort();
          resolve({
            data: Buffer.concat(chunks).slice(0, max_size),
            headers: response.headers,
          });
        }
      });

      response.on("end", () => {
        resolve({
          data: Buffer.concat(chunks).slice(0, max_size),
          headers: response.headers,
        });
      });
    }, reject);
  });
}

/**
 * Return a Download object that can be connected to for advanced usage.
 */
exports.request = function(url) {
  return new Download(url);
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
import '../../server/env';

import { ArgumentParser } from 'argparse';
import { get, intersection } from 'lodash';

import { MODERATION_CATEGORIES_ALIASES } from '../../server/constants/moderation-categories';
import orderStatus from '../../server/constants/order_status';
import { purgeCacheForCollective } from '../../server/lib/cache';
import logger from '../../server/lib/logger';
import { notifyAdminsOfCollective } from '../../server/lib/notifications';
import * as libPayments from '../../server/lib/payments';
import models, { Op, sequelize } from '../../server/models';

// Fetch all orders potentially affected: contributor flagged AND recipient setup rejection

const query = `SELECT "Orders"."id"
  FROM "Orders", "Collectives", "Collectives" as "FromCollectives"
  WHERE "Orders"."CollectiveId" = "Collectives"."id" AND "FromCollectives"."id" = "Orders"."FromCollectiveId"
  AND "Orders"."status" IN ('ACTIVE', 'PAID')
  AND "Collectives"."settings"->'moderation'->'rejectedCategories' IS NOT NULL
  AND "FromCollectives"."data"->'categories' IS NOT NULL`;

const getContributorRejectedCategories = (fromCollective, collective) => {
  const rejectedCategories = get(collective, 'settings.moderation.rejectedCategories', []);
  const contributorCategories = get(fromCollective, 'data.categories', []);

  if (rejectedCategories.length === 0 || contributorCategories.length === 0) {
    return [];
  }

  // Example:
  // MODERATION_CATEGORIES_ALIASES = ['CASINO_GAMBLING': ['casino', 'gambling'], 'VPN_PROXY': ['vpn', 'proxy']]
  // - when contributorCategories = ['CASINO_GAMBLING'], returns ['CASINO_GAMBLING']
  // - when contributorCategories = ['vpn'] or ['proxy'], returns ['VPN_PROXY']
  const contributorRejectedCategories = Object.keys(MODERATION_CATEGORIES_ALIASES).filter(key => {
    return (
      contributorCategories.includes(key) ||
      intersection(MODERATION_CATEGORIES_ALIASES[key], contributorCategories).length !== 0
    );
  });

  return intersection(rejectedCategories, contributorRejectedCategories);
};

async function run({ dryRun, limit, force } = {}) {
  let rows = await sequelize.query(query, { type: sequelize.QueryTypes.SELECT });

  if (rows.length > 0 && limit) {
    rows = rows.slice(0, limit);
  }

  for (const row of rows) {
    const order = await models.Order.findByPk(row['id']);
    const collective = await models.Collective.findByPk(order.CollectiveId);
    const fromCollective = await models.Collective.findByPk(order.FromCollectiveId);

    logger.info(`Checking order #${order.id} from #${fromCollective.slug} to #${collective.slug}`);

    const rejectedCategories = getContributorRejectedCategories(fromCollective, collective);

    if (rejectedCategories.length === 0) {
      logger.info(`  - No rejected categories`);
      continue;
    }

    logger.info(`  - Found rejected categories: ${rejectedCategories.join(', ')}`);

    // Retrieve latest transaction
    const transaction = await models.Transaction.findOne({
      where: {
        OrderId: order.id,
        type: 'CREDIT',
        createdAt: { [Op.gte]: sequelize.literal("NOW() - INTERVAL '30 days'") },
      },
      order: [['createdAt', 'DESC']],
    });

    if (transaction) {
      logger.info(`  - Found transaction #${transaction.id}`);
      // Refund transaction if not already refunded
      if (!transaction.RefundTransactionId) {
        logger.info(`  - Refunding transaction`);
        const paymentMethod = transaction.PaymentMethodId
          ? await models.PaymentMethod.findByPk(transaction.PaymentMethodId)
          : null;
        const paymentMethodProvider = paymentMethod ? libPayments.findPaymentMethodProvider(paymentMethod) : null;
        if (!paymentMethodProvider || !paymentMethodProvider.refundTransaction) {
          if (force) {
            logger.info(`  - refundTransaction not available. Creating refundTransaction in the database only.`);
          } else {
            logger.info(`  - refundTransaction not available. Use 'force' to refundTransaction in the database only.`);
          }
        }
        if (!dryRun) {
          try {
            if (paymentMethodProvider.refundTransaction) {
              await libPayments.refundTransaction(transaction);
            } else if (force) {
              await libPayments.createRefundTransaction(transaction, 0, null);
            }
          } catch (e) {
            if (e.message.includes('has already been refunded')) {
              logger.info(`  - Transaction has already been refunded on Payment Provider`);
              if (paymentMethodProvider && paymentMethodProvider.refundTransactionOnlyInDatabase) {
                await paymentMethodProvider.refundTransactionOnlyInDatabase(transaction);
              }
            }
          }
        }
      } else {
        logger.info(`  - Transaction already refunded`);
      }
    } else {
      logger.info(`  - No transaction found`);
    }

    // Mark the Order as rejected
    logger.info(`  - Marking order #${order.id} as rejected `);
    if (!dryRun) {
      await order.update({ status: orderStatus.REJECTED });
    }

    // Deactivate subscription

    if (order.SubscriptionId) {
      const subscription = await models.Subscription.findByPk(order.SubscriptionId);
      if (subscription) {
        logger.info(`  - Deactivating subscription #${order.SubscriptionId}`);
        if (!dryRun) {
          await subscription.deactivate();
        }
      } else {
        logger.info(`  - Subscription not found`);
      }
    } else {
      logger.info(`  - No subscription to deactivate`);
    }

    // Remove memberships
    logger.info(`  - Deleting BACKER membership`);
    if (!dryRun) {
      await models.Member.destroy({
        where: {
          MemberCollectiveId: fromCollective.id,
          CollectiveId: collective.id,
          role: 'BACKER',
        },
      });
    }

    logger.info(`  - Purging cache for ${collective.slug}`);
    logger.info(`  - Purging cache for ${fromCollective.slug}`);
    if (!dryRun) {
      purgeCacheForCollective(collective.slug);
      purgeCacheForCollective(fromCollective.slug);
    }

    const activity = {
      type: 'contribution.rejected',
      data: {
        collective: { name: collective.name },
        rejectionReason: `${collective.name} banned some specific categories of contributors and there was a match with your profile.`,
      },
    };

    logger.info(`  - Notifying admins of ${fromCollective.slug}`);
    if (!dryRun) {
      await notifyAdminsOfCollective(fromCollective.id, activity);
    }
  }
}

/* eslint-disable camelcase */
function parseCommandLineArguments() {
  const parser = new ArgumentParser({
    add_help: true,
    description: 'Reject Contributions based on Categories',
  });
  parser.add_argument('--dryrun', {
    help: "Don't perform any change, just log.",
    default: false,
    action: 'store_const',
    const: true,
  });
  parser.add_argument('-l', '--limit', {
    help: 'Total matching orders to process',
  });
  parser.add_argument('--force', {
    help: "Force refunds even if payment provider doesn't support it.",
    default: false,
    action: 'store_const',
    const: true,
  });
  const args = parser.parse_args();
  return {
    dryRun: args.dryrun,
    limit: args.limit,
    force: args.force,
  };
}

/* eslint-enable camelcase */

if (require.main === module) {
  run(parseCommandLineArguments())
    .then(() => {
      process.exit(0);
    })
    .catch(e => {
      console.error(e);
      process.exit(1);
    });
}
<commit_msg>reject-contributions: don't mark as REJECTED non-refunded one time contributions<commit_after>#!/usr/bin/env node
import '../../server/env';

import { ArgumentParser } from 'argparse';
import { get, intersection } from 'lodash';

import { MODERATION_CATEGORIES_ALIASES } from '../../server/constants/moderation-categories';
import orderStatus from '../../server/constants/order_status';
import { purgeCacheForCollective } from '../../server/lib/cache';
import logger from '../../server/lib/logger';
import { notifyAdminsOfCollective } from '../../server/lib/notifications';
import * as libPayments from '../../server/lib/payments';
import models, { Op, sequelize } from '../../server/models';

// Fetch all orders potentially affected: contributor flagged AND recipient setup rejection

const query = `SELECT "Orders"."id"
  FROM "Orders", "Collectives", "Collectives" as "FromCollectives"
  WHERE "Orders"."CollectiveId" = "Collectives"."id" AND "FromCollectives"."id" = "Orders"."FromCollectiveId"
  AND "Orders"."status" IN ('ACTIVE', 'PAID')
  AND "Collectives"."settings"->'moderation'->'rejectedCategories' IS NOT NULL
  AND "FromCollectives"."data"->'categories' IS NOT NULL`;

const getContributorRejectedCategories = (fromCollective, collective) => {
  const rejectedCategories = get(collective, 'settings.moderation.rejectedCategories', []);
  const contributorCategories = get(fromCollective, 'data.categories', []);

  if (rejectedCategories.length === 0 || contributorCategories.length === 0) {
    return [];
  }

  // Example:
  // MODERATION_CATEGORIES_ALIASES = ['CASINO_GAMBLING': ['casino', 'gambling'], 'VPN_PROXY': ['vpn', 'proxy']]
  // - when contributorCategories = ['CASINO_GAMBLING'], returns ['CASINO_GAMBLING']
  // - when contributorCategories = ['vpn'] or ['proxy'], returns ['VPN_PROXY']
  const contributorRejectedCategories = Object.keys(MODERATION_CATEGORIES_ALIASES).filter(key => {
    return (
      contributorCategories.includes(key) ||
      intersection(MODERATION_CATEGORIES_ALIASES[key], contributorCategories).length !== 0
    );
  });

  return intersection(rejectedCategories, contributorRejectedCategories);
};

async function run({ dryRun, limit, force } = {}) {
  let rows = await sequelize.query(query, { type: sequelize.QueryTypes.SELECT });

  if (rows.length > 0 && limit) {
    rows = rows.slice(0, limit);
  }

  for (const row of rows) {
    const order = await models.Order.findByPk(row['id']);
    const collective = await models.Collective.findByPk(order.CollectiveId);
    const fromCollective = await models.Collective.findByPk(order.FromCollectiveId);

    logger.info(`Checking order #${order.id} from #${fromCollective.slug} to #${collective.slug}`);

    const rejectedCategories = getContributorRejectedCategories(fromCollective, collective);

    if (rejectedCategories.length === 0) {
      logger.info(`  - No rejected categories`);
      continue;
    }

    logger.info(`  - Found rejected categories: ${rejectedCategories.join(', ')}`);

    let shouldMarkAsRejected = true;

    // Retrieve latest transaction
    const transaction = await models.Transaction.findOne({
      where: {
        OrderId: order.id,
        type: 'CREDIT',
        createdAt: { [Op.gte]: sequelize.literal("NOW() - INTERVAL '30 days'") },
      },
      order: [['createdAt', 'DESC']],
    });

    if (transaction) {
      logger.info(`  - Found transaction #${transaction.id}`);
      // Refund transaction if not already refunded
      if (!transaction.RefundTransactionId) {
        logger.info(`  - Refunding transaction`);
        const paymentMethod = transaction.PaymentMethodId
          ? await models.PaymentMethod.findByPk(transaction.PaymentMethodId)
          : null;
        const paymentMethodProvider = paymentMethod ? libPayments.findPaymentMethodProvider(paymentMethod) : null;
        if (!paymentMethodProvider || !paymentMethodProvider.refundTransaction) {
          if (force) {
            logger.info(`  - refundTransaction not available. Creating refundTransaction in the database only.`);
          } else {
            logger.info(`  - refundTransaction not available. Use 'force' to refundTransaction in the database only.`);
          }
        }
        if (!dryRun) {
          try {
            if (paymentMethodProvider.refundTransaction) {
              await libPayments.refundTransaction(transaction);
            } else if (force) {
              await libPayments.createRefundTransaction(transaction, 0, null);
            }
          } catch (e) {
            if (e.message.includes('has already been refunded')) {
              logger.info(`  - Transaction has already been refunded on Payment Provider`);
              if (paymentMethodProvider && paymentMethodProvider.refundTransactionOnlyInDatabase) {
                await paymentMethodProvider.refundTransactionOnlyInDatabase(transaction);
              }
            }
          }
        }
      } else {
        logger.info(`  - Transaction already refunded`);
      }
    } else {
      logger.info(`  - No transaction found`);
      if (order.status === 'PAID') {
        shouldMarkAsRejected = false;
      }
    }

    // Mark the Order as rejected (only if we found a transaction to refund)
    if (shouldMarkAsRejected) {
      logger.info(`  - Marking order #${order.id} as rejected `);
      if (!dryRun) {
        await order.update({ status: orderStatus.REJECTED });
      }
    }

    // Deactivate subscription
    if (order.SubscriptionId) {
      const subscription = await models.Subscription.findByPk(order.SubscriptionId);
      if (subscription) {
        logger.info(`  - Deactivating subscription #${order.SubscriptionId}`);
        if (!dryRun) {
          await subscription.deactivate();
        }
      } else {
        logger.info(`  - Subscription not found`);
      }
    } else {
      logger.info(`  - No subscription to deactivate`);
    }

    // Remove memberships
    logger.info(`  - Deleting BACKER membership`);
    if (!dryRun) {
      await models.Member.destroy({
        where: {
          MemberCollectiveId: fromCollective.id,
          CollectiveId: collective.id,
          role: 'BACKER',
        },
      });
    }

    logger.info(`  - Purging cache for ${collective.slug}`);
    logger.info(`  - Purging cache for ${fromCollective.slug}`);
    if (!dryRun) {
      purgeCacheForCollective(collective.slug);
      purgeCacheForCollective(fromCollective.slug);
    }

    const activity = {
      type: 'contribution.rejected',
      data: {
        collective: { name: collective.name },
        rejectionReason: `${collective.name} banned some specific categories of contributors and there was a match with your profile.`,
      },
    };

    logger.info(`  - Notifying admins of ${fromCollective.slug}`);
    if (!dryRun) {
      await notifyAdminsOfCollective(fromCollective.id, activity);
    }
  }
}

/* eslint-disable camelcase */
function parseCommandLineArguments() {
  const parser = new ArgumentParser({
    add_help: true,
    description: 'Reject Contributions based on Categories',
  });
  parser.add_argument('--dryrun', {
    help: "Don't perform any change, just log.",
    default: false,
    action: 'store_const',
    const: true,
  });
  parser.add_argument('-l', '--limit', {
    help: 'Total matching orders to process',
  });
  parser.add_argument('--force', {
    help: "Force refunds even if payment provider doesn't support it.",
    default: false,
    action: 'store_const',
    const: true,
  });
  const args = parser.parse_args();
  return {
    dryRun: args.dryrun,
    limit: args.limit,
    force: args.force,
  };
}

/* eslint-enable camelcase */

if (require.main === module) {
  run(parseCommandLineArguments())
    .then(() => {
      process.exit(0);
    })
    .catch(e => {
      console.error(e);
      process.exit(1);
    });
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

/*
 * CouchJob
 * executes CouchDB-hosted code with Node.js
 *
 * by Felix Neumann <condor@the-contented.de>
 */

if (process.argv.length < 5) {
	console.log("Usage: couchjob [couchdb-name] [design-doc-name] [startup-module-name]");
	process.exit(1);
}

/// config //////

var couchdb_srv = 'http://localhost:5984/';
var couchdb_db = process.argv[2];
var couchdb_ddoc = process.argv[3];
var init_module_name = process.argv[4];

var couchdb_uri_prefix = couchdb_srv + couchdb_db + "/_design/" + couchdb_ddoc + "/";

/// setup //////

var Needy = require('needy'),
	couchdb = require('couchdb-api'),
	Fiber = require('fibers'),
	Future = require('fibers/future');

/// CouchDbModuleResolver //////

function CouchDbModuleResolver(db, ddoc_name) {
	this._designdoc = db.ddoc(ddoc_name);
	this._fallback_resolver = new Needy.Resolver({ /* no options */ });
}

Needy.Resolver.extend(CouchDbModuleResolver, {
	resolve : function(identity, dirname) {
		var self = this;
		if(dirname) {
			console.logger("WARN: dirname is not supported! [value = %s]", dirname);
		}

		var future = new Future;
		self._designdoc.attachment(identity).get(future.resolver());

		try {
			var result = future.wait();
			return new Needy.Module(couchdb_uri_prefix + identity, result);
		} catch(couchDbError) {
			return self._fallback_resolver.resolve(identity, dirname);
		}
	}
});

/// start up //////

Fiber(function(){
	console.info("Connecting to CouchDB @ " + couchdb_srv + couchdb_db);
	var db = require('couchdb-api').srv(couchdb_srv).db(couchdb_db);

	console.info("Starting Module: " + init_module_name);
	var needy = new Needy({resolver: new CouchDbModuleResolver(db, couchdb_ddoc)});

	needy.init(init_module_name)
}).run();

<commit_msg>Use to node-optimist for command line parsing<commit_after>#!/usr/bin/env node

/*
 * CouchJob
 * executes CouchDB-hosted code with Node.js
 *
 * by Felix Neumann <condor@the-contented.de>
 */

var argv = require('optimist')
	.usage('Your local Node.js running remote CouchApp code\nUsage: $0')
	.option('connection', {
		alias : 'c',
		default : 'http://localhost:5984',
		describe : '[protocol://]hostname[:port]'})
	.option('database', {
		alias: 'b',
		demand : true,
		describe : 'Database name'})
	.option('ddoc', {
		alias : 'd',
		demand : true,
		describe : 'Design document name'})
	.option('module', {
		alias : 'm',
		demand : true,
		describe : 'Module that contains your code'})
	.argv;

/// config //////

// TODO: Use a REST library where we can just demand a URL?
var couchdb_uri_prefix = require('util').format(
	'couchdb:%s/%s/_design/%s/',
	argv.connection, argv.database, argv.ddoc
);

/// setup //////

var Needy = require('needy'),
	Fiber = require('fibers'),
	Future = require('fibers/future');

/// CouchDbModuleResolver //////

function CouchDbModuleResolver(db, ddoc_name) {
	this._ddoc = db.ddoc(ddoc_name);
	this._resolver_fallback = new Needy.Resolver({ /* no options */ });
}

Needy.Resolver.extend(CouchDbModuleResolver, {
	resolve : function(module_name, dir_name) {
		var self = this;
		if(dir_name) {
			console.warn("WARN: dirname is not supported! [value = %s]", dir_name);
		}

		var future = new Future;
		self._ddoc.attachment(module_name).get(future.resolver());

		try {
			var result = future.wait();
			return new Needy.Module(couchdb_uri_prefix + module_name, result);
		} catch(couchDbError) {
			console.warn('HINT: cannot resolve module [%s] on your CouchDB, falling back to local', module_name);
			return self._resolver_fallback.resolve(module_name, dir_name);
		}
	}
});

/// start up //////

Fiber(function(){
	console.info('Connecting to CouchDB @ %s/%s', argv.connection, argv.database);
	var db = require('couchdb-api').srv(argv.connection).db(argv.database);

	var needy = new Needy({
		resolver: new CouchDbModuleResolver(db, argv.ddoc)
	});

	console.info("Starting Module: " + argv.module);
	needy.init(argv.module);
}).run();

<|endoftext|>


<commit_before>var Model = require('../../../src/core/model');
var RangeFilter = require('../../../src/windshaft/filters/range');
var HistogramDataviewModel = require('../../../src/dataviews/histogram-dataview-model');

describe('dataviews/histogram-dataview-model', function () {
  beforeEach(function () {
    this.map = jasmine.createSpyObj('map', ['getViewBounds', 'bind', 'reload']);
    this.map.getViewBounds.and.returnValue([[1, 2], [3, 4]]);
    this.filter = new RangeFilter();
    this.layer = new Model();
    this.layer.getDataProvider = function () {};
    spyOn(HistogramDataviewModel.prototype, 'listenTo').and.callThrough();
    spyOn(HistogramDataviewModel.prototype, 'fetch').and.callThrough();
    this.model = new HistogramDataviewModel({}, {
      map: this.map,
      layer: this.layer,
      filter: this.filter
    });
  });

  it('should not listen any url change from the beginning', function () {
    this.model.set('url', 'http://cartodb.com');
    expect(this.model.fetch).not.toHaveBeenCalled();
  });

  it('should set unfiltered model url when model has changed it', function () {
    spyOn(this.model._unfilteredData, 'setUrl');
    this.model.set('url', 'hey!');
    expect(this.model._unfilteredData.setUrl).toHaveBeenCalled();
  });

  it('should set the api_key attribute on the internal models', function () {
    this.model = new HistogramDataviewModel({
      apiKey: 'API_KEY'
    }, {
      map: this.map,
      layer: jasmine.createSpyObj('layer', ['get', 'getDataProvider']),
      filter: this.filter
    });

    expect(this.model._unfilteredData.get('apiKey')).toEqual('API_KEY');
  });

  fdescribe('should get the correct histogram shape', function () {
    beforeEach(function () {
      this.model.set('bins', 6);
    });
    it ('when it is flat', function () {
      this.model.set('data', [
        {bin: 0, freq: 25},
        {bin: 1, freq: 26},
        {bin: 2, freq: 25},
        {bin: 3, freq: 26},
        {bin: 4, freq: 26},
        {bin: 5, freq: 25}
      ]);
      expect(this.model.getHistogramShape()).toEqual('F');
    });
    it ('when it is A', function () {
      this.model.set('data', [
        {bin: 0, freq: 0},
        {bin: 1, freq: 5},
        {bin: 2, freq: 25},
        {bin: 3, freq: 18},
        {bin: 4, freq: 8},
        {bin: 5, freq: 2}
      ]);
      expect(this.model.getHistogramShape()).toEqual('A');
    });
    it ('when it is J', function () {
      this.model.set('data', [
        {bin: 0, freq: 0},
        {bin: 1, freq: 2},
        {bin: 2, freq: 5},
        {bin: 3, freq: 8},
        {bin: 4, freq: 18},
        {bin: 5, freq: 25}
      ]);
      expect(this.model.getHistogramShape()).toEqual('J');
    });
    it ('when it is L', function () {
      this.model.set('data', [
        {bin: 0, freq: 25},
        {bin: 1, freq: 18},
        {bin: 4, freq: 8},
        {bin: 2, freq: 5},
        {bin: 5, freq: 2},
        {bin: 3, freq: 0}
      ]);
      expect(this.model.getHistogramShape()).toEqual('L');
    });
    // it ('when it is clustered', function () {
    //   this.model.set('data', [
    //     {bin: 0, freq: 20},
    //     {bin: 1, freq: 18},
    //     {bin: 2, freq: 5},
    //     {bin: 3, freq: 0},
    //     {bin: 4, freq: 32},
    //     {bin: 5, freq: 16}
    //   ]);
    //   expect(this.model.getHistogramShape()).toEqual('C');
    // });
  });

  describe('on unfiltered data model fetch', function () {
    beforeEach(function () {
      var histogramData = {
        'bin_width': 10,
        'bins_count': 3,
        'bins_start': 1,
        'nulls': 0
      };

      spyOn(this.model._unfilteredData, 'sync').and.callFake(function (method, model, options) {
        options.success(histogramData);
      });
    });

    it('should calculate start, end and bins', function () {
      expect(this.model.get('start')).toBeUndefined();
      expect(this.model.get('end')).toBeUndefined();
      this.model._unfilteredData.fetch();
      expect(this.model.get('start')).toEqual(1);
      expect(this.model.get('end')).toEqual(31);
      expect(this.model.get('bins')).toEqual(3);
    });

    it('should run _onChangeBins', function () {
      spyOn(this.model, '_onChangeBinds');
      this.model._unfilteredData.fetch();
      expect(this.model._onChangeBinds).toHaveBeenCalled();
    });
  });

  describe('when column changes', function () {
    it('should reload map and force fetch', function () {
      this.map.reload.calls.reset();
      this.model.set('column', 'random_col');
      expect(this.map.reload).toHaveBeenCalledWith({ forceFetch: true, sourceLayerId: undefined });
    });
  });

  describe('when bins change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');
      this.model.set('bins', 123);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  describe('when start change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');

      this.model.set('start', 0);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  describe('when end change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');
      this.model.set('end', 0);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  it('should parse the bins', function () {
    var data = {
      bin_width: 14490.25,
      bins: [
        { bin: 0, freq: 2, max: 70151, min: 55611 },
        { bin: 1, freq: 2, max: 79017, min: 78448 },
        { bin: 3, freq: 1, max: 113572, min: 113572 }
      ],
      bins_count: 4,
      bins_start: 55611,
      nulls: 0,
      type: 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(4);
    expect(JSON.stringify(parsedData)).toBe('[{"bin":0,"start":55611,"end":70101.25,"freq":2,"max":70151,"min":55611},{"bin":1,"start":70101.25,"end":84591.5,"freq":2,"max":79017,"min":78448},{"bin":2,"start":84591.5,"end":99081.75,"freq":0},{"bin":3,"start":99081.75,"end":113572,"freq":1,"max":113572,"min":113572}]');
  });

  it('parser do not fails when there are no bins', function () {
    var data = {
      bin_width: 0,
      bins: [],
      bins_count: 0,
      bins_start: 0,
      nulls: 0,
      type: 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(0);
  });

  it('should parse the bins and fix end bucket issues', function () {
    var data = {
      'bin_width': 1041.66645833333,
      'bins_count': 48,
      'bins_start': 0.01,
      'nulls': 0,
      'avg': 55.5007561961441,
      'bins': [{
        'bin': 47,
        'min': 50000,
        'max': 50000,
        'avg': 50000,
        'freq': 6
        // NOTE - The end of this bucket is 48 * 1041.66645833333 = 49999.98999999984
        // but it must be corrected to 50.000.
      }],
      'type': 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(48);
    expect(parsedData[47].end).not.toBeLessThan(parsedData[47].max);
  });

  describe('when layer changes meta', function () {
    beforeEach(function () {
      expect(this.model.filter.get('column_type')).not.toEqual('date');
      this.model.layer.set({
        meta: {
          column_type: 'date'
        }
      });
    });

    it('should change the filter column_type', function () {
      expect(this.model.filter.get('column_type')).toEqual('date');
    });
  });

  describe('.url', function () {
    it('should include bbox and initial bins', function () {
      this.model.set('url', 'http://example.com');
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&bins=10');
    });
    it('should include start, end and bins when own_filter is enabled', function () {
      this.model.set({
        'url': 'http://example.com',
        'start': 0,
        'end': 10,
        'bins': 25
      });
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&start=0&end=10&bins=25');
      this.model.enableFilter();
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=1');
    });
  });

  describe('.enableFilter', function () {
    it('should set the own_filter attribute', function () {
      expect(this.model.get('own_filter')).toBeUndefined();

      this.model.enableFilter();

      expect(this.model.get('own_filter')).toEqual(1);
    });
  });

  describe('.disabeFilter', function () {
    it('should unset the own_filter attribute', function () {
      this.model.enableFilter();
      this.model.disableFilter();

      expect(this.model.get('own_filter')).toBeUndefined();
    });
  });
});
<commit_msg>skips test instead of commenting our<commit_after>var Model = require('../../../src/core/model');
var RangeFilter = require('../../../src/windshaft/filters/range');
var HistogramDataviewModel = require('../../../src/dataviews/histogram-dataview-model');

describe('dataviews/histogram-dataview-model', function () {
  beforeEach(function () {
    this.map = jasmine.createSpyObj('map', ['getViewBounds', 'bind', 'reload']);
    this.map.getViewBounds.and.returnValue([[1, 2], [3, 4]]);
    this.filter = new RangeFilter();
    this.layer = new Model();
    this.layer.getDataProvider = function () {};
    spyOn(HistogramDataviewModel.prototype, 'listenTo').and.callThrough();
    spyOn(HistogramDataviewModel.prototype, 'fetch').and.callThrough();
    this.model = new HistogramDataviewModel({}, {
      map: this.map,
      layer: this.layer,
      filter: this.filter
    });
  });

  it('should not listen any url change from the beginning', function () {
    this.model.set('url', 'http://cartodb.com');
    expect(this.model.fetch).not.toHaveBeenCalled();
  });

  it('should set unfiltered model url when model has changed it', function () {
    spyOn(this.model._unfilteredData, 'setUrl');
    this.model.set('url', 'hey!');
    expect(this.model._unfilteredData.setUrl).toHaveBeenCalled();
  });

  it('should set the api_key attribute on the internal models', function () {
    this.model = new HistogramDataviewModel({
      apiKey: 'API_KEY'
    }, {
      map: this.map,
      layer: jasmine.createSpyObj('layer', ['get', 'getDataProvider']),
      filter: this.filter
    });

    expect(this.model._unfilteredData.get('apiKey')).toEqual('API_KEY');
  });

  fdescribe('should get the correct histogram shape', function () {
    beforeEach(function () {
      this.model.set('bins', 6);
    });
    it ('when it is flat', function () {
      this.model.set('data', [
        {bin: 0, freq: 25},
        {bin: 1, freq: 26},
        {bin: 2, freq: 25},
        {bin: 3, freq: 26},
        {bin: 4, freq: 26},
        {bin: 5, freq: 25}
      ]);
      expect(this.model.getHistogramShape()).toEqual('F');
    });
    it ('when it is A', function () {
      this.model.set('data', [
        {bin: 0, freq: 0},
        {bin: 1, freq: 5},
        {bin: 2, freq: 25},
        {bin: 3, freq: 18},
        {bin: 4, freq: 8},
        {bin: 5, freq: 2}
      ]);
      expect(this.model.getHistogramShape()).toEqual('A');
    });
    it ('when it is J', function () {
      this.model.set('data', [
        {bin: 0, freq: 0},
        {bin: 1, freq: 2},
        {bin: 2, freq: 5},
        {bin: 3, freq: 8},
        {bin: 4, freq: 18},
        {bin: 5, freq: 25}
      ]);
      expect(this.model.getHistogramShape()).toEqual('J');
    });
    it ('when it is L', function () {
      this.model.set('data', [
        {bin: 0, freq: 25},
        {bin: 1, freq: 18},
        {bin: 4, freq: 8},
        {bin: 2, freq: 5},
        {bin: 5, freq: 2},
        {bin: 3, freq: 0}
      ]);
      expect(this.model.getHistogramShape()).toEqual('L');
    });
   xit ('when it is clustered', function () {
     this.model.set('data', [
       {bin: 0, freq: 20},
       {bin: 1, freq: 18},
       {bin: 2, freq: 5},
       {bin: 3, freq: 0},
       {bin: 4, freq: 32},
       {bin: 5, freq: 16}
     ]);
     expect(this.model.getHistogramShape()).toEqual('C');
   });
  });

  describe('on unfiltered data model fetch', function () {
    beforeEach(function () {
      var histogramData = {
        'bin_width': 10,
        'bins_count': 3,
        'bins_start': 1,
        'nulls': 0
      };

      spyOn(this.model._unfilteredData, 'sync').and.callFake(function (method, model, options) {
        options.success(histogramData);
      });
    });

    it('should calculate start, end and bins', function () {
      expect(this.model.get('start')).toBeUndefined();
      expect(this.model.get('end')).toBeUndefined();
      this.model._unfilteredData.fetch();
      expect(this.model.get('start')).toEqual(1);
      expect(this.model.get('end')).toEqual(31);
      expect(this.model.get('bins')).toEqual(3);
    });

    it('should run _onChangeBins', function () {
      spyOn(this.model, '_onChangeBinds');
      this.model._unfilteredData.fetch();
      expect(this.model._onChangeBinds).toHaveBeenCalled();
    });
  });

  describe('when column changes', function () {
    it('should reload map and force fetch', function () {
      this.map.reload.calls.reset();
      this.model.set('column', 'random_col');
      expect(this.map.reload).toHaveBeenCalledWith({ forceFetch: true, sourceLayerId: undefined });
    });
  });

  describe('when bins change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');
      this.model.set('bins', 123);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  describe('when start change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');

      this.model.set('start', 0);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  describe('when end change', function () {
    beforeEach(function () {
      this.map.reload.calls.reset();
      spyOn(this.model.filter, 'unsetRange');
      this.model.set('end', 0);
    });

    it('should refresh data on bins change', function () {
      expect(this.map.reload).not.toHaveBeenCalled();
      expect(this.model.fetch).toHaveBeenCalled();
    });

    it('should disable filter', function () {
      expect(this.model.get('own_filter')).toBeUndefined();
    });

    it('should unset range filter', function () {
      expect(this.model.filter.unsetRange).toHaveBeenCalled();
    });
  });

  it('should parse the bins', function () {
    var data = {
      bin_width: 14490.25,
      bins: [
        { bin: 0, freq: 2, max: 70151, min: 55611 },
        { bin: 1, freq: 2, max: 79017, min: 78448 },
        { bin: 3, freq: 1, max: 113572, min: 113572 }
      ],
      bins_count: 4,
      bins_start: 55611,
      nulls: 0,
      type: 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(4);
    expect(JSON.stringify(parsedData)).toBe('[{"bin":0,"start":55611,"end":70101.25,"freq":2,"max":70151,"min":55611},{"bin":1,"start":70101.25,"end":84591.5,"freq":2,"max":79017,"min":78448},{"bin":2,"start":84591.5,"end":99081.75,"freq":0},{"bin":3,"start":99081.75,"end":113572,"freq":1,"max":113572,"min":113572}]');
  });

  it('parser do not fails when there are no bins', function () {
    var data = {
      bin_width: 0,
      bins: [],
      bins_count: 0,
      bins_start: 0,
      nulls: 0,
      type: 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(0);
  });

  it('should parse the bins and fix end bucket issues', function () {
    var data = {
      'bin_width': 1041.66645833333,
      'bins_count': 48,
      'bins_start': 0.01,
      'nulls': 0,
      'avg': 55.5007561961441,
      'bins': [{
        'bin': 47,
        'min': 50000,
        'max': 50000,
        'avg': 50000,
        'freq': 6
        // NOTE - The end of this bucket is 48 * 1041.66645833333 = 49999.98999999984
        // but it must be corrected to 50.000.
      }],
      'type': 'histogram'
    };

    this.model.parse(data);

    var parsedData = this.model.getData();

    expect(data.nulls).toBe(0);
    expect(parsedData.length).toBe(48);
    expect(parsedData[47].end).not.toBeLessThan(parsedData[47].max);
  });

  describe('when layer changes meta', function () {
    beforeEach(function () {
      expect(this.model.filter.get('column_type')).not.toEqual('date');
      this.model.layer.set({
        meta: {
          column_type: 'date'
        }
      });
    });

    it('should change the filter column_type', function () {
      expect(this.model.filter.get('column_type')).toEqual('date');
    });
  });

  describe('.url', function () {
    it('should include bbox and initial bins', function () {
      this.model.set('url', 'http://example.com');
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&bins=10');
    });
    it('should include start, end and bins when own_filter is enabled', function () {
      this.model.set({
        'url': 'http://example.com',
        'start': 0,
        'end': 10,
        'bins': 25
      });
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&start=0&end=10&bins=25');
      this.model.enableFilter();
      expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=1');
    });
  });

  describe('.enableFilter', function () {
    it('should set the own_filter attribute', function () {
      expect(this.model.get('own_filter')).toBeUndefined();

      this.model.enableFilter();

      expect(this.model.get('own_filter')).toEqual(1);
    });
  });

  describe('.disabeFilter', function () {
    it('should unset the own_filter attribute', function () {
      this.model.enableFilter();
      this.model.disableFilter();

      expect(this.model.get('own_filter')).toBeUndefined();
    });
  });
});
<|endoftext|>


<commit_before>const { MongoClient } = require('mongodb');
const Eris = require('eris');
const fs   = require('fs');

const utils = require(`${__dirname}/../utils`);
const dbFunctions = require(`${__dirname}/../dbFunctions`);
const events = require(`${__dirname}/events`);

class RemindMeBot {
  constructor (config) {
    this.utils = utils;
    this.log = utils.log;

    this.config = config;
    utils.validateConfig(config)
      .catch(err => {
        this.log(`Invalid configuration, aborting:\n${err}`, 'error');
        process.exit(1);
      });

    this.commands = {};
    this.loadCommands();

    this.dbClient = null;
    this.dbConn = null;
    this.db = null;
    this.initDB();

    this.client = new Eris(this.config.keys.token, this.clientOptions);
    this.client
      .on('connect', events.onConnect.bind(this))
      .on('ready', events.onReady.bind(this))
      .on('messageCreate', events.onMessageCreate.bind(this))
      .on('guildCreate', events.onGuildCreate.bind(this))
      .on('guildDelete', events.onGuildDelete.bind(this))
      .once('ready', this.start.bind(this));

    this.client.connect();
  }

  async initDB () {
    this.dbClient = await MongoClient.connect(this.config.dbURL || 'mongodb://localhost:27017')
      .catch(e => {
        if (e.message.includes('ECONNREFUSED')) {
          this.log('Failed to connect to MongoDB! Exiting...', 'error');
          process.exit();
        }
      });
    this.dbConn = this.dbClient.db('remindmebot');

    this.db = {};
    for (const dbFunction in dbFunctions) {
      this.db[dbFunction] = dbFunctions[dbFunction].bind(this);
    }
  }

  async loadCommands () {
    fs.readdir(`${__dirname}/commands`, (err, files) => {
      if (err) {
        return this.log(err.stack, 'error');
      }

      let failed = 0;

      files.forEach(file => {
        try {
          const command = require(`${__dirname}/commands/${file}`);
          if (command instanceof Object) {
            this.commands[command.name] = Object.assign({
              aliases: [],
              ownerOnly: false,
              usage: '{command}'
            }, command);
          }
        } catch (err) {
          failed++;
          this.log(`Failed to load command ${file}: \n${err.stack}`, 'error');
        }
      });

      this.log(`Successfully loaded ${files.length - failed}/${files.length} commands.`);
    });
  }

  async start () {

  }

  async sendMessage (target, content, isUser = false) {
    if (content.embed && !content.embed.color) {
      content.embed.color = this.config.embedColor;
    }

    try {
      if (isUser) {
        const DMChannel = await this.client.getDMChannel(target);
        return await DMChannel.createMessage(content); // return await is okay here
      } else {                                        //  because we're in a try-catch
        return await this.client.createMessage(target, content);
      }
    } catch (err) {
      if (
        !err.message.includes('Missing Permissions') && // TODO: re-test these and replace these strings with HTTP codes
        !err.message.includes('Cannot send messages to this user') &&
        !err.message.includes('Missing Access')
      ) {
        this.log(err.stack, 'error');
      } else {
        return false;
      }
    }
  }

  get clientOptions () {
    return {
      disableEveryone: true,
      maxShards: 'auto',
      messageLimit: 10,
      disableEvents: {
        CHANNEL_PINS_UPDATE: true,
        USER_SETTINGS_UPDATE: true,
        USER_NOTE_UPDATE: true,
        RELATIONSHIP_ADD: true,
        RELATIONSHIP_REMOVE: true,
        GUILD_BAN_ADD: true,
        GUILD_BAN_REMOVE: true,
        TYPING_START: true,
        MESSAGE_UPDATE: true,
        MESSAGE_DELETE: true,
        MESSAGE_DELETE_BULK: true,
        VOICE_STATE_UPDATE: true
      }
    };
  }
}

module.exports = RemindMeBot;<commit_msg>check for command descriptions<commit_after>const { MongoClient } = require('mongodb');
const Eris = require('eris');
const fs   = require('fs');

const utils = require(`${__dirname}/../utils`);
const dbFunctions = require(`${__dirname}/../dbFunctions`);
const events = require(`${__dirname}/events`);

class RemindMeBot {
  constructor (config) {
    this.utils = utils;
    this.log = utils.log;

    this.config = config;
    utils.validateConfig(config)
      .catch(err => {
        this.log(`Invalid configuration, aborting:\n${err}`, 'error');
        process.exit(1);
      });

    this.commands = {};
    this.loadCommands();

    this.dbClient = null;
    this.dbConn   = null;
    this.db       = null;
    this.initDB();

    this.client = new Eris(this.config.keys.token, this.clientOptions);
    this.client
      .on('connect', events.onConnect.bind(this))
      .on('ready', events.onReady.bind(this))
      .on('messageCreate', events.onMessageCreate.bind(this))
      .on('guildCreate', events.onGuildCreate.bind(this))
      .on('guildDelete', events.onGuildDelete.bind(this))
      .once('ready', this.start.bind(this));

    this.client.connect();
  }

  async initDB () {
    this.dbClient = await MongoClient.connect(this.config.dbURL || 'mongodb://localhost:27017')
      .catch(e => {
        if (e.message.includes('ECONNREFUSED')) {
          this.log('Failed to connect to MongoDB! Exiting...', 'error');
          process.exit();
        }
      });
    this.dbConn = this.dbClient.db('remindmebot');

    this.db = {};
    for (const dbFunction in dbFunctions) {
      this.db[dbFunction] = dbFunctions[dbFunction].bind(this);
    }
  }

  async loadCommands () {
    fs.readdir(`${__dirname}/commands`, (err, files) => {
      if (err) {
        return this.log(err.stack, 'error');
      }

      let failed = 0;

      files.forEach(file => {
        try {
          const command = require(`${__dirname}/commands/${file}`);
          if (command instanceof Object && command.description) {
            this.commands[command.name] = Object.assign({
              aliases: [],
              ownerOnly: false,
              usage: '{command}'
            }, command);
          }
        } catch (err) {
          failed++;
          this.log(`Failed to load command ${file}: \n${err.stack}`, 'error');
        }
      });

      this.log(`Successfully loaded ${files.length - failed}/${files.length} commands.`);
    });
  }

  async start () {

  }

  async sendMessage (target, content, isUser = false) {
    if (content.embed && !content.embed.color) {
      content.embed.color = this.config.embedColor;
    }

    try {
      if (isUser) {
        const DMChannel = await this.client.getDMChannel(target);
        return await DMChannel.createMessage(content); // return await is okay here
      } else {                                        //  because we're in a try-catch
        return await this.client.createMessage(target, content);
      }
    } catch (err) {
      if (
        !err.message.includes('Missing Permissions') && // TODO: re-test these and replace these strings with HTTP codes
        !err.message.includes('Cannot send messages to this user') &&
        !err.message.includes('Missing Access')
      ) {
        this.log(err.stack, 'error');
      } else {
        return false;
      }
    }
  }

  get clientOptions () {
    return {
      disableEveryone: true,
      maxShards: 'auto',
      messageLimit: 10,
      disableEvents: {
        CHANNEL_PINS_UPDATE: true,
        USER_SETTINGS_UPDATE: true,
        USER_NOTE_UPDATE: true,
        RELATIONSHIP_ADD: true,
        RELATIONSHIP_REMOVE: true,
        GUILD_BAN_ADD: true,
        GUILD_BAN_REMOVE: true,
        TYPING_START: true,
        MESSAGE_UPDATE: true,
        MESSAGE_DELETE: true,
        MESSAGE_DELETE_BULK: true,
        VOICE_STATE_UPDATE: true
      }
    };
  }
}

module.exports = RemindMeBot;<|endoftext|>


<commit_before>(function() {
  var dialog = null;

  // set up handler for any form w/in the dialog
  $('#pick_accessories_dialog form.pick_accessories_form').live('ajax:complete', function(e, jqXHR, status) {
    var response = jqXHR.responseText;

    dialog.html(response);

    // close dialog if ajax call succeeded
    if (status == "success") {
      dialog.dialog('close');
    }

    return false;
  });

  // when the dialog closes, reload this page since the link shouldn't be there anymore
  $('#pick_accessories_dialog').live('dialogclose', function() {
    window.location = window.location.href;
  });

  $('.end_reservation_link').live('click', function() {
    var clicked = $(this)
      , url = clicked.attr('href');

    // set closure's dialog so other handler(s) can find it
    dialog = $('#pick_accessories_dialog');

    // build dialog if necessary
    if (dialog.length == 0) {
      dialog = $('
');
      clicked.after(dialog);
    }

    // load pick_accessories_form into dialog
    dialog.load(
      url,
      function(response, status, xhr) {
        // show dialog
        dialog.dialog({
          title: 'Accessories Entry'
        });
      }
    );

    return false;
  });

})();
<commit_msg>make Accessories Entry dialog modal and disable closeOnEscape<commit_after>(function() {
  var dialog = null;

  // set up handler for any form w/in the dialog
  $('#pick_accessories_dialog form.pick_accessories_form').live('ajax:complete', function(e, jqXHR, status) {
    var response = jqXHR.responseText;

    dialog.html(response);

    // close dialog if ajax call succeeded
    if (status == "success") {
      dialog.dialog('close');
    }

    return false;
  });

  // when the dialog closes, reload this page since the link shouldn't be there anymore
  $('#pick_accessories_dialog').live('dialogclose', function() {
    window.location = window.location.href;
  });

  $('.end_reservation_link').live('click', function() {
    var clicked = $(this)
      , url = clicked.attr('href');

    // set closure's dialog so other handler(s) can find it
    dialog = $('#pick_accessories_dialog');

    // build dialog if necessary
    if (dialog.length == 0) {
      dialog = $('
');
      clicked.after(dialog);
    }

    // load pick_accessories_form into dialog
    dialog.load(
      url,
      function(response, status, xhr) {
        // show dialog
        dialog.dialog({
          closeOnEscape:  false,
          modal:          true,
          title:          'Accessories Entry'
        });
      }
    );

    return false;
  });

})();
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import {
  View,
  StyleSheet,
  Platform,
  Image,
  Text as NativeText,
} from 'react-native';
import fonts from '../config/fonts';
import colors from '../config/colors';
import Text from '../text/Text';
import Divider from '../divider/Divider';
import normalize from '../helpers/normalizeText';

const Card = props => {
  const {
    children,
    flexDirection,
    containerStyle,
    wrapperStyle,
    imageWrapperStyle,
    title,
    titleStyle,
    featuredTitle,
    featuredTitleStyle,
    featuredSubtitle,
    featuredSubtitleStyle,
    dividerStyle,
    image,
    imageStyle,
    fontFamily,
    ...attributes
  } = props;

  return (
    <View
      style={[
        styles.container,
        image && { padding: 0 },
        containerStyle && containerStyle,
      ]}
      {...attributes}
    >
      <View
        style={[
          styles.wrapper,
          wrapperStyle && wrapperStyle,
          flexDirection && { flexDirection },
        ]}
      >
        {title &&
          <View>
            <Text
              style={[
                styles.cardTitle,
                image && styles.imageCardTitle,
                titleStyle && titleStyle,
                fontFamily && { fontFamily },
              ]}
            >
              {title}
            </Text>
            {!image &&
              <Divider
                style={[styles.divider, dividerStyle && dividerStyle]}
              />}
          </View>}
        {image &&
          <View style={imageWrapperStyle && imageWrapperStyle}>
            <Image
              resizeMode="cover"
              style={[{ width: null, height: 150 }, imageStyle && imageStyle]}
              source={image}
            >
              <View style={styles.overlayContainer}>
                {featuredTitle &&
                  <Text
                    style={[
                      styles.featuredTitle,
                      featuredTitleStyle && featuredTitleStyle,
                    ]}
                  >
                    {featuredTitle}
                  </Text>}
                {featuredSubtitle &&
                  <Text
                    style={[
                      styles.featuredSubtitle,
                      featuredSubtitleStyle && featuredSubtitleStyle,
                    ]}
                  >
                    {featuredSubtitle}
                  </Text>}
              </View>
            </Image>
            <View style={[{ padding: 10 }, wrapperStyle && wrapperStyle]}>
              {children}
            </View>
          </View>}
        {!image && children}
      </View>
    </View>
  );
};

Card.propTypes = {
  children: PropTypes.any,
  flexDirection: PropTypes.string,
  containerStyle: View.propTypes.style,
  wrapperStyle: View.propTypes.style,
  title: PropTypes.string,
  titleStyle: NativeText.propTypes.style,
  featuredTitle: PropTypes.string,
  featuredTitleStyle: Text.propTypes.style,
  featuredSubtitle: PropTypes.string,
  featuredSubtitleStyle: Text.propTypes.style,
  dividerStyle: View.propTypes.style,
  image: Image.propTypes.source,
  imageStyle: View.propTypes.style,
  imageWrapperStyle: View.propTypes.style,
  fontFamily: PropTypes.string,
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'white',
    borderColor: colors.grey5,
    borderWidth: 1,
    padding: 15,
    margin: 15,
    marginBottom: 0,
    ...Platform.select({
      ios: {
        shadowColor: 'rgba(0,0,0, .2)',
        shadowOffset: { height: 0, width: 0 },
        shadowOpacity: 1,
        shadowRadius: 1,
      },
      android: {
        elevation: 1,
      },
    }),
  },
  featuredTitle: {
    fontSize: normalize(18),
    marginBottom: 8,
    color: 'white',
    ...Platform.select({
      ios: {
        fontWeight: '800',
      },
      android: {
        ...fonts.android.black,
      },
    }),
  },
  featuredSubtitle: {
    fontSize: normalize(13),
    marginBottom: 8,
    color: 'white',
    ...Platform.select({
      ios: {
        fontWeight: '400',
      },
      android: {
        ...fonts.android.black,
      },
    }),
  },
  wrapper: {
    backgroundColor: 'transparent',
  },
  divider: {
    marginBottom: 15,
  },
  cardTitle: {
    fontSize: normalize(14),
    ...Platform.select({
      ios: {
        fontWeight: 'bold',
      },
      android: {
        ...fonts.android.black,
      },
    }),
    textAlign: 'center',
    marginBottom: 15,
    color: colors.grey1,
  },
  imageCardTitle: {
    marginTop: 15,
  },
  overlayContainer: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.2)',
    alignSelf: 'stretch',
    justifyContent: 'center',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

export default Card;
<commit_msg>Allow overlay container styles to be overridden<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import {
  View,
  StyleSheet,
  Platform,
  Image,
  Text as NativeText,
} from 'react-native';
import fonts from '../config/fonts';
import colors from '../config/colors';
import Text from '../text/Text';
import Divider from '../divider/Divider';
import normalize from '../helpers/normalizeText';

const Card = props => {
  const {
    children,
    flexDirection,
    containerStyle,
    wrapperStyle,
    overlayStyle,
    imageWrapperStyle,
    title,
    titleStyle,
    featuredTitle,
    featuredTitleStyle,
    featuredSubtitle,
    featuredSubtitleStyle,
    dividerStyle,
    image,
    imageStyle,
    fontFamily,
    ...attributes
  } = props;

  return (
    <View
      style={[
        styles.container,
        image && { padding: 0 },
        containerStyle && containerStyle,
      ]}
      {...attributes}
    >
      <View
        style={[
          styles.wrapper,
          wrapperStyle && wrapperStyle,
          flexDirection && { flexDirection },
        ]}
      >
        {title &&
          <View>
            <Text
              style={[
                styles.cardTitle,
                image && styles.imageCardTitle,
                titleStyle && titleStyle,
                fontFamily && { fontFamily },
              ]}
            >
              {title}
            </Text>
            {!image &&
              <Divider
                style={[styles.divider, dividerStyle && dividerStyle]}
              />}
          </View>}
        {image &&
          <View style={imageWrapperStyle && imageWrapperStyle}>
            <Image
              resizeMode="cover"
              style={[{ width: null, height: 150 }, imageStyle && imageStyle]}
              source={image}
            >
              <View style={[styles.overlayContainer, overlayStyle && overlayStyle]}>
                {featuredTitle &&
                  <Text
                    style={[
                      styles.featuredTitle,
                      featuredTitleStyle && featuredTitleStyle,
                    ]}
                  >
                    {featuredTitle}
                  </Text>}
                {featuredSubtitle &&
                  <Text
                    style={[
                      styles.featuredSubtitle,
                      featuredSubtitleStyle && featuredSubtitleStyle,
                    ]}
                  >
                    {featuredSubtitle}
                  </Text>}
              </View>
            </Image>
            <View style={[{ padding: 10 }, wrapperStyle && wrapperStyle]}>
              {children}
            </View>
          </View>}
        {!image && children}
      </View>
    </View>
  );
};

Card.propTypes = {
  children: PropTypes.any,
  flexDirection: PropTypes.string,
  containerStyle: View.propTypes.style,
  wrapperStyle: View.propTypes.style,
  overlayStyle: View.propTypes.style,
  title: PropTypes.string,
  titleStyle: NativeText.propTypes.style,
  featuredTitle: PropTypes.string,
  featuredTitleStyle: Text.propTypes.style,
  featuredSubtitle: PropTypes.string,
  featuredSubtitleStyle: Text.propTypes.style,
  dividerStyle: View.propTypes.style,
  image: Image.propTypes.source,
  imageStyle: View.propTypes.style,
  imageWrapperStyle: View.propTypes.style,
  fontFamily: PropTypes.string,
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'white',
    borderColor: colors.grey5,
    borderWidth: 1,
    padding: 15,
    margin: 15,
    marginBottom: 0,
    ...Platform.select({
      ios: {
        shadowColor: 'rgba(0,0,0, .2)',
        shadowOffset: { height: 0, width: 0 },
        shadowOpacity: 1,
        shadowRadius: 1,
      },
      android: {
        elevation: 1,
      },
    }),
  },
  featuredTitle: {
    fontSize: normalize(18),
    marginBottom: 8,
    color: 'white',
    ...Platform.select({
      ios: {
        fontWeight: '800',
      },
      android: {
        ...fonts.android.black,
      },
    }),
  },
  featuredSubtitle: {
    fontSize: normalize(13),
    marginBottom: 8,
    color: 'white',
    ...Platform.select({
      ios: {
        fontWeight: '400',
      },
      android: {
        ...fonts.android.black,
      },
    }),
  },
  wrapper: {
    backgroundColor: 'transparent',
  },
  divider: {
    marginBottom: 15,
  },
  cardTitle: {
    fontSize: normalize(14),
    ...Platform.select({
      ios: {
        fontWeight: 'bold',
      },
      android: {
        ...fonts.android.black,
      },
    }),
    textAlign: 'center',
    marginBottom: 15,
    color: colors.grey1,
  },
  imageCardTitle: {
    marginTop: 15,
  },
  overlayContainer: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.2)',
    alignSelf: 'stretch',
    justifyContent: 'center',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

export default Card;
<|endoftext|>


<commit_before>/* eslint-env node */

module.exports = {
  parser: "@typescript-eslint/parser",

  parserOptions: {
    // Modern ECMAScript features
    ecmaVersion: 2020,

    // Use of imports
    sourceType: "module",
  },

  env: {
    browser: false,
    es6: true,
    node: true,
  },

  extends: [
    // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    "plugin:@typescript-eslint/recommended",

    // Uses eslint-config-prettier to disable ESLint rules from
    // @typescript-eslint/eslint-plugin that would conflict with prettier
    "prettier/@typescript-eslint",
    ...[
      "../rules/best.practices",
      "../rules/comments",
      "../rules/errors",
      "../rules/es6",
      "../rules/import",
      "../rules/import-ts",
      "../rules/jsdoc",
      "../rules/node",
      "../rules/promise",
      "../rules/style",
      "../rules/unicorn",
      "../rules/variables",
      "../rules/prettier",
    ].map(require.resolve),
  ],

  plugins: ["json", "no-inferred-method-name"],

  rules: {
    "@typescript-eslint/no-var-requires": "off",

    "no-unused-vars": "off",

    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        args: "after-used",
      },
    ],
  },
}
<commit_msg>fix(node-ts): Disable TS no-unused-vars, gives false positives. Ex. middle parameter.<commit_after>/* eslint-env node */

module.exports = {
  parser: "@typescript-eslint/parser",

  parserOptions: {
    // Modern ECMAScript features
    ecmaVersion: 2020,

    // Use of imports
    sourceType: "module",
  },

  env: {
    browser: false,
    es6: true,
    node: true,
  },

  extends: [
    // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    "plugin:@typescript-eslint/recommended",

    // Uses eslint-config-prettier to disable ESLint rules from
    // @typescript-eslint/eslint-plugin that would conflict with prettier
    "prettier/@typescript-eslint",
    ...[
      "../rules/best.practices",
      "../rules/comments",
      "../rules/errors",
      "../rules/es6",
      "../rules/import",
      "../rules/import-ts",
      "../rules/jsdoc",
      "../rules/node",
      "../rules/promise",
      "../rules/style",
      "../rules/unicorn",
      "../rules/variables",
      "../rules/prettier",
    ].map(require.resolve),
  ],

  plugins: ["json", "no-inferred-method-name"],

  rules: {
    "@typescript-eslint/no-var-requires": "off",

    // disable, it gives false positives. let eslint/no-unused-vars
    "@typescript-eslint/no-unused-vars": "off",

    "no-unused-vars": [
      "error",
      {
        args: "after-used",
        varsIgnorePattern: "debug",
      },
    ],
  },
}
<|endoftext|>


<commit_before>// Copyright 2016 Joseph W. May. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Returns an HTML-formatted string to display the imported gradebook
 * file selector.
 *
 * @returns An HTML-formatted string.
 */
function displayGradebooks() {
  var masteryTracker = new MasteryTracker();
  var gradebookSheets = masteryTracker.getUserCreatedSheets();
  var gradebookItems = constructSheetSelectItems_(gradebookSheets);

  // Construct the html form if there are imported gradebook sheets.
  var form = [];
  if (gradebookSheets.length > 0) {
    form.push('<form class="block" id="gradebooks">' +
        '
' +
          '
Scores will be imported from the following gradebooks. ' +
              'Uncheck a gradebook to skip it. ' +
              'Selected gradebooks must have ' +
              'identical assignments.
');

    // Construct the checkboxes for each assignment.
    form.push('
' +
        showCheckboxes('gradebooks', gradebookItems, true) +
      '

');

    // Construct the form control buttons.
    form.push('
' +
        '<input type="button" value="Select gradebooks" class="action" ' +
            'onclick="selectGradebooks_onclick();">' +
        showCloseButton() +
      '
</form>');
  } else {
    form.push('
' +
        '
' +
          'No gradebooks have been loaded into the tracker. ' +
          'You must complete step 1 first.' +
        '
' +
      '
' +
      showCloseButton());
  }

  return form.join('');
}

/**
 * Returns an HTML-formatted string to display the assignment selector.
 *
 * @returns An HTML-formatted string.
 */
function displayAssignments(sheetIds) {
  // Use the first selected assignment to get assignment names. This assumes
  // that all assignment names will be found in the other sheets...perhaps a
  // faulty assumption.
  // TODO: *** Add error catching for this ***
  var sheetId = sheetIds[0];
  var form = [];

  if (sheetIds.length > 0) {
    // Save the current and remaining sheetIds for later use.
    var storage = new PropertyStore();
    storage.setProperty('sheetIds', sheetIds, true);

    var gradebook = new Gradebook(sheetId);
    var assignments = gradebook.getAssignmentNames();
    var assignmentItems = constructColumnSelectItems_(assignments, 2);

    // Construct the html form.
    form.push('<form class="block" id="assignments">' +
      '
' +
        '
Select learning goal mastery data to import by checking the ' +
        'appropriate gradebook assignments below.
');

    // Construct the checkboxes for each assignment.
    form.push('
' +
        showCheckboxes('assignments', assignmentItems) +
      '

');

    // Construct the form control buttons.
    form.push('
' +
        '<input type="button" value="Select assignments" class="action" ' +
            'onclick="selectAssignments_onclick();">' +
        showCloseButton() +
      '
</form>');
  } else {
    form.push('
' +
        'No gradebooks were selected. You may close this window.' +
      '
' +
      showCloseButton());
  }

  return form.join('');
}

/**
 * Returns an HTML-formatted string to display the mastery data importer form.
 *
 * @returns An HTML-formatted string.
 */
function displayMasteryData(assignments) {
  // Use the first selected assignment to get assignment names. This assumes
  // that all assignment names will be found in the other sheets...perhaps a
  // faulty assumption.
  // TODO: *** Add error catching for this ***
  var storage = new PropertyStore();
  var sheetIds = storage.getProperty('sheetIds', true);
  var sheetId = sheetIds[0];

  // Get the current gradebook.
  var gradebook = new Gradebook(sheetId);
  var masteryData = new MasteryData();

  // Construct the html form.
  var form = [];
  form.push('<form class="block" id="masteryData">' +
      '
' +
        '
Set the learning goal number and title for each imported ' +
           'assignment:
');

  // Construct assignments.
  form.push('
');
  for (var i = 0; i < assignments.length; i++) {
    form.push(
      '
' +
        '
' +
          'Assignment: ' +
          gradebook.getAssignmentName(assignments[i]) +
        '
' +
        '
' +
          'Select learning goal:' +
            masteryData.getLearningGoalSelector() +
        '
' +
        '
' +
          'Learning goal title:' +
          '<input type="text" name="lgName">' +
        '
' +
        '
' +
          '' +
            '<input type="checkbox" name="retake"> Re-take ' +
            'This will add re-take data to a ' +
                'learning goal rather than replace existing data.' +
          '' +
        '
' +
        '<input type="hidden" name="assignment" value="' +
          assignments[i] +
        '">' +
      '
'
    );
  }
  form.push('
');

  // Construct the form control buttons.
  form.push('
' +
      '<input type="button" value="Import mastery data" class="action" ' +
          'onclick="importMasteryData_onclick();">' +
      showCloseButton() +
      '' +
    '
</form>');

  return form.join('');
}

/**
 * Imports the given assignments into the respective learning goal columns of
 * the mastery data sheet. Takes into account whether an assignment is a retake.
 *
 * @param {array} assignments Array of assignment numbers to import.
 * @param {array} lgNums Array of learning goal numbers as targets for
 *     the import.
 * @param {array} lgNames Array of learning goal names for the import.
 * @param {array} retakes Array of booleans indicating which learning goals
 *     are to be imported as retakes.
 * @returns An HTML-formatted string dislaying the completion message and
 *     close button.
 */
function importMasteryData(assignments, lgNums, lgNames, retakes) {
  var storage = new PropertyStore();
  var sheetIds = storage.getProperty('sheetIds', true);
  var masteryData = new MasteryData();

  // Loop over the selected spreadsheets containing the imported gradebook data.
  for (var i = 0; i < sheetIds.length; i++) {
    var sheetId = sheetIds[i];
    var gradebook = new Gradebook(sheetId);

    // Get the class period.
    var period = gradebook.getClassPeriod();

    // Get the CC flag.
    var isCC = gradebook.isCC();

    // Verify that the imported data arrays are equal in length.
    if ((assignments.length !== lgNums.length) &&
            (lgNums.length !== lgNames.length) &&
            (lgNames.length !== retakes.length)) {
      throw new Error('There was a problem importing the data. ' +
              'Please try again later.');
    }

    // Loop over the selected learning goal numbers.
    for (var j = 0; j < lgNums.length; j++) {
      var assignment = assignments[j];
      var lgNum = lgNums[j];
      var lgName = lgNames[j];
      var retake = retakes[j];

      // Set the learning goal name if given.
      if (lgName !== undefined && lgName !== null && lgName !== '') {
        masteryData.setLearningGoalName(lgNum, lgName);
      }

      // Set the mastery scores after clearing the old data.
      // Append data for CC classes.
      var scores = gradebook.getAssignmentScores(assignment);
      if (isCC === false) {
        masteryData.clearLearningGoalScores(period, lgNum, retake);
        masteryData.setLearningGoalScores(period, lgNum, scores, retake);
      } else {
        masteryData.appendLearningGoalScores(period, lgNum, scores, retake);
      }
    }
  }

  // Construct display message and close button for return.
  var complete = '
' +
        'Import complete. You may close this window and ' +
        'complete step 3.' +
      '
' +
      showCloseButton();

  return complete;
}<commit_msg>remove unneeded error handling<commit_after>// Copyright 2016 Joseph W. May. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Returns an HTML-formatted string to display the imported gradebook
 * file selector.
 *
 * @returns An HTML-formatted string.
 */
function displayGradebooks() {
  var masteryTracker = new MasteryTracker();
  var gradebookSheets = masteryTracker.getUserCreatedSheets();
  var gradebookItems = constructSheetSelectItems_(gradebookSheets);

  // Construct the html form if there are imported gradebook sheets.
  var form = [];
  if (gradebookSheets.length > 0) {
    form.push('<form class="block" id="gradebooks">' +
        '
' +
          '
Scores will be imported from the following gradebooks. ' +
              'Uncheck a gradebook to skip it. ' +
              'Selected gradebooks must have ' +
              'identical assignments.
');

    // Construct the checkboxes for each assignment.
    form.push('
' +
        showCheckboxes('gradebooks', gradebookItems, true) +
      '

');

    // Construct the form control buttons.
    form.push('
' +
        '<input type="button" value="Select gradebooks" class="action" ' +
            'onclick="selectGradebooks_onclick();">' +
        showCloseButton() +
      '
</form>');
  } else {
    form.push('
' +
        '
' +
          'No gradebooks have been loaded into the tracker. ' +
          'You must complete step 1 first.' +
        '
' +
      '
' +
      showCloseButton());
  }

  return form.join('');
}

/**
 * Returns an HTML-formatted string to display the assignment selector.
 *
 * @returns An HTML-formatted string.
 */
function displayAssignments(sheetIds) {
  // Use the first selected assignment to get assignment names. This assumes
  // that all assignment names will be found in the other sheets...perhaps a
  // faulty assumption.
  // TODO: *** Add error catching for this ***
  var sheetId = sheetIds[0];
  var form = [];

  if (sheetIds.length > 0) {
    // Save the current and remaining sheetIds for later use.
    var storage = new PropertyStore();
    storage.setProperty('sheetIds', sheetIds, true);

    var gradebook = new Gradebook(sheetId);
    var assignments = gradebook.getAssignmentNames();
    var assignmentItems = constructColumnSelectItems_(assignments, 2);

    // Construct the html form.
    form.push('<form class="block" id="assignments">' +
      '
' +
        '
Select learning goal mastery data to import by checking the ' +
        'appropriate gradebook assignments below.
');

    // Construct the checkboxes for each assignment.
    form.push('
' +
        showCheckboxes('assignments', assignmentItems) +
      '

');

    // Construct the form control buttons.
    form.push('
' +
        '<input type="button" value="Select assignments" class="action" ' +
            'onclick="selectAssignments_onclick();">' +
        showCloseButton() +
      '
</form>');
  } else {
    form.push('
' +
        'No gradebooks were selected. You may close this window.' +
      '
' +
      showCloseButton());
  }

  return form.join('');
}

/**
 * Returns an HTML-formatted string to display the mastery data importer form.
 *
 * @returns An HTML-formatted string.
 */
function displayMasteryData(assignments) {
  // Use the first selected assignment to get assignment names. This assumes
  // that all assignment names will be found in the other sheets...perhaps a
  // faulty assumption.
  // TODO: *** Add error catching for this ***
  var storage = new PropertyStore();
  var sheetIds = storage.getProperty('sheetIds', true);
  var sheetId = sheetIds[0];

  // Get the current gradebook.
  var gradebook = new Gradebook(sheetId);
  var masteryData = new MasteryData();

  // Construct the html form.
  var form = [];
  form.push('<form class="block" id="masteryData">' +
      '
' +
        '
Set the learning goal number and title for each imported ' +
           'assignment:
');

  // Construct assignments.
  form.push('
');
  for (var i = 0; i < assignments.length; i++) {
    form.push(
      '
' +
        '
' +
          'Assignment: ' +
          gradebook.getAssignmentName(assignments[i]) +
        '
' +
        '
' +
          'Select learning goal:' +
            masteryData.getLearningGoalSelector() +
        '
' +
        '
' +
          'Learning goal title:' +
          '<input type="text" name="lgName">' +
        '
' +
        '
' +
          '' +
            '<input type="checkbox" name="retake"> Re-take ' +
            'This will add re-take data to a ' +
                'learning goal rather than replace existing data.' +
          '' +
        '
' +
        '<input type="hidden" name="assignment" value="' +
          assignments[i] +
        '">' +
      '
'
    );
  }
  form.push('
');

  // Construct the form control buttons.
  form.push('
' +
      '<input type="button" value="Import mastery data" class="action" ' +
          'onclick="importMasteryData_onclick();">' +
      showCloseButton() +
      '' +
    '
</form>');

  return form.join('');
}

/**
 * Imports the given assignments into the respective learning goal columns of
 * the mastery data sheet. Takes into account whether an assignment is a retake.
 *
 * @param {array} assignments Array of assignment numbers to import.
 * @param {array} lgNums Array of learning goal numbers as targets for
 *     the import.
 * @param {array} lgNames Array of learning goal names for the import.
 * @param {array} retakes Array of booleans indicating which learning goals
 *     are to be imported as retakes.
 * @returns An HTML-formatted string dislaying the completion message and
 *     close button.
 */
function importMasteryData(assignments, lgNums, lgNames, retakes) {
  var storage = new PropertyStore();
  var sheetIds = storage.getProperty('sheetIds', true);
  var masteryData = new MasteryData();

  // Loop over the selected spreadsheets containing the imported gradebook data.
  for (var i = 0; i < sheetIds.length; i++) {
    var sheetId = sheetIds[i];
    var gradebook = new Gradebook(sheetId);

    // Get the class period.
    var period = gradebook.getClassPeriod();

    // Get the CC flag.
    var isCC = gradebook.isCC();

    // Loop over the selected learning goal numbers.
    for (var j = 0; j < lgNums.length; j++) {
      var assignment = assignments[j];
      var lgNum = lgNums[j];
      var lgName = lgNames[j];
      var retake = retakes[j];

      // Set the learning goal name if given.
      if (lgName !== undefined && lgName !== null && lgName !== '') {
        masteryData.setLearningGoalName(lgNum, lgName);
      }

      // Set the mastery scores after clearing the old data.
      // Append data for CC classes.
      var scores = gradebook.getAssignmentScores(assignment);
      if (isCC === false) {
        masteryData.clearLearningGoalScores(period, lgNum, retake);
        masteryData.setLearningGoalScores(period, lgNum, scores, retake);
      } else {
        masteryData.appendLearningGoalScores(period, lgNum, scores, retake);
      }
    }
  }

  // Construct display message and close button for return.
  var complete = '
' +
        'Import complete. You may close this window and ' +
        'complete step 3.' +
      '
' +
      showCloseButton();

  return complete;
}<|endoftext|>


<commit_before>describe('Testing Properties of app and app.views', function () {

  before(function () {
    // any code you put here will be run before any of your tests run
  });

  after(function() {
    // any code you put here will be run after all of your tests run
  });

  beforeEach(function () {
    // var app = new app.MainView
    // any code you put here will be run before each of your tests run
  });

  afterEach(function () {
    // any code you put here will be run after each of your tests run
  });

  it('typeof app should be an object', function () {
    expect(typeof app).to.equal('object');
  });

  it('app should be an object', function () {
    expect(app).to.be.a('object');
  });

  it('typeof app.AllProjectsView should be a function', function() {
    expect(typeof app.AllProjectsView).to.equal('function');
  });

  it('app.AllProjectsView should be a function', function() {
    expect(app.Views.AllProjectsView).to.be.a('function');
  });

  it('typeof app.EditProfileView should be a function', function() {
    expect(typeof app.Views.EditProfileView).to.equal('function');
  });

  it('app.EditProfileView should be a function', function() {
    expect(app.Views.EditProfileView).to.be.a('function');
  });

  it('typeof app.EditProjectView should be a function', function() {
    expect(typeof app.Views.EditProjectView).to.equal('function');
  });

  it('app.EditProjectView should be a function', function() {
    expect(app.Views.EditProjectView).to.be.a('function');
  });

  it('typeof app.ErrorView should be a function', function() {
    expect(typeof app.Views.ErrorView).to.equal('function');
  });

  it('app.ErrorView should be a function', function() {
    expect(app.Views.ErrorView).to.be.a('function');
  });

  it('typeof app.ForgotPasswordView should be a function', function() {
    expect(typeof app.Views.ForgotPasswordView).to.equal('function');
  });

  it('app.ForgotPasswordView should be a function', function() {
    expect(app.Views.ForgotPasswordView).to.be.a('function');
  });

  it('typeof app.ProjectView should be a function', function() {
    expect(typeof app.Views.ProjectView).to.equal('function');
  });

  it('app.ProjectView should be a function', function() {
    expect(app.Views.ProjectView).to.be.a('function');
  });

  it('OPP_users should exist in the database', function() {
    expect(app.Views.OPP_users).to.be.a('object');
  });
});
<commit_msg>capitalize app to App to make tests pass<commit_after>describe('Testing Properties of app and app.views', function () {

  before(function () {
    // any code you put here will be run before any of your tests run
  });

  after(function() {
    // any code you put here will be run after all of your tests run
  });

  beforeEach(function () {
    // var app = new app.MainView
    // any code you put here will be run before each of your tests run
  });

  afterEach(function () {
    // any code you put here will be run after each of your tests run
  });

  it('typeof app should be an object', function () {
    expect(typeof app).to.equal('object');
  });

  it('app should be an object', function () {
    expect(app).to.be.a('object');
  });

  it('typeof App.AllProjectsView should be a function', function() {
    expect(typeof App.AllProjectsView).to.equal('function');
  });

  it('App.AllProjectsView should be a function', function() {
    expect(App.Views.AllProjectsView).to.be.a('function');
  });

  it('typeof App.EditProfileView should be a function', function() {
    expect(typeof App.Views.EditProfileView).to.equal('function');
  });

  it('App.EditProfileView should be a function', function() {
    expect(App.Views.EditProfileView).to.be.a('function');
  });

  it('typeof App.EditProjectView should be a function', function() {
    expect(typeof App.Views.EditProjectView).to.equal('function');
  });

  it('App.EditProjectView should be a function', function() {
    expect(App.Views.EditProjectView).to.be.a('function');
  });

  it('typeof App.ErrorView should be a function', function() {
    expect(typeof App.Views.ErrorView).to.equal('function');
  });

  it('App.ErrorView should be a function', function() {
    expect(App.Views.ErrorView).to.be.a('function');
  });

  it('typeof App.ForgotPasswordView should be a function', function() {
    expect(typeof App.Views.ForgotPasswordView).to.equal('function');
  });

  it('App.ForgotPasswordView should be a function', function() {
    expect(app.Views.ForgotPasswordView).to.be.a('function');
  });

  it('typeof app.ProjectView should be a function', function() {
    expect(typeof app.Views.ProjectView).to.equal('function');
  });

  it('app.ProjectView should be a function', function() {
    expect(app.Views.ProjectView).to.be.a('function');
  });

  it('OPP_users should exist in the database', function() {
    expect(app.Views.OPP_users).to.be.a('object');
  });
});
<|endoftext|>