<commit_before> /* eslint-disable no-console */
const fs = require("fs");
const {mkdir} = require("shelljs");
const path = require("path");

// Note: this file is generated by webpack from system-addon/content-src/activity-stream-prerender.jsx
const {prerender} = require("./prerender");

const DEFAULT_LOCALE = "en-US";
const DEFAULT_OPTIONS = {
  addonPath: "../system-addon",
  baseUrl: "resource://activity-stream/"
};

// This locales list is to find any similar locales that we can reuse strings
// instead of falling back to the default, e.g., use bn-BD strings for bn-IN.
// https://hg.mozilla.org/mozilla-central/file/tip/browser/locales/l10n.toml
const CENTRAL_LOCALES = [
  "ach",
  "af",
  "an",
  "ar",
  "as",
  "ast",
  "az",
  "be",
  "bg",
  "bn-BD",
  "bn-IN",
  "br",
  "bs",
  "ca",
  "cak",
  "cs",
  "cy",
  "da",
  "de",
  "dsb",
  "el",
  "en-GB",
  "en-ZA",
  "eo",
  "es-AR",
  "es-CL",
  "es-ES",
  "es-MX",
  "et",
  "eu",
  "fa",
  "ff",
  "fi",
  "fr",
  "fy-NL",
  "ga-IE",
  "gd",
  "gl",
  "gn",
  "gu-IN",
  "he",
  "hi-IN",
  "hr",
  "hsb",
  "hu",
  "hy-AM",
  "ia",
  "id",
  "is",
  "it",
  "ja",
  "ja-JP-mac",
  "ka",
  "kab",
  "kk",
  "km",
  "kn",
  "ko",
  "lij",
  "lo",
  "lt",
  "ltg",
  "lv",
  "mai",
  "mk",
  "ml",
  "mr",
  "ms",
  "my",
  "nb-NO",
  "ne-NP",
  "nl",
  "nn-NO",
  "or",
  "pa-IN",
  "pl",
  "pt-BR",
  "pt-PT",
  "rm",
  "ro",
  "ru",
  "si",
  "sk",
  "sl",
  "son",
  "sq",
  "sr",
  "sv-SE",
  "ta",
  "te",
  "th",
  "tl",
  "tr",
  "uk",
  "ur",
  "uz",
  "vi",
  "xh",
  "zh-CN",
  "zh-TW"
];

// Locales that should be displayed RTL
const RTL_LIST = ["ar", "he", "fa", "ur"];

/**
 * Get the language part of the locale.
 */
function getLanguage(locale) {
  return locale.split("-")[0];
}

/**
 * Get the best strings for a single provided locale using similar locales and
 * DEFAULT_LOCALE as fallbacks.
 */
function getStrings(locale, allStrings) {
  const availableLocales = Object.keys(allStrings);

  const language = getLanguage(locale);
  const similarLocales = availableLocales.filter(other =>
    other !== locale && getLanguage(other) === language);

  // Rank locales from least desired to most desired
  const localeFallbacks = [DEFAULT_LOCALE, ...similarLocales, locale];

  // Get strings from each locale replacing with those from more desired ones
  return Object.assign({}, ...localeFallbacks.map(l => allStrings[l]));
}

/**
 * Get the text direction of the locale.
 */
function getTextDirection(locale) {
  return RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr";
}

/**
 * templateHTML - Generates HTML for activity stream, given some options and
 * prerendered HTML if necessary.
 *
 * @param  {obj} options
 *         {str} options.locale         The locale to render in lang="" attribute
 *         {str} options.direction      The language direction to render in dir="" attribute
 *         {str} options.baseUrl        The base URL for all local assets
 *         {bool} options.debug         Should we use dev versions of JS libraries?
 * @param  {str} html    The prerendered HTML created with React.renderToString (optional)
 * @return {str}         An HTML document as a string
 */
function templateHTML(options, html) {
  const isPrerendered = !!html;
  const debugString = options.debug ? "-dev" : "";
  const scripts = [
    "chrome://browser/content/contentSearchUI.js",
    `${options.baseUrl}vendor/react${debugString}.js`,
    `${options.baseUrl}vendor/react-dom${debugString}.js`,
    `${options.baseUrl}vendor/prop-types.js`,
    `${options.baseUrl}vendor/react-intl.js`,
    `${options.baseUrl}vendor/redux.js`,
    `${options.baseUrl}vendor/react-redux.js`,
    `${options.baseUrl}prerendered/${options.locale}/activity-stream-strings.js`,
    `${options.baseUrl}data/content/activity-stream.bundle.js`
  ];
  if (isPrerendered) {
    scripts.unshift(`${options.baseUrl}prerendered/static/activity-stream-initial-state.js`);
  }
  return `<!doctype html>

    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
    <title>${options.strings.newtab_page_title}</title>
    <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
    <link rel="stylesheet" href="${options.baseUrl}css/activity-stream.css" />

${isPrerendered ? html : ""}

`;
}

/**
 * templateJs - Generates a js file that passes the initial state of the prerendered
 * DOM to the React version. This is necessary to ensure the checksum matches when
 * React mounts so that it can attach to the prerendered elements instead of blowing
 * them away.
 *
 * Note that this may no longer be necessary in React 16 and we should review whether
 * it is still necessary.
 *
 * @param  {string} name The name of the global to expose
 * @param  {obj}    data The data to expose as a window global
 * @return {str}         The js file as a string
 */
function templateJs(name, state) {
  return `// Note - this is a generated file.
window.${name} = ${JSON.stringify(state, null, 2)};
`;
}

/**
 * writeFiles - Writes to the desired files the result of a template given
 * various prerendered data and options.
 *
 * @param {string} name          Something to identify in the console
 * @param {string} destPath      Path to write the files to
 * @param {Map}    filesMap      Mapping of a string file name to templater
 * @param {Object} prerenderData Contains the html and state
 * @param {Object} options       Various options for the templater
 */
function writeFiles(name, destPath, filesMap, {html, state}, options) {
  for (const [file, templater] of filesMap) {
    fs.writeFileSync(path.join(destPath, file), templater({html, options, state}));
  }
  console.log("\x1b[32m", `✓ ${name}`, "\x1b[0m");
}

const STATIC_FILES = new Map([
  ["activity-stream-debug.html", ({options}) => templateHTML(options)],
  ["activity-stream-initial-state.js", ({state}) => templateJs("gActivityStreamPrerenderedState", state)],
  ["activity-stream-prerendered-debug.html", ({html, options}) => templateHTML(options, html)]
]);

const LOCALIZED_FILES = new Map([
  ["activity-stream-prerendered.html", ({html, options}) => templateHTML(options, html)],
  ["activity-stream-strings.js", ({options: {strings}}) => templateJs("gActivityStreamStrings", strings)],
  ["activity-stream.html", ({options}) => templateHTML(options)]
]);

/**
 * main - Parses command line arguments, generates html and js with templates,
 *        and writes files to their specified locations.
 */
function main() { // eslint-disable-line max-statements
  // This code parses command line arguments passed to this script.
  // Note: process.argv.slice(2) is necessary because the first two items in
  // process.argv are paths
  const args = require("minimist")(process.argv.slice(2), {
    alias: {
      addonPath: "a",
      baseUrl: "b"
    }
  });

  const baseOptions = Object.assign({debug: false}, DEFAULT_OPTIONS, args || {});
  const addonPath = path.resolve(__dirname, baseOptions.addonPath);
  const allStrings = require(`${baseOptions.addonPath}/data/locales.json`);
  const extraLocales = Object.keys(allStrings).filter(locale =>
    locale !== DEFAULT_LOCALE && !CENTRAL_LOCALES.includes(locale));

  const prerenderedPath = path.join(addonPath, "prerendered");
  console.log(`Writing prerendered files to individual directories under ${prerenderedPath}:`);

  // Save default locale's strings to compare against other locales' strings
  let defaultStrings;
  let langStrings;
  const isSubset = (strings, existing) => existing &&
    Object.keys(strings).every(key => strings[key] === existing[key]);

  // Process the default locale first then all the ones from mozilla-central
  const localizedLocales = [];
  const skippedLocales = [];
  for (const locale of [DEFAULT_LOCALE, ...CENTRAL_LOCALES, ...extraLocales]) {
    // Skip the locale if it would have resulted in duplicate packaged files
    const strings = getStrings(locale, allStrings);
    if (isSubset(strings, defaultStrings) || isSubset(strings, langStrings)) {
      skippedLocales.push(locale);
      continue;
    }

    const prerenderData  = prerender(locale, strings);
    const options = Object.assign({}, baseOptions, {
      direction: getTextDirection(locale),
      locale,
      strings
    });

    // Put locale-specific files in their own directory
    const localePath = path.join(prerenderedPath, "locales", locale);
    mkdir("-p", localePath);
    writeFiles(locale, localePath, LOCALIZED_FILES, prerenderData, options);

    // Only write static files once for the default locale
    if (locale === DEFAULT_LOCALE) {
      const staticPath = path.join(prerenderedPath, "static");
      mkdir("-p", staticPath);
      writeFiles(`${locale} (static)`, staticPath, STATIC_FILES, prerenderData,
        Object.assign({}, options, {debug: true}));

      // Save the default strings to compare against other locales' strings
      defaultStrings = strings;
    }

    // Save the language's strings to maybe reuse for the next similar locales
    if (getLanguage(locale) === locale) {
      langStrings = strings;
    }

    localizedLocales.push(locale);
  }

  if (skippedLocales.length) {
    console.log("\x1b[33m", `Skipped the following locales because they use the same strings as ${DEFAULT_LOCALE} or its language locale: ${skippedLocales.join(", ")}`, "\x1b[0m");
  }
  if (extraLocales.length) {
    console.log("\x1b[31m", `✗ These locales were not in CENTRAL_LOCALES, but probably should be: ${extraLocales.join(", ")}`, "\x1b[0m");
  }

  // Provide some help to copy/paste locales if tests are failing
  console.log(`\nIf aboutNewTabService tests are failing for unexpected locales, make sure its list is updated:\nconst ACTIVITY_STREAM_LOCALES = "${localizedLocales.join(" ")}".split(" ");`);
}

main();
<commit_msg>chore(lint): Use includes to avoid eslint error when upgrading to 0.8.1<commit_after> /* eslint-disable no-console */
const fs = require("fs");
const {mkdir} = require("shelljs");
const path = require("path");

// Note: this file is generated by webpack from system-addon/content-src/activity-stream-prerender.jsx
const {prerender} = require("./prerender");

const DEFAULT_LOCALE = "en-US";
const DEFAULT_OPTIONS = {
  addonPath: "../system-addon",
  baseUrl: "resource://activity-stream/"
};

// This locales list is to find any similar locales that we can reuse strings
// instead of falling back to the default, e.g., use bn-BD strings for bn-IN.
// https://hg.mozilla.org/mozilla-central/file/tip/browser/locales/l10n.toml
const CENTRAL_LOCALES = [
  "ach",
  "af",
  "an",
  "ar",
  "as",
  "ast",
  "az",
  "be",
  "bg",
  "bn-BD",
  "bn-IN",
  "br",
  "bs",
  "ca",
  "cak",
  "cs",
  "cy",
  "da",
  "de",
  "dsb",
  "el",
  "en-GB",
  "en-ZA",
  "eo",
  "es-AR",
  "es-CL",
  "es-ES",
  "es-MX",
  "et",
  "eu",
  "fa",
  "ff",
  "fi",
  "fr",
  "fy-NL",
  "ga-IE",
  "gd",
  "gl",
  "gn",
  "gu-IN",
  "he",
  "hi-IN",
  "hr",
  "hsb",
  "hu",
  "hy-AM",
  "ia",
  "id",
  "is",
  "it",
  "ja",
  "ja-JP-mac",
  "ka",
  "kab",
  "kk",
  "km",
  "kn",
  "ko",
  "lij",
  "lo",
  "lt",
  "ltg",
  "lv",
  "mai",
  "mk",
  "ml",
  "mr",
  "ms",
  "my",
  "nb-NO",
  "ne-NP",
  "nl",
  "nn-NO",
  "or",
  "pa-IN",
  "pl",
  "pt-BR",
  "pt-PT",
  "rm",
  "ro",
  "ru",
  "si",
  "sk",
  "sl",
  "son",
  "sq",
  "sr",
  "sv-SE",
  "ta",
  "te",
  "th",
  "tl",
  "tr",
  "uk",
  "ur",
  "uz",
  "vi",
  "xh",
  "zh-CN",
  "zh-TW"
];

// Locales that should be displayed RTL
const RTL_LIST = ["ar", "he", "fa", "ur"];

/**
 * Get the language part of the locale.
 */
function getLanguage(locale) {
  return locale.split("-")[0];
}

/**
 * Get the best strings for a single provided locale using similar locales and
 * DEFAULT_LOCALE as fallbacks.
 */
function getStrings(locale, allStrings) {
  const availableLocales = Object.keys(allStrings);

  const language = getLanguage(locale);
  const similarLocales = availableLocales.filter(other =>
    other !== locale && getLanguage(other) === language);

  // Rank locales from least desired to most desired
  const localeFallbacks = [DEFAULT_LOCALE, ...similarLocales, locale];

  // Get strings from each locale replacing with those from more desired ones
  return Object.assign({}, ...localeFallbacks.map(l => allStrings[l]));
}

/**
 * Get the text direction of the locale.
 */
function getTextDirection(locale) {
  return RTL_LIST.includes(locale.split("-")[0]) ? "rtl" : "ltr";
}

/**
 * templateHTML - Generates HTML for activity stream, given some options and
 * prerendered HTML if necessary.
 *
 * @param  {obj} options
 *         {str} options.locale         The locale to render in lang="" attribute
 *         {str} options.direction      The language direction to render in dir="" attribute
 *         {str} options.baseUrl        The base URL for all local assets
 *         {bool} options.debug         Should we use dev versions of JS libraries?
 * @param  {str} html    The prerendered HTML created with React.renderToString (optional)
 * @return {str}         An HTML document as a string
 */
function templateHTML(options, html) {
  const isPrerendered = !!html;
  const debugString = options.debug ? "-dev" : "";
  const scripts = [
    "chrome://browser/content/contentSearchUI.js",
    `${options.baseUrl}vendor/react${debugString}.js`,
    `${options.baseUrl}vendor/react-dom${debugString}.js`,
    `${options.baseUrl}vendor/prop-types.js`,
    `${options.baseUrl}vendor/react-intl.js`,
    `${options.baseUrl}vendor/redux.js`,
    `${options.baseUrl}vendor/react-redux.js`,
    `${options.baseUrl}prerendered/${options.locale}/activity-stream-strings.js`,
    `${options.baseUrl}data/content/activity-stream.bundle.js`
  ];
  if (isPrerendered) {
    scripts.unshift(`${options.baseUrl}prerendered/static/activity-stream-initial-state.js`);
  }
  return `<!doctype html>

    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
    <title>${options.strings.newtab_page_title}</title>
    <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
    <link rel="stylesheet" href="${options.baseUrl}css/activity-stream.css" />

${isPrerendered ? html : ""}

`;
}

/**
 * templateJs - Generates a js file that passes the initial state of the prerendered
 * DOM to the React version. This is necessary to ensure the checksum matches when
 * React mounts so that it can attach to the prerendered elements instead of blowing
 * them away.
 *
 * Note that this may no longer be necessary in React 16 and we should review whether
 * it is still necessary.
 *
 * @param  {string} name The name of the global to expose
 * @param  {obj}    data The data to expose as a window global
 * @return {str}         The js file as a string
 */
function templateJs(name, state) {
  return `// Note - this is a generated file.
window.${name} = ${JSON.stringify(state, null, 2)};
`;
}

/**
 * writeFiles - Writes to the desired files the result of a template given
 * various prerendered data and options.
 *
 * @param {string} name          Something to identify in the console
 * @param {string} destPath      Path to write the files to
 * @param {Map}    filesMap      Mapping of a string file name to templater
 * @param {Object} prerenderData Contains the html and state
 * @param {Object} options       Various options for the templater
 */
function writeFiles(name, destPath, filesMap, {html, state}, options) {
  for (const [file, templater] of filesMap) {
    fs.writeFileSync(path.join(destPath, file), templater({html, options, state}));
  }
  console.log("\x1b[32m", `✓ ${name}`, "\x1b[0m");
}

const STATIC_FILES = new Map([
  ["activity-stream-debug.html", ({options}) => templateHTML(options)],
  ["activity-stream-initial-state.js", ({state}) => templateJs("gActivityStreamPrerenderedState", state)],
  ["activity-stream-prerendered-debug.html", ({html, options}) => templateHTML(options, html)]
]);

const LOCALIZED_FILES = new Map([
  ["activity-stream-prerendered.html", ({html, options}) => templateHTML(options, html)],
  ["activity-stream-strings.js", ({options: {strings}}) => templateJs("gActivityStreamStrings", strings)],
  ["activity-stream.html", ({options}) => templateHTML(options)]
]);

/**
 * main - Parses command line arguments, generates html and js with templates,
 *        and writes files to their specified locations.
 */
function main() { // eslint-disable-line max-statements
  // This code parses command line arguments passed to this script.
  // Note: process.argv.slice(2) is necessary because the first two items in
  // process.argv are paths
  const args = require("minimist")(process.argv.slice(2), {
    alias: {
      addonPath: "a",
      baseUrl: "b"
    }
  });

  const baseOptions = Object.assign({debug: false}, DEFAULT_OPTIONS, args || {});
  const addonPath = path.resolve(__dirname, baseOptions.addonPath);
  const allStrings = require(`${baseOptions.addonPath}/data/locales.json`);
  const extraLocales = Object.keys(allStrings).filter(locale =>
    locale !== DEFAULT_LOCALE && !CENTRAL_LOCALES.includes(locale));

  const prerenderedPath = path.join(addonPath, "prerendered");
  console.log(`Writing prerendered files to individual directories under ${prerenderedPath}:`);

  // Save default locale's strings to compare against other locales' strings
  let defaultStrings;
  let langStrings;
  const isSubset = (strings, existing) => existing &&
    Object.keys(strings).every(key => strings[key] === existing[key]);

  // Process the default locale first then all the ones from mozilla-central
  const localizedLocales = [];
  const skippedLocales = [];
  for (const locale of [DEFAULT_LOCALE, ...CENTRAL_LOCALES, ...extraLocales]) {
    // Skip the locale if it would have resulted in duplicate packaged files
    const strings = getStrings(locale, allStrings);
    if (isSubset(strings, defaultStrings) || isSubset(strings, langStrings)) {
      skippedLocales.push(locale);
      continue;
    }

    const prerenderData  = prerender(locale, strings);
    const options = Object.assign({}, baseOptions, {
      direction: getTextDirection(locale),
      locale,
      strings
    });

    // Put locale-specific files in their own directory
    const localePath = path.join(prerenderedPath, "locales", locale);
    mkdir("-p", localePath);
    writeFiles(locale, localePath, LOCALIZED_FILES, prerenderData, options);

    // Only write static files once for the default locale
    if (locale === DEFAULT_LOCALE) {
      const staticPath = path.join(prerenderedPath, "static");
      mkdir("-p", staticPath);
      writeFiles(`${locale} (static)`, staticPath, STATIC_FILES, prerenderData,
        Object.assign({}, options, {debug: true}));

      // Save the default strings to compare against other locales' strings
      defaultStrings = strings;
    }

    // Save the language's strings to maybe reuse for the next similar locales
    if (getLanguage(locale) === locale) {
      langStrings = strings;
    }

    localizedLocales.push(locale);
  }

  if (skippedLocales.length) {
    console.log("\x1b[33m", `Skipped the following locales because they use the same strings as ${DEFAULT_LOCALE} or its language locale: ${skippedLocales.join(", ")}`, "\x1b[0m");
  }
  if (extraLocales.length) {
    console.log("\x1b[31m", `✗ These locales were not in CENTRAL_LOCALES, but probably should be: ${extraLocales.join(", ")}`, "\x1b[0m");
  }

  // Provide some help to copy/paste locales if tests are failing
  console.log(`\nIf aboutNewTabService tests are failing for unexpected locales, make sure its list is updated:\nconst ACTIVITY_STREAM_LOCALES = "${localizedLocales.join(" ")}".split(" ");`);
}

main();
<|endoftext|>


<commit_before>/**
 * In some cases it is desirable to override the exported implementation and constructor for a symbol.
 * This is useful for a few reasons:
 * - A symbol could have multiple constructors
 * - It is possible to disambiguate multiple different signatures from a single polymorphic constructor
 * - The return type of the overridden constructor can differ (e.g. `Foo<T|null>` vs `Foo<T>`)
 *
 * This looks like the following:
 *
 * ```
 * /**
 * * @overriddenImplementation FooCtor
 * *\/
 * export interface Foo {
 *   bar();
 * }
 *
 * type FooInterface = Foo;
 *
 * export class FooImpl { ... }
 *
 * export interface FooCtor {
 *   new(): Foo;
 * }
 *
 * export const Foo: FooCtor =
    (class Foo implements FooInterface { ... }
 * ```
 *
 * This processor will extend the docs for symbol `Foo` by copying all documented constructor overrides from `FooCtor`.
 *
 * In order to use this processor, annotate the exported interface with `@overriddenImplementation`. Place the desired
 * documentation on the interface and constructor signatures.
 */
 module.exports = function mergeOverriddenImplementation(getDocFromAlias, log) {
  return {
    $runAfter: ['tags-extracted', 'ids-computed'],
    $runBefore: ['filterPrivateDocs'],
    propertiesToKeep: [
      'name', 'id', 'aliases', 'fileInfo', 'startingLine', 'endingLine',
      'path', 'originalModule', 'outputPath', 'privateExport', 'moduleDoc'
    ],
    $process(docs) {
      docs.forEach(doc => {
        if (doc.overriddenImplementation) {
          // Convert the specified name into a doc.
          const ctorDocArray = getDocFromAlias(doc.overriddenImplementation);
            if (ctorDocArray.length === 0) {
              throw new Error(`@overriddenImplementation failed to find a doc for ${doc.overriddenImplementation}. Are you sure this symbol is documented and exported?`);
            }
            if (ctorDocArray.length >= 2) {
              throw new Error(`@overriddenImplementation found multiple docs for ${doc.overriddenImplementation}. You may only have one documented symbol for each.`);
            }

          const ctorDoc = ctorDocArray[0];

          // Copy the constructor overrides from the constructor doc, if any are present.
          if (!ctorDoc.members || ctorDoc.members.length === 0 || !ctorDoc.members[0].name.includes('new')) {
            throw new Error(`@overriddenImplementation requires that the provided constructor ${ctorDoc.id} have a member called "new", possibly with multiple overrides.`);
          }
          doc.constructorDoc = ctorDoc.members[0];

          // Mark the constructor doc internal.
          if (!ctorDoc.internal) {
            log.warn(`Constructor doc ${ctorDoc.id} was not marked '@internal'; adding this annotation.`);
            ctorDoc.internal = true;
          }

          // The exported doc should not be private.
          doc.privateExport = false;
          doc.internal = false;
        }
      });
    }
  };
};
<commit_msg>fix(docs-infra): prevent unnecessary warning in `mergeOverridenImplementation` Dgeni processor (#47201)<commit_after>/**
 * In some cases it is desirable to override the exported implementation and constructor for a symbol.
 * This is useful for a few reasons:
 * - A symbol could have multiple constructors
 * - It is possible to disambiguate multiple different signatures from a single polymorphic constructor
 * - The return type of the overridden constructor can differ (e.g. `Foo<T|null>` vs `Foo<T>`)
 *
 * This looks like the following:
 *
 * ```
 * /**
 * * @overriddenImplementation FooCtor
 * *\/
 * export interface Foo {
 *   bar();
 * }
 *
 * type FooInterface = Foo;
 *
 * export class FooImpl { ... }
 *
 * export interface FooCtor {
 *   new(): Foo;
 * }
 *
 * export const Foo: FooCtor =
    (class Foo implements FooInterface { ... }
 * ```
 *
 * This processor will extend the docs for symbol `Foo` by copying all documented constructor overrides from `FooCtor`.
 *
 * In order to use this processor, annotate the exported interface with `@overriddenImplementation`. Place the desired
 * documentation on the interface and constructor signatures.
 */
 module.exports = function mergeOverriddenImplementation(getDocFromAlias, log) {
  return {
    $runAfter: ['tags-extracted', 'ids-computed'],
    $runBefore: ['filterPrivateDocs'],
    propertiesToKeep: [
      'name', 'id', 'aliases', 'fileInfo', 'startingLine', 'endingLine',
      'path', 'originalModule', 'outputPath', 'privateExport', 'moduleDoc'
    ],
    $process(docs) {
      docs.forEach(doc => {
        if (doc.overriddenImplementation) {
          // Convert the specified name into a doc.
          const ctorDocArray = getDocFromAlias(doc.overriddenImplementation);
            if (ctorDocArray.length === 0) {
              throw new Error(`@overriddenImplementation failed to find a doc for ${doc.overriddenImplementation}. Are you sure this symbol is documented and exported?`);
            }
            if (ctorDocArray.length >= 2) {
              throw new Error(`@overriddenImplementation found multiple docs for ${doc.overriddenImplementation}. You may only have one documented symbol for each.`);
            }

          const ctorDoc = ctorDocArray[0];

          // Copy the constructor overrides from the constructor doc, if any are present.
          if (!ctorDoc.members || ctorDoc.members.length === 0 || !ctorDoc.members[0].name.includes('new')) {
            throw new Error(`@overriddenImplementation requires that the provided constructor ${ctorDoc.id} have a member called "new", possibly with multiple overrides.`);
          }
          doc.constructorDoc = ctorDoc.members[0];

          // Mark the constructor doc internal.
          if (!ctorDoc.internal && !ctorDoc.privateExport) {
            log.warn(
                `Constructor doc ${ctorDoc.id} was not marked as internal (either via an ` +
                '\'@internal\' annotation or a \'ɵ\' prefix). Marking it as internal.');
            ctorDoc.internal = true;
          }

          // The exported doc should not be private.
          doc.privateExport = false;
          doc.internal = false;
        }
      });
    }
  };
};
<|endoftext|>


<commit_before>/*jslint nomen: true*/
/*global define*/
define([
    'jquery',
    'oroui/js/mediator',
    'oroui/js/app/views/base/view',
    'oroform/js/app/views/datepair-view'
], function ($, mediator, BaseView, DatepairView) {
    'use strict';

    var CalendarEventFormView;
    CalendarEventFormView = BaseView.extend({

        /**
         * Options
         */
        options: {},

        /**
         * @constructor
         *
         * @param {Object} options
         */
        initialize: function (options) {
            this.options = options || {};
            this.render();
        },

        render: function () {
            var self = this;
            mediator.execute('layout:init', this.$el, this).done(function () {
                self.handleLayoutInit();
            });
        },

        handleLayoutInit: function () {
            var opts, datepair;
            opts = this.options;
            opts.container = this.$el;
            datepair = new DatepairView(opts);
            this.subview('datepair', datepair);
        }
    });

    return CalendarEventFormView;
});
<commit_msg>BAP-7667: Calendar Event - End Date/Time should update when Start Date/Time is updated. - Removed useless option<commit_after>/*jslint nomen: true*/
/*global define*/
define([
    'jquery',
    'oroui/js/mediator',
    'oroui/js/app/views/base/view',
    'oroform/js/app/views/datepair-view'
], function ($, mediator, BaseView, DatepairView) {
    'use strict';

    var CalendarEventFormView;
    CalendarEventFormView = BaseView.extend({

        /**
         * Options
         */
        options: {},

        /**
         * @constructor
         *
         * @param {Object} options
         */
        initialize: function (options) {
            this.options = options || {};
            this.render();
        },

        render: function () {
            var self = this;
            mediator.execute('layout:init', this.$el, this).done(function () {
                self.handleLayoutInit();
            });
        },

        handleLayoutInit: function () {
            var opts, datepair;
            opts = this.options;
            datepair = new DatepairView(opts);
            this.subview('datepair', datepair);
        }
    });

    return CalendarEventFormView;
});
<|endoftext|>


<commit_before>/**
 * Search widget with a {@link devilry.extjshelpers.searchwidget.MultiSearchField} on top
 * and results in a {@link devilry.extjshelpers.searchwidget.MultiSearchResults} below.
 *
 *     Search: ______________
 *
 *     |Result1             |
 *     +--------------------|
 *     |                    |
 *     |                    |
 *     |                    |
 *     +--------------------+
 *
 *     |Result2             |
 *     +--------------------|
 *     |                    |
 *     |                    |
 *     |                    |
 *     +--------------------+
 *
 * */
Ext.define('devilry.extjshelpers.searchwidget.SearchWidget', {
    extend: 'Ext.container.Container',
    alias: 'widget.searchwidget',
    cls: 'widget-searchwidget',
    requires: [
        'devilry.extjshelpers.SearchField',
        'devilry.extjshelpers.searchwidget.SearchResults',
        'devilry.extjshelpers.searchwidget.MultiSearchResults',
        'devilry.extjshelpers.SearchStringParser'
    ],

    config: {
        /**
         * @cfg
         * The {@link devilry.extjshelpers.searchwidget.SearchResults}, use
         * when searching.
         */
        searchResultItems: undefined
    },

    constructor: function(config) {
        this.callParent([config]);
        this.initConfig(config);
    },

    initComponent: function() {
        this.multisearchresultid = this.id + "-multisearchresults";
        Ext.apply(this, {
            items: [{
                xtype: 'searchfield'
            }, {
                xtype: 'multisearchresults',
                items: this.searchResultItems,
                id: this.multisearchresultid, // We need to use an id because it may be floating.
                searchWidget: this
            }]
        });

        this.callParent(arguments);
        this.setupSearchEventListeners();
    },

    setupSearchEventListeners: function() {
        var me = this;
        this.getSearchField().addListener('emptyInput', function() {
            me.hideResults();
        });
        this.getSearchField().addListener('newSearchValue', function(value) {
            console.log(value);
            me.search(value);
        });
    },

    getSearchField: function() {
        return this.down('searchfield');
    },

    focusOnSearchfield: function() {
        this.getSearchField().focus();
    },

    /**
     * @private
     *
     * Get the result container, which we expect to be the second item in this
     * container. */
    getResultContainer: function() {
        //return this.items.items[1];
        return Ext.getCmp(this.multisearchresultid);
    },

    showResults: function() {
        this.getResultContainer().show();
        this.getResultContainer().alignTo(this.getSearchField(), 'bl', [15, 0]);
    },

    hideResults: function() {
        this.getResultContainer().hide();
    },

    search: function(value) {
        var parsedSearch = Ext.create('devilry.extjshelpers.SearchStringParser', {
            searchstring: value
        });
        this.currentParsedSearch = parsedSearch;
        this.showResults();
        Ext.each(this.getResultContainer().items.items, function(searchresults, index, resultgrids) {
            searchresults.search(parsedSearch);
        });
    },

    modifySearch: function(properties) {
        Ext.apply(this.currentParsedSearch, properties);
        this.setSearchValue(this.currentParsedSearch.toString());
    },

    setSearchValue: function(value) {
        this.getSearchField().setValue(value);
    },

    loadInitialValues: function() {
        //var value = 'type:delivery deadline__assignment_group:>:33 3580';
        //var value = 'type:delivery assignment__short_name:week1';
        //var value = 'type:delivery group:'
        //var value = 'type:delivery deadline__assignment_group__parentnode__parentnode__short_name:duck3580';
        //var value = '1100';
        //this.setSearchValue(value);
    }
});
<commit_msg>apps.extjshelpers: Remove console.log.<commit_after>/**
 * Search widget with a {@link devilry.extjshelpers.searchwidget.MultiSearchField} on top
 * and results in a {@link devilry.extjshelpers.searchwidget.MultiSearchResults} below.
 *
 *     Search: ______________
 *
 *     |Result1             |
 *     +--------------------|
 *     |                    |
 *     |                    |
 *     |                    |
 *     +--------------------+
 *
 *     |Result2             |
 *     +--------------------|
 *     |                    |
 *     |                    |
 *     |                    |
 *     +--------------------+
 *
 * */
Ext.define('devilry.extjshelpers.searchwidget.SearchWidget', {
    extend: 'Ext.container.Container',
    alias: 'widget.searchwidget',
    cls: 'widget-searchwidget',
    requires: [
        'devilry.extjshelpers.SearchField',
        'devilry.extjshelpers.searchwidget.SearchResults',
        'devilry.extjshelpers.searchwidget.MultiSearchResults',
        'devilry.extjshelpers.SearchStringParser'
    ],

    config: {
        /**
         * @cfg
         * The {@link devilry.extjshelpers.searchwidget.SearchResults}, use
         * when searching.
         */
        searchResultItems: undefined
    },

    constructor: function(config) {
        this.callParent([config]);
        this.initConfig(config);
    },

    initComponent: function() {
        this.multisearchresultid = this.id + "-multisearchresults";
        Ext.apply(this, {
            items: [{
                xtype: 'searchfield'
            }, {
                xtype: 'multisearchresults',
                items: this.searchResultItems,
                id: this.multisearchresultid, // We need to use an id because it may be floating.
                searchWidget: this
            }]
        });

        this.callParent(arguments);
        this.setupSearchEventListeners();
    },

    setupSearchEventListeners: function() {
        var me = this;
        this.getSearchField().addListener('emptyInput', function() {
            me.hideResults();
        });
        this.getSearchField().addListener('newSearchValue', function(value) {
            //console.log(value);
            me.search(value);
        });
    },

    getSearchField: function() {
        return this.down('searchfield');
    },

    focusOnSearchfield: function() {
        this.getSearchField().focus();
    },

    /**
     * @private
     *
     * Get the result container, which we expect to be the second item in this
     * container. */
    getResultContainer: function() {
        //return this.items.items[1];
        return Ext.getCmp(this.multisearchresultid);
    },

    showResults: function() {
        this.getResultContainer().show();
        this.getResultContainer().alignTo(this.getSearchField(), 'bl', [15, 0]);
    },

    hideResults: function() {
        this.getResultContainer().hide();
    },

    search: function(value) {
        var parsedSearch = Ext.create('devilry.extjshelpers.SearchStringParser', {
            searchstring: value
        });
        this.currentParsedSearch = parsedSearch;
        this.showResults();
        Ext.each(this.getResultContainer().items.items, function(searchresults, index, resultgrids) {
            searchresults.search(parsedSearch);
        });
    },

    modifySearch: function(properties) {
        Ext.apply(this.currentParsedSearch, properties);
        this.setSearchValue(this.currentParsedSearch.toString());
    },

    setSearchValue: function(value) {
        this.getSearchField().setValue(value);
    },

    loadInitialValues: function() {
        //var value = 'type:delivery deadline__assignment_group:>:33 3580';
        //var value = 'type:delivery assignment__short_name:week1';
        //var value = 'type:delivery group:'
        //var value = 'type:delivery deadline__assignment_group__parentnode__parentnode__short_name:duck3580';
        //var value = '1100';
        //this.setSearchValue(value);
    }
});
<|endoftext|>


<commit_before>/**
  Keyboard Shortcut related functions.

  @class KeyboardShortcuts
  @namespace Discourse
  @module Discourse
**/
Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
  PATH_BINDINGS: {
    'g h': '/',
    'g l': '/latest',
    'g n': '/new',
    'g u': '/unread',
    'g f': '/favorited',
    'g c': '/categories',
    'g t': '/top',
  },

  CLICK_BINDINGS: {
    'b': 'article.selected button.bookmark',                      // bookmark current post
    'c': '#create-topic',                                         // create new topic
    'd': 'article.selected button.delete',                        // delete selected post
    'e': 'article.selected button.edit',                          // edit selected post

    // favorite topic
    'f': '#topic-footer-buttons button.favorite, #topic-list tr.topic-list-item.selected a.star',

    'l': 'article.selected button.like',                          // like selected post
    'm m': 'div.notification-options li[data-id="0"] a',          // mark topic as muted
    'm r': 'div.notification-options li[data-id="1"] a',          // mark topic as regular
    'm t': 'div.notification-options li[data-id="2"] a',          // mark topic as tracking
    'm w': 'div.notification-options li[data-id="3"] a',          // mark topic as watching
    'n': '#user-notifications',                                   // open notifictions menu
    'o,enter': '#topic-list tr.topic-list-item.selected a.title', // open selected topic
    'r': '#topic-footer-buttons button.create',                   // reply to topic
    'R': 'article.selected button.create',                        // reply to selected post
    's': '#topic-footer-buttons button.share',                    // share topic
    'S': 'article.selected button.share',                         // share selected post
    '/': '#search-button',                                        // focus search
    '!': 'article.selected button.flag',                          // flag selected post
    '?': '#keyboard-help'                                         // open keyboard shortcut help
  },

  FUNCTION_BINDINGS: {
    'j': 'selectDown',
    'k': 'selectUp',
    'u': 'goBack',
    '`': 'nextSection',
    '~': 'prevSection'
  },

  bindEvents: function(keyTrapper) {
    this.keyTrapper = keyTrapper;
    _.each(this.PATH_BINDINGS, this._bindToPath, this);
    _.each(this.CLICK_BINDINGS, this._bindToClick, this);
    _.each(this.FUNCTION_BINDINGS, this._bindToFunction, this);
  },

  selectDown: function() {
    this._moveSelection(1);
  },

  selectUp: function() {
    this._moveSelection(-1);
  },

  goBack: function() {
    history.back();
  },

  nextSection: function() {
    this._changeSection(1);
  },

  prevSection: function() {
    this._changeSection(-1);
  },

  _bindToPath: function(path, binding) {
    this.keyTrapper.bind(binding, function() {
      Discourse.URL.routeTo(path);
    });
  },

  _bindToClick: function(selector, binding) {
    binding = binding.split(',');
    this.keyTrapper.bind(binding, function(e) {
      if (!_.isUndefined(e) && _.isFunction(e.preventDefault)) {
        e.preventDefault();
      }

      $(selector).click();
    });
  },

  _bindToFunction: function(func, binding) {
    if (typeof this[func] === 'function') {
      this.keyTrapper.bind(binding, _.bind(this[func], this));
    }
  },

  _moveSelection: function(num) {
    var $articles = this._findArticles();

    if (typeof $articles === 'undefined') {
      return;
    }

    var $selected = $articles.filter('.selected'),
        index = $articles.index($selected),
        $article = $articles.eq(index + num);

    if ($article.size() > 0) {
      $articles.removeClass('selected');
      $article.addClass('selected');
      this._scrollList($article);
    }
  },

  _scrollList: function($article) {
    var $body = $('body'),
        distToElement = $article.position().top + $article.height() - $(window).height() - $body.scrollTop();

    $('html, body').scrollTop($body.scrollTop() + distToElement);
  },

  _findArticles: function() {
    var $topicList = $('#topic-list'),
        $topicArea = $('.posts-wrapper');

    if ($topicArea.size() > 0) {
      return $topicArea.find('.topic-post');
    }
    else if ($topicList.size() > 0) {
      return $topicList.find('.topic-list-item');
    }
  },

  _changeSection: function(num) {
    var $sections = $('#category-filter').find('li'),
        index = $sections.index('.active');

    $sections.eq(index + num).find('a').click();
  }
});
<commit_msg>BUGFIX: pressing '?' will now always open the keyboard shortcut modal<commit_after>/**
  Keyboard Shortcut related functions.

  @class KeyboardShortcuts
  @namespace Discourse
  @module Discourse
**/
Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
  PATH_BINDINGS: {
    'g h': '/',
    'g l': '/latest',
    'g n': '/new',
    'g u': '/unread',
    'g f': '/favorited',
    'g c': '/categories',
    'g t': '/top'
  },

  CLICK_BINDINGS: {
    'b': 'article.selected button.bookmark',                      // bookmark current post
    'c': '#create-topic',                                         // create new topic
    'd': 'article.selected button.delete',                        // delete selected post
    'e': 'article.selected button.edit',                          // edit selected post

    // favorite topic
    'f': '#topic-footer-buttons button.favorite, #topic-list tr.topic-list-item.selected a.star',

    'l': 'article.selected button.like',                          // like selected post
    'm m': 'div.notification-options li[data-id="0"] a',          // mark topic as muted
    'm r': 'div.notification-options li[data-id="1"] a',          // mark topic as regular
    'm t': 'div.notification-options li[data-id="2"] a',          // mark topic as tracking
    'm w': 'div.notification-options li[data-id="3"] a',          // mark topic as watching
    'n': '#user-notifications',                                   // open notifictions menu
    'o,enter': '#topic-list tr.topic-list-item.selected a.title', // open selected topic
    'r': '#topic-footer-buttons button.create',                   // reply to topic
    'R': 'article.selected button.create',                        // reply to selected post
    's': '#topic-footer-buttons button.share',                    // share topic
    'S': 'article.selected button.share',                         // share selected post
    '/': '#search-button',                                        // focus search
    '!': 'article.selected button.flag'                           // flag selected post
  },

  FUNCTION_BINDINGS: {
    'j': 'selectDown',
    'k': 'selectUp',
    'u': 'goBack',
    '`': 'nextSection',
    '~': 'prevSection',
    '?': 'showHelpModal'                                          // open keyboard shortcut help
  },

  bindEvents: function(keyTrapper) {
    this.keyTrapper = keyTrapper;
    _.each(this.PATH_BINDINGS, this._bindToPath, this);
    _.each(this.CLICK_BINDINGS, this._bindToClick, this);
    _.each(this.FUNCTION_BINDINGS, this._bindToFunction, this);
  },

  selectDown: function() {
    this._moveSelection(1);
  },

  selectUp: function() {
    this._moveSelection(-1);
  },

  goBack: function() {
    history.back();
  },

  nextSection: function() {
    this._changeSection(1);
  },

  prevSection: function() {
    this._changeSection(-1);
  },

  showHelpModal: function() {
    Discourse.__container__.lookup('controller:application').send("showKeyboardShortcutsHelp");
  },

  _bindToPath: function(path, binding) {
    this.keyTrapper.bind(binding, function() {
      Discourse.URL.routeTo(path);
    });
  },

  _bindToClick: function(selector, binding) {
    binding = binding.split(',');
    this.keyTrapper.bind(binding, function(e) {
      if (!_.isUndefined(e) && _.isFunction(e.preventDefault)) {
        e.preventDefault();
      }

      $(selector).click();
    });
  },

  _bindToFunction: function(func, binding) {
    if (typeof this[func] === 'function') {
      this.keyTrapper.bind(binding, _.bind(this[func], this));
    }
  },

  _moveSelection: function(num) {
    var $articles = this._findArticles();

    if (typeof $articles === 'undefined') {
      return;
    }

    var $selected = $articles.filter('.selected'),
        index = $articles.index($selected),
        $article = $articles.eq(index + num);

    if ($article.size() > 0) {
      $articles.removeClass('selected');
      $article.addClass('selected');
      this._scrollList($article);
    }
  },

  _scrollList: function($article) {
    var $body = $('body'),
        distToElement = $article.position().top + $article.height() - $(window).height() - $body.scrollTop();

    $('html, body').scrollTop($body.scrollTop() + distToElement);
  },

  _findArticles: function() {
    var $topicList = $('#topic-list'),
        $topicArea = $('.posts-wrapper');

    if ($topicArea.size() > 0) {
      return $topicArea.find('.topic-post');
    }
    else if ($topicList.size() > 0) {
      return $topicList.find('.topic-list-item');
    }
  },

  _changeSection: function(num) {
    var $sections = $('#category-filter').find('li'),
        index = $sections.index('.active');

    $sections.eq(index + num).find('a').click();
  }
});
<|endoftext|>


<commit_before>Groups = new Mongo.Collection("groups");

Meteor.startup(function () {
	Meteor.publish('userScores', function () {
		return Meteor.users.find({}, {profile: {displayName: 1, subs: 1, rejs: 1, accs: 1, wds: 1, score: 1, groupId: 1}});
	});

	/// Email Verification nonsense cribbed from gentlenode.com
	Accounts.emailTemplates.from = 'Sink or Submit <no-reply@meteor.com>';

	Accounts.emailTemplates.siteName = 'Sink or Submit';

	Accounts.emailTemplates.verifyEmail.subject = function (user) {
		return 'Confirm Your Email Address';
	};

	Accounts.emailTemplates.verifyEmail.text = function (user, url) {
		return 'Click the following link to verify your email address for Sink or Submit: ' + url;
	};

/*	Meteor.publish('groups', function () {
		return Groups.find(Meteor.user().groupId, {fields: {groupId: 1, groupDescription: 1}});
	});*/
});

Accounts.onCreateUser(function(options, user) {
	user.displayName = user.username;
	user.subs = 0;
	user.rejs = 0;
	user.accs = 0;
	user.wds = 0;
	user.score = 0;
	user.prevScore = 0;
	user.groupId = null;
  	user.groupAdmin = false;
	user.groups = [];

	if (options.profile)
		user.profile = options.profile;

	Meteor.setTimeout(function () {
		Accounts.sendVerificationEmail(user._id);
	}, 2 * 1000);

	return user;
});

Accounts.validateLoginAttempt(function (attempt) {
	if (attempt.user && attempt.user.emails && !attempt.user.emails[0].verified ) {
		console.log('email not verified');

		return false;
	}

	return true;
});

Meteor.publish('userData', function() {
	if (!this.userId) return null;
	return Meteor.users.find(this.userId, {fields: {
		displayName: 1,
		subs: 1,
		rejs: 1,
		accs: 1,
		wds: 1,
		score: 1,
	  	groupId: 1,
	  	groupAdmin: 1,
		groups: 1}
	});
});

Meteor.publish("groups", function () {
	return Groups.find({}, {fields: {
		groupId: 1,
		groupDescription: 1,
		members: 1,
		admins: 1}
	});
});

Meteor.methods({
	setDisplayName: function (name) {
		Meteor.users.update(Meteor.userId(), {$set: {displayName: name}});
	},

	groupStrap: function () {
		// bootstrap users who don't have the groups[] attribute in their user profile yet
		if (!Meteor.user().groups) {
			Meteor.users.update(Meteor.userId(), {$push: { groups: {
				gid: Meteor.user().groupId,
				description: Groups.findOne(Meteor.user().groupId).groupDescription,
				adminOfGroup: Meteor.user().groupAdmin}}
			});
		}
	},

	joinGroup: function (group, admin, secret) {
		// confirms that secret provided matches with group secret
		if (secret === Groups.findOne({groupId: group}, {fields: {secret: 1}}).secret) {
			// takes a group's shortname and converts it to group's _id
			var id = Groups.findOne({groupId: group}, {fields: {_id: 1}})._id;
			Meteor.users.update(Meteor.userId(), {$set: {groupId: id}});
			Meteor.users.update(Meteor.userId(), {$push: { groups: {
				gid: id,
				description: Groups.findOne(id).groupDescription,
				adminOfGroup: admin}}
			});
			Meteor.call("memberNum", id);
			Meteor.call("makeAdmin", Meteor.userId(), admin);
		}
	},

	switchGroup: function (gid) {
		// Switch which group
		var group = Meteor.user().groups.filter(function (obj) {
			return obj.gid === gid
		});
		Meteor.users.update(Meteor.userId(), {$set: {groupId: group[0].gid, groupAdmin: group[0].adminOfGroup}});
	},

	memberNum: function (gid) {
		var number = Meteor.users.find({groupId: gid}).fetch().length;
		console.log("This group now has " + number + " members.");
		Groups.update(gid, {$set: {members: number}});
	},

	removeGroup: function (gid) {
		if (Groups.findOne(gid).members === 0) {
			console.log("Removing group " + gid);
			Groups.remove(gid);
		}
	},

	changeGroupName: function (name) {
		Groups.update(Meteor.user().groupId, {$set: {groupDescription: name}});
	},

	changeGroupSecret: function (oldSecret, newSecret) {
		if (Groups.findOne(Meteor.user().groupId).secret === oldSecret)
			Groups.update(Meteor.user().groupId, {$set: {secret: newSecret}});
	},

	makeAdmin: function (id, admin) {
		// Below comment no longer true, but I'm keeping it there so that this stupid function will
		// remember what it's done.
		//
		// THIS PIECE OF SHIT STILL WON'T GIVE ACCURATE NUMBERS ON GROUP ADMINS ON THE FIRST TRY
		// AND I HATE ITS GUTS
		var gid = Meteor.users.findOne(id).groupId;
		console.log("Group id: " + gid);
		if (admin) {
			console.log("Adding admin");
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			Meteor.users.update({"_id": id, "groups.gid": gid}, {$set: {"groups.$.adminOfGroup": admin}});
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		} else {
			console.log("Setting admin status to " + admin);
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			Meteor.users.update({"_id": id, "groups.gid": gid}, {$set: {"groups.$.adminOfGroup": admin}});
			console.log("Calculating number of admins...");
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group, according to users.find.length method: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		}
		console.log("Final number of admins: " + num);
	},

	adminNum: function (gid, number) {
		Groups.update(gid, {$set: {admins: number}});
	},

	leaveGroup: function (id) {
		// Set user id's group to null and strip admin privileges
		var gid = Meteor.users.findOne(id).groupId;
		console.log("User with id " + id + " leaving group");
		Meteor.call("makeAdmin", id, false);
		Meteor.users.update(id, {$set: {groupId: null}});
		Meteor.call("memberNum", gid);
	},

	createGroup: function (groupId, groupDescription, secret) {
		if (! Meteor.userId())
			throw new Meteor.Error("not-authorized");

		Groups.insert({
			groupId: groupId,
			groupDescription: groupDescription,
			secret: secret
		});
	},

  	incSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incAccs: function () {
		// Buffers user's previous score in case of accidental acceptance log
		var score = Meteor.user().score;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: 1}});
		Meteor.users.update(Meteor.userId(), {$set: {prevScore: score}});
		Meteor.users.update(Meteor.userId(), {$set: {score: 0}});
  	},

  	decAccs: function () {
		// Restores user's score in case of accidental acceptance log
		var prevScore = Meteor.user().prevScore;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: -1}});
		Meteor.users.update(Meteor.userId(), {$set: {score: prevScore}});
  	},

  	incWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: 1}});
  	},

  	decWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: -1}});
  	},

	setGroup: function (group) {
		Meteor.users.update(Meteor.user(), {$set: {groupId: group}});
	}

});
<commit_msg>Fixed bug with users leaving groups not updating groups[] attribure in user record.<commit_after>Groups = new Mongo.Collection("groups");

Meteor.startup(function () {
	Meteor.publish('userScores', function () {
		return Meteor.users.find({}, {profile: {displayName: 1, subs: 1, rejs: 1, accs: 1, wds: 1, score: 1, groupId: 1}});
	});

	/// Email Verification nonsense cribbed from gentlenode.com
	Accounts.emailTemplates.from = 'Sink or Submit <no-reply@meteor.com>';

	Accounts.emailTemplates.siteName = 'Sink or Submit';

	Accounts.emailTemplates.verifyEmail.subject = function (user) {
		return 'Confirm Your Email Address';
	};

	Accounts.emailTemplates.verifyEmail.text = function (user, url) {
		return 'Click the following link to verify your email address for Sink or Submit: ' + url;
	};

/*	Meteor.publish('groups', function () {
		return Groups.find(Meteor.user().groupId, {fields: {groupId: 1, groupDescription: 1}});
	});*/
});

Accounts.onCreateUser(function(options, user) {
	user.displayName = user.username;
	user.subs = 0;
	user.rejs = 0;
	user.accs = 0;
	user.wds = 0;
	user.score = 0;
	user.prevScore = 0;
	user.groupId = null;
  	user.groupAdmin = false;
	user.groups = [];

	if (options.profile)
		user.profile = options.profile;

	Meteor.setTimeout(function () {
		Accounts.sendVerificationEmail(user._id);
	}, 2 * 1000);

	return user;
});

Accounts.validateLoginAttempt(function (attempt) {
	if (attempt.user && attempt.user.emails && !attempt.user.emails[0].verified ) {
		console.log('email not verified');

		return false;
	}

	return true;
});

Meteor.publish('userData', function() {
	if (!this.userId) return null;
	return Meteor.users.find(this.userId, {fields: {
		displayName: 1,
		subs: 1,
		rejs: 1,
		accs: 1,
		wds: 1,
		score: 1,
	  	groupId: 1,
	  	groupAdmin: 1,
		groups: 1}
	});
});

Meteor.publish("groups", function () {
	return Groups.find({}, {fields: {
		groupId: 1,
		groupDescription: 1,
		members: 1,
		admins: 1}
	});
});

Meteor.methods({
	setDisplayName: function (name) {
		Meteor.users.update(Meteor.userId(), {$set: {displayName: name}});
	},

	groupStrap: function () {
		// bootstrap users who don't have the groups[] attribute in their user profile yet
		if (!Meteor.user().groups) {
			Meteor.users.update(Meteor.userId(), {$push: { groups: {
				gid: Meteor.user().groupId,
				description: Groups.findOne(Meteor.user().groupId).groupDescription,
				adminOfGroup: Meteor.user().groupAdmin}}
			});
		}
	},

	joinGroup: function (group, admin, secret) {
		// confirms that secret provided matches with group secret
		if (secret === Groups.findOne({groupId: group}, {fields: {secret: 1}}).secret) {
			// takes a group's shortname and converts it to group's _id
			var id = Groups.findOne({groupId: group}, {fields: {_id: 1}})._id;
			Meteor.users.update(Meteor.userId(), {$set: {groupId: id}});
			Meteor.users.update(Meteor.userId(), {$push: { groups: {
				gid: id,
				description: Groups.findOne(id).groupDescription,
				adminOfGroup: admin}}
			});
			Meteor.call("memberNum", id);
			Meteor.call("makeAdmin", Meteor.userId(), admin);
		}
	},

	switchGroup: function (gid) {
		// Switch which group
		var group = Meteor.user().groups.filter(function (obj) {
			return obj.gid === gid
		});
		Meteor.users.update(Meteor.userId(), {$set: {groupId: group[0].gid, groupAdmin: group[0].adminOfGroup}});
	},

	memberNum: function (gid) {
		var number = Meteor.users.find({groupId: gid}).fetch().length;
		console.log("This group now has " + number + " members.");
		Groups.update(gid, {$set: {members: number}});
	},

	removeGroup: function (gid) {
		if (Groups.findOne(gid).members === 0) {
			console.log("Removing group " + gid);
			Groups.remove(gid);
		}
	},

	changeGroupName: function (name) {
		Groups.update(Meteor.user().groupId, {$set: {groupDescription: name}});
	},

	changeGroupSecret: function (oldSecret, newSecret) {
		if (Groups.findOne(Meteor.user().groupId).secret === oldSecret)
			Groups.update(Meteor.user().groupId, {$set: {secret: newSecret}});
	},

	makeAdmin: function (id, admin) {
		// Below comment no longer true, but I'm keeping it there so that this stupid function will
		// remember what it's done.
		//
		// THIS PIECE OF SHIT STILL WON'T GIVE ACCURATE NUMBERS ON GROUP ADMINS ON THE FIRST TRY
		// AND I HATE ITS GUTS
		var gid = Meteor.users.findOne(id).groupId;
		console.log("Group id: " + gid);
		if (admin) {
			console.log("Adding admin");
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			Meteor.users.update({"_id": id, "groups.gid": gid}, {$set: {"groups.$.adminOfGroup": admin}});
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		} else {
			console.log("Setting admin status to " + admin);
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			Meteor.users.update({"_id": id, "groups.gid": gid}, {$set: {"groups.$.adminOfGroup": admin}});
			console.log("Calculating number of admins...");
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group, according to users.find.length method: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		}
		console.log("Final number of admins: " + num);
	},

	adminNum: function (gid, number) {
		Groups.update(gid, {$set: {admins: number}});
	},

	leaveGroup: function (id) {
		// Set user id's group to null and strip admin privileges
		var gid = Meteor.users.findOne(id).groupId;
		console.log("User with id " + id + " leaving group");
		Meteor.call("makeAdmin", id, false);
		if (Meteor.users.findOne(id).groups.length < 2) {
			Meteor.users.update(id, {$set: {groupId: null}});
		} else {
			Meteor.users.update(id, {$set: {groupId: Meteor.users.findOne(id).groups[0].gid, groupAdmin: Meteor.users.findOne(id).groups[0].adminOfGroup}});
		}
		Meteor.users.update(id, {$pull: {groups: {gid: gid}}}, {multi: true});
		Meteor.call("memberNum", gid);
	},

	createGroup: function (groupId, groupDescription, secret) {
		if (! Meteor.userId())
			throw new Meteor.Error("not-authorized");

		Groups.insert({
			groupId: groupId,
			groupDescription: groupDescription,
			secret: secret
		});
	},

  	incSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incAccs: function () {
		// Buffers user's previous score in case of accidental acceptance log
		var score = Meteor.user().score;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: 1}});
		Meteor.users.update(Meteor.userId(), {$set: {prevScore: score}});
		Meteor.users.update(Meteor.userId(), {$set: {score: 0}});
  	},

  	decAccs: function () {
		// Restores user's score in case of accidental acceptance log
		var prevScore = Meteor.user().prevScore;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: -1}});
		Meteor.users.update(Meteor.userId(), {$set: {score: prevScore}});
  	},

  	incWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: 1}});
  	},

  	decWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: -1}});
  	},

	setGroup: function (group) {
		Meteor.users.update(Meteor.user(), {$set: {groupId: group}});
	}

});
<|endoftext|>


<commit_before>const config = require('config');
const {EVENTS, ERRORS, REPOSITORY_SYNC_TASKS} = require('../constants');
const GitHubApi = require("github");
const GitHubToken = config.get('github.token');
const parseGitHubUrl = require('parse-github-url');
const {Repository, Issue} = require('../models');
const async = require('async');
const _ = require('lodash');
const {getIssues} = require('../issues');
const {train, issueSimilarities} = require('../classifier');

const github = new GitHubApi({debug: false});

module.exports = function(socket, io) {

  socket.on(EVENTS.PARSE_REPOSITORY_URL, (repositoryUrl, cb) => {
    let repo = parseGitHubUrl(repositoryUrl);
    return cb(repo);
  });

  /**
  Sync repository issues
  */
  socket.on(EVENTS.REPOSITORY_SYNC, (repository_url, cb) => {
    if (!socket.github)
      return cb(ERRORS.MISSING_GITHUB);

    // Validate repo URL
    let repo = parseGitHubUrl(repository_url);
    if (repo && repo.name) {
      // Join Repository room
      let repoRoom = `repo:${repo.repo}`;
      socket.join(repoRoom);

      // Repository specific progress emitter
      let progress = (data) => {
        console.log('Progress', data);
        io.sockets. in(repoRoom).emit(EVENTS.REPOSITORY_SYNC_PROGRESS, data);
      };

      // Get all Issues for a GitHub Repository
      let {owner, name} = repo;

      async.auto({
        /**
        Get repository information
        */
        repo: (cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.REPO,
            percent: 0
          });

          socket.github.repos.get({
            user: owner,
            repo: name
          }, (err, repo) => {
            if (err) {
              return cb(err);
            }
            repo = Repository.transformFields(repo);
            // Attach the authentication token for repository access later
            repo.token = socket.token;

            progress({
              task: REPOSITORY_SYNC_TASKS.REPO,
              percent: 1
            });

            return cb(null, repo);
          });
        },
        /**
        Get all Issues for repository
        */
        issues: (cb) => {

          function showProgress({percent}) {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUES,
              percent: percent
            });
          }
          getIssues(socket.github, owner, name, showProgress)
          .then((results) => {
            console.log('Has Issues!', results.length);
            // Transform GitHub Issues list
            let issues = _.map(results, Issue.transformFields);

            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUES,
              percent: 1
            });

            console.log('Finished getting issues!');
            cb(null, issues);
          })
          .catch((err) => {
            cb(err);
          });
        },
        /**
        Create records for Repository and Issues
        */
        records: ['repo', 'issues', (results, cb) => {
          console.log('Bulk insert records!');

          progress({
            task: REPOSITORY_SYNC_TASKS.DATABASE,
            percent: 0
          });

          // Insert Repository
          let repo = results.repo;
          let {issues} = results;
          let repoId = repo.id;
          Repository.upsert(results.repo)
          .then(() => {
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 0.5
            });

            // Delete any existing Issues
            // Sequelize + PostgreSQL does not support Bulk+Upsert
            return Issue.destroy({where:{repository_id: repoId}});
          })
          .then(() => {
            // Bulk Insert Issues
            _.each(issues, (issue) => {
              // Associate Issue with Repository
              issue.repository_id = repoId;
            });
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 0.6
            });
            return Issue.bulkCreate(issues);
          })
          .then((issues) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 1
            });
            return cb(null, issues);
          })
          .catch((err) => {
            return cb(err);
          });

        }],
        /**
        Create webhooks for repository

        See https://developer.github.com/v3/repos/hooks/#create-a-hook
        and http://mikedeboer.github.io/node-github/#api-repos-createHook
        */
        webhooks: ['repo', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.WEBHOOK,
            percent: 0
          });

          let {repo} = results;
          // let webhookUrl = `${config.get('server.base_url')}/webhook/${repo.owner}/${repo.name}`;
          let webhookUrl = `${config.get('server.base_url')}/webhook`;
          socket.github.repos.createHook({
            user: repo.owner,
            repo: repo.name,
            name: 'web',
            events: ['issues','issue_comment','pull_request'],
            active: true,
            config: {
              url: webhookUrl,
              content_type: 'json',
              secret: config.get('github.webhook_secret')
            }
          }, (err) => {
            if (err) {
              console.log('webhook error', err);
              let errorMessage = err && err.message;
              try {
                errorMessage = _.get(JSON.parse(err.message), 'errors[0].message');
              } catch (err) {
                // Don't worry about it right now.
                // These errors are likely because of duplicate webhook.
                // We can write webhooks but cannot read, so we write assuming it does not exist
                // and fail silently otherwise.
              }
              progress({
                task: REPOSITORY_SYNC_TASKS.WEBHOOK,
                percent: 1,
                message: errorMessage
              });
            } else {
              progress({
                task: REPOSITORY_SYNC_TASKS.WEBHOOK,
                percent: 1,
              });
            }
            return cb();
            // return cb(err && err.message);
          });
        }],
        /**
        Train model to label issues
        */
        train_labels: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
            percent: 0
          });
          let issues = results.issues;

          let {repo} = results;
          let {owner, name} = repo;
          let ignoreLabels = []; // TODO

          train('labels', [owner, name, issues, ignoreLabels])
          .then((resp) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
              percent: 1
            });
            return cb(null, resp);
          })
          .catch((err) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
              percent: 1,
              message: err.message
            });
            return cb(err);
          });

        }],
        /**
        Train model to detect duplicate issues
        */
        issue_similarities: ['issues', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
            percent: 0
          });
          let issues = results.issues;

          issueSimilarities(issues)
          .then((resp) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
              percent: 1
            });
            return cb(null, resp);
          })
          .catch((err) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
              percent: 1,
              message: err.message
            });
            return cb(err);
          });

        }],
        /**
        Train model to predict assignees for issues
        */
        train_assignees: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_ASSIGNEES,
            percent: 1
          });
          return cb();
        }],
        /**
        Train model to predict milestones for issues
        */
        train_milestones: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_MILESTONES,
            percent: 1
          });
          return cb();
        }]
      }, (err, results) => {
        console.log('Done!', err, Object.keys(results));
        socket.leave(repoRoom);

        // TOOD:
        return cb(err && err.message, results);

      });

    } else {
      // Repository URL was invalid
      return cb('Invalid repository URL. Please try again.');
    }

  });

};<commit_msg>Handle errors when parsing GitHub URL throws up<commit_after>const config = require('config');
const {EVENTS, ERRORS, REPOSITORY_SYNC_TASKS} = require('../constants');
const GitHubApi = require("github");
const GitHubToken = config.get('github.token');
const parseGitHubUrl = require('parse-github-url');
const {Repository, Issue} = require('../models');
const async = require('async');
const _ = require('lodash');
const {getIssues} = require('../issues');
const {train, issueSimilarities} = require('../classifier');

const github = new GitHubApi({debug: false});

module.exports = function(socket, io) {

  socket.on(EVENTS.PARSE_REPOSITORY_URL, (repositoryUrl, cb) => {
    try {
      let repo = parseGitHubUrl(repositoryUrl);
      return cb(repo);
    } catch (err) {
      return cb(null);
    }
  });

  /**
  Sync repository issues
  */
  socket.on(EVENTS.REPOSITORY_SYNC, (repository_url, cb) => {
    if (!socket.github)
      return cb(ERRORS.MISSING_GITHUB);

    // Validate repo URL
    let repo = parseGitHubUrl(repository_url);
    if (repo && repo.name) {
      // Join Repository room
      let repoRoom = `repo:${repo.repo}`;
      socket.join(repoRoom);

      // Repository specific progress emitter
      let progress = (data) => {
        console.log('Progress', data);
        io.sockets. in(repoRoom).emit(EVENTS.REPOSITORY_SYNC_PROGRESS, data);
      };

      // Get all Issues for a GitHub Repository
      let {owner, name} = repo;

      async.auto({
        /**
        Get repository information
        */
        repo: (cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.REPO,
            percent: 0
          });

          socket.github.repos.get({
            user: owner,
            repo: name
          }, (err, repo) => {
            if (err) {
              return cb(err);
            }
            repo = Repository.transformFields(repo);
            // Attach the authentication token for repository access later
            repo.token = socket.token;

            progress({
              task: REPOSITORY_SYNC_TASKS.REPO,
              percent: 1
            });

            return cb(null, repo);
          });
        },
        /**
        Get all Issues for repository
        */
        issues: (cb) => {

          function showProgress({percent}) {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUES,
              percent: percent
            });
          }
          getIssues(socket.github, owner, name, showProgress)
          .then((results) => {
            console.log('Has Issues!', results.length);
            // Transform GitHub Issues list
            let issues = _.map(results, Issue.transformFields);

            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUES,
              percent: 1
            });

            console.log('Finished getting issues!');
            cb(null, issues);
          })
          .catch((err) => {
            cb(err);
          });
        },
        /**
        Create records for Repository and Issues
        */
        records: ['repo', 'issues', (results, cb) => {
          console.log('Bulk insert records!');

          progress({
            task: REPOSITORY_SYNC_TASKS.DATABASE,
            percent: 0
          });

          // Insert Repository
          let repo = results.repo;
          let {issues} = results;
          let repoId = repo.id;
          Repository.upsert(results.repo)
          .then(() => {
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 0.5
            });

            // Delete any existing Issues
            // Sequelize + PostgreSQL does not support Bulk+Upsert
            return Issue.destroy({where:{repository_id: repoId}});
          })
          .then(() => {
            // Bulk Insert Issues
            _.each(issues, (issue) => {
              // Associate Issue with Repository
              issue.repository_id = repoId;
            });
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 0.6
            });
            return Issue.bulkCreate(issues);
          })
          .then((issues) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.DATABASE,
              percent: 1
            });
            return cb(null, issues);
          })
          .catch((err) => {
            return cb(err);
          });

        }],
        /**
        Create webhooks for repository

        See https://developer.github.com/v3/repos/hooks/#create-a-hook
        and http://mikedeboer.github.io/node-github/#api-repos-createHook
        */
        webhooks: ['repo', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.WEBHOOK,
            percent: 0
          });

          let {repo} = results;
          // let webhookUrl = `${config.get('server.base_url')}/webhook/${repo.owner}/${repo.name}`;
          let webhookUrl = `${config.get('server.base_url')}/webhook`;
          socket.github.repos.createHook({
            user: repo.owner,
            repo: repo.name,
            name: 'web',
            events: ['issues','issue_comment','pull_request'],
            active: true,
            config: {
              url: webhookUrl,
              content_type: 'json',
              secret: config.get('github.webhook_secret')
            }
          }, (err) => {
            if (err) {
              console.log('webhook error', err);
              let errorMessage = err && err.message;
              try {
                errorMessage = _.get(JSON.parse(err.message), 'errors[0].message');
              } catch (err) {
                // Don't worry about it right now.
                // These errors are likely because of duplicate webhook.
                // We can write webhooks but cannot read, so we write assuming it does not exist
                // and fail silently otherwise.
              }
              progress({
                task: REPOSITORY_SYNC_TASKS.WEBHOOK,
                percent: 1,
                message: errorMessage
              });
            } else {
              progress({
                task: REPOSITORY_SYNC_TASKS.WEBHOOK,
                percent: 1,
              });
            }
            return cb();
            // return cb(err && err.message);
          });
        }],
        /**
        Train model to label issues
        */
        train_labels: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
            percent: 0
          });
          let issues = results.issues;

          let {repo} = results;
          let {owner, name} = repo;
          let ignoreLabels = []; // TODO

          train('labels', [owner, name, issues, ignoreLabels])
          .then((resp) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
              percent: 1
            });
            return cb(null, resp);
          })
          .catch((err) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.TRAIN_LABELS,
              percent: 1,
              message: err.message
            });
            return cb(err);
          });

        }],
        /**
        Train model to detect duplicate issues
        */
        issue_similarities: ['issues', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
            percent: 0
          });
          let issues = results.issues;

          issueSimilarities(issues)
          .then((resp) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
              percent: 1
            });
            return cb(null, resp);
          })
          .catch((err) => {
            progress({
              task: REPOSITORY_SYNC_TASKS.ISSUE_SIMILARITIES,
              percent: 1,
              message: err.message
            });
            return cb(err);
          });

        }],
        /**
        Train model to predict assignees for issues
        */
        train_assignees: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_ASSIGNEES,
            percent: 1
          });
          return cb();
        }],
        /**
        Train model to predict milestones for issues
        */
        train_milestones: ['repo', 'issues', 'records', (results, cb) => {
          progress({
            task: REPOSITORY_SYNC_TASKS.TRAIN_MILESTONES,
            percent: 1
          });
          return cb();
        }]
      }, (err, results) => {
        console.log('Done!', err, Object.keys(results));
        socket.leave(repoRoom);

        // TOOD:
        return cb(err && err.message, results);

      });

    } else {
      // Repository URL was invalid
      return cb('Invalid repository URL. Please try again.');
    }

  });

};<|endoftext|>


<commit_before>var fileStorage = require('../fileStorage')('chunks');

module.exports = {
  saveChunk: function(chunkId, chunk) {
    return fileStorage.save('chunks/' + chunkId, chunk);
  },
  loadChunk: function(chunkId) {
    return fileStorage.load('chunks/' + chunkId);
  }
};
<commit_msg>fixing bug with voxel store<commit_after>var fileStorage = require('../fileStorage')('chunks');

module.exports = {
  saveChunk: function(chunkId, chunk) {
    return fileStorage.save(chunkId, chunk);
  },
  loadChunk: function(chunkId) {
    return fileStorage.load(chunkId);
  }
};
<|endoftext|>