<commit_before>import fs from 'fs';
import json2csv from 'json2csv';
import { ArgumentParser } from 'argparse';

import * as payments from '../server/lib/payments';
import emailLib from '../server/lib/email';
import { promiseSeq } from '../server/lib/utils';
import { sequelize } from '../server/models';
import {
  ordersWithPendingCharges,
  processOrderWithSubscription,
  updateNextChargeDate,
  updateChargeRetryCount,
  handleRetryStatus,
} from '../server/lib/subscriptions';

const REPORT_EMAIL = '<EMAIL>';

// These field names are the ones returned by
// processOrderWithSubscription().
const csvFields = [
  'orderId',
  'subscriptionId',
  'amount',
  'from',
  'to',
  'status',
  'error',
  'retriesBefore',
  'retriesAfter',
  'chargeDateBefore',
  'chargeDateAfter',
  'nextPeriodStartBefore',
  'nextPeriodStartAfter'
];

/** Run the script with parameters read from the command line */
async function run(options) {
  const start = new Date;
  const orders = await ordersWithPendingCharges();
  vprint(options, `${orders.length} subscriptions pending charges. dryRun: ${options.dryRun}`);
  const data = [];
  await promiseSeq(orders, async (order) => {
    vprint(options,
           `order: ${order.id}, subscription: ${order.Subscription.id}, ` +
           `attempt: #${order.Subscription.chargeRetryCount}, ` +
           `due: ${order.Subscription.nextChargeDate}`);
    data.push(await processOrderWithSubscription(options, order));
  }, options.batchSize);

  if (data.length > 0) {
    json2csv({ data, fields: csvFields }, (err, csv) => {
      vprint(options, 'Writing the output to a CSV file');
      if (err) console.log(err);
      else fs.writeFileSync('charge_subscriptions.output.csv', csv);
    });
  } else {
    vprint(options, 'Not generating CSV file');
  }
  if (!options.dryRun) {
    vprint(options, 'Sending email report');
    await emailReport(start, orders, data);
  }
}

/** Send an email with details of the subscriptions processed */
async function emailReport(start, orders, data) {
  const icon = (err) => err ? '❌' : '✅';
  let issuesFound = false;
  let result = [`Total Subscriptions pending charges found: ${orders.length}`, ''];

  result = result.concat(data.map((i) => {
    if (i.status === 'failure') issuesFound = true;
    return ` ${i.status !== 'unattempted' ? icon(i.error) : ''} ` + [
      `order: ${i.orderId}`,
      `subscription: ${i.subscriptionId}`,
      `amount: ${i.amount}`,
      `from: ${i.from}`,
      `to: ${i.to}`,
      `status: ${i.status}`,
      `error: ${i.error}`,
    ].join(', ');
  }));

  const now = new Date;
  const end = now - start;
  result.push(`\n\nTotal time taken: ${end}ms`);
  const subject = `${icon(issuesFound)} Daily Subscription Report - ${now.toLocaleDateString()}`;
  return emailLib.sendMessage(REPORT_EMAIL, subject, '', { bcc: ' ', text: result.join('\n') });
}

/** Print `message` to console if `options.verbose` is true */
function vprint(options, message) {
  if (options.verbose) {
    console.log(message);
  }
}

/** Return the options passed by the user to run the script */
function parseCommandLineArguments() {
  const parser = new ArgumentParser({
    addHelp: true,
    description: 'Charge due subscriptions',
  });
  parser.addArgument(['-v', '--verbose'], {
    help: 'Verbose output',
    defaultValue: false,
    action: 'storeConst',
    constant: true,
  });<commit_msg>Charge Subscriptions: Attach CSV to report email (#1138)<commit_after>import fs from 'fs';
import json2csv from 'json2csv';
import { ArgumentParser } from 'argparse';

import * as payments from '../server/lib/payments';
import emailLib from '../server/lib/email';
import { promiseSeq } from '../server/lib/utils';
import { sequelize } from '../server/models';
import {
  ordersWithPendingCharges,
  processOrderWithSubscription,
  updateNextChargeDate,
  updateChargeRetryCount,
  handleRetryStatus,
} from '../server/lib/subscriptions';

const REPORT_EMAIL = '<EMAIL>';

// These field names are the ones returned by
// processOrderWithSubscription().
const csvFields = [
  'orderId',
  'subscriptionId',
  'amount',
  'from',
  'to',
  'status',
  'error',
  'retriesBefore',
  'retriesAfter',
  'chargeDateBefore',
  'chargeDateAfter',
  'nextPeriodStartBefore',
  'nextPeriodStartAfter'
];

/** Run the script with parameters read from the command line */
async function run(options) {
  const start = new Date;
  const orders = await ordersWithPendingCharges();
  vprint(options, `${orders.length} subscriptions pending charges. dryRun: ${options.dryRun}`);
  const data = [];
  await promiseSeq(orders, async (order) => {
    vprint(options,
           `order: ${order.id}, subscription: ${order.Subscription.id}, ` +
           `attempt: #${order.Subscription.chargeRetryCount}, ` +
           `due: ${order.Subscription.nextChargeDate}`);
    data.push(await processOrderWithSubscription(options, order));
  }, options.batchSize);

  if (data.length > 0) {
    await json2csv({ data, fields: csvFields }, async (err, csv) => {
      vprint(options, 'Writing the output to a CSV file');
      if (err) console.log(err);
      else {
        if (options.dryRun) {
          fs.writeFileSync('charge_subscriptions.output.csv', csv);
        } else {
          if (!options.dryRun) {
            vprint(options, 'Sending email report');
            const attachments = [{
              filename: `${(new Date).toLocaleDateString()}.csv`,
              content: csv
            }];
            await emailReport(start, orders, data, attachments);
          }
        }
      }
    });
  } else {
    vprint(options, 'Not generating CSV file');
  }
}

/** Send an email with details of the subscriptions processed */
async function emailReport(start, orders, data, attachments) {
  const icon = (err) => err ? '❌' : '✅';
  let issuesFound = false;
  let result = [`Total Subscriptions pending charges found: ${orders.length}`, ''];

  result = result.concat(data.map((i) => {
    if (i.status === 'failure') issuesFound = true;
    return ` ${i.status !== 'unattempted' ? icon(i.error) : ''} ` + [
      `order: ${i.orderId}`,
      `subscription: ${i.subscriptionId}`,
      `amount: ${i.amount}`,
      `from: ${i.from}`,
      `to: ${i.to}`,
      `status: ${i.status}`,
      `error: ${i.error}`,
    ].join(', ');
  }));

  const now = new Date;
  const end = now - start;
  result.push(`\n\nTotal time taken: ${end}ms`);
  const subject = `${icon(issuesFound)} Daily Subscription Report - ${now.toLocaleDateString()}`;
  return emailLib.sendMessage(REPORT_EMAIL, subject, '', {
    bcc: ' ',
    text: result.join('\n'),
    attachments
  });
}

/** Print `message` to console if `options.verbose` is true */
function vprint(options, message) {
  if (options.verbose) {
    console.log(message);
  }
}

/** Return the options passed by the user to run the script */
function parseCommandLineArguments() {
  const parser = new ArgumentParser({
    addHelp: true,
    description: 'Charge due subscriptions',
  });
  parser.addArgument(['-v', '--verbose'], {
    help: 'Verbose output',
    defaultValue: false,
    action: 'storeConst',
    constant: true,
  });
{'fraction_non_alphanumeric': 0.16023408109237844, 'fraction_numerical': 0.0016720078027030792, 'mean_word_length': 3.477854023705552, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2623668', 'n_tokens_mistral': 2230, 'n_tokens_neox': 2249, 'n_words': 582}