<commit_before>module.exports = {
  rules: {
    // enforce spacing inside array brackets
    'array-bracket-spacing': ['error', 'never'],

    // enforce spacing inside single-line blocks
    // http://eslint.org/docs/rules/block-spacing
    'block-spacing': ['error', 'always'],

    // enforce one true brace style
    'brace-style': ['error', '1tbs', { allowSingleLine: true }],

    // require camel case names
    camelcase: ['error', { properties: 'never' }],

    // enforce spacing before and after comma
    'comma-spacing': ['error', { before: false, after: true }],

    // enforce one true comma style
    'comma-style': ['error', 'last'],

    // disallow padding inside computed properties
    'computed-property-spacing': ['error', 'never'],

    // enforces consistent naming when capturing the current execution context
    'consistent-this': 'off',

    // enforce newline at the end of file, with no multiple empty lines
    'eol-last': 'error',

    // enforce spacing between functions and their invocations
    // http://eslint.org/docs/rules/func-call-spacing
    'func-call-spacing': ['error', 'never'],

    // require function expressions to have a name
    'func-names': 'warn',

    // enforces use of function declarations or expressions
    'func-style': 'off',

    // Blacklist certain identifiers to prevent them being used
    // http://eslint.org/docs/rules/id-blacklist
    'id-blacklist': 'off',

    // this option enforces minimum and maximum identifier lengths
    // (variable names, property names etc.)
    'id-length': 'off',

    // require identifiers to match the provided regular expression
    'id-match': 'off',

    // this option sets a specific tab width for your code
    // http://eslint.org/docs/rules/indent
    indent: ['error', 2, { SwitchCase: 1, VariableDeclarator: 1, outerIIFEBody: 1 }],

    // specify whether double or single quotes should be used in JSX attributes
    // http://eslint.org/docs/rules/jsx-quotes
    'jsx-quotes': ['off', 'prefer-double'],

    // enforces spacing between keys and values in object literal properties
    'key-spacing': ['error', { beforeColon: false, afterColon: true }],

    // require a space before & after certain keywords
    'keyword-spacing': ['error', {
      before: true,
      after: true,
      overrides: {
        return: { after: true },
        throw: { after: true },
        case: { after: true }
      }
    }],

    // disallow mixed 'LF' and 'CRLF' as linebreaks
    // http://eslint.org/docs/rules/linebreak-style
    'linebreak-style': ['error', 'unix'],

    // enforces empty lines around comments
    'lines-around-comment': 'off',

    // specify the maximum depth that blocks can be nested
    'max-depth': ['off', 4],

    // specify the maximum length of a line in your program
    // http://eslint.org/docs/rules/max-len
    'max-len': ['error', 100, 2, {
      ignoreUrls: true,
      ignoreComments: false
    }],

    // specify the max number of lines in a file
    // http://eslint.org/docs/rules/max-lines
    'max-lines': ['off', {
      max: 300,
      skipBlankLines: true,
      skipComments: true
    }],

    // specify the maximum depth callbacks can be nested
    'max-nested-callbacks': 'off',

    // limits the number of parameters that can be used in the function declaration.
    'max-params': ['off', 3],

    // specify the maximum number of statement allowed in a function
    'max-statements': ['off', 10],

    // restrict the number of statements per line
    // http://eslint.org/docs/rules/max-statements-per-line
    'max-statements-per-line': ['off', { max: 1 }],

    // require multiline ternary
    // http://eslint.org/docs/rules/multiline-ternary
    'multiline-ternary': 'off',

    // require a capital letter for constructors
    'new-cap': ['error', { newIsCap: true }],

    // disallow the omission of parentheses when invoking a constructor with no arguments
    // http://eslint.org/docs/rules/new-parens
    'new-parens': 'error',

    // allow/disallow an empty newline after var statement
    'newline-after-var': 'off',

    // http://eslint.org/docs/rules/newline-before-return
    'newline-before-return': 'off',

    // enforces new line after each method call in the chain to make it
    // more readable and easy to maintain
    // http://eslint.org/docs/rules/newline-per-chained-call
    'newline-per-chained-call': ['error', { ignoreChainWithDepth: 4 }],

    // disallow use of the Array constructor
    'no-array-constructor': 'error',

    // disallow use of bitwise operators
    // http://eslint.org/docs/rules/no-bitwise
    // TODO: enable
    'no-bitwise': 'off',

    // disallow use of the continue statement
    // http://eslint.org/docs/rules/no-continue
    'no-continue': 'error',

    // disallow comments inline after code
    'no-inline-comments': 'off',

    // disallow if as the only statement in an else block
    // http://eslint.org/docs/rules/no-lonely-if
    'no-lonely-if': 'error',

    // disallow un-paren'd mixes of different operators
    // http://eslint.org/docs/rules/no-mixed-operators
    'no-mixed-operators': ['error', {
      groups: [
        ['+', '-', '*', '/', '%', '**'],
        ['&', '|', '^', '~', '<<', '>>', '>>>'],
        ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
        ['&&', '||'],
        ['in', 'instanceof']
      ],
      allowSamePrecedence: false
    }],

    // disallow mixed spaces and tabs for indentation
    'no-mixed-spaces-and-tabs': 'error',

    // disallow multiple empty lines and only one newline at the end
    'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],

    // disallow negated conditions
    // http://eslint.org/docs/rules/no-negated-condition
    'no-negated-condition': 'off',

    // disallow nested ternary expressions
    'no-nested-ternary': 'error',

    // disallow use of the Object constructor
    'no-new-object': 'error',

    // disallow use of unary operators, ++ and --
    'no-plusplus': 'off',

    // disallow certain syntax forms
    // http://eslint.org/docs/rules/no-restricted-syntax
    'no-restricted-syntax': [
      'error',
      'ForInStatement',
      'LabeledStatement',
      'WithStatement',
    ],

    // disallow space between function identifier and application
    'no-spaced-func': 'error',

    // disallow tab characters entirely
    // TODO: enable
    'no-tabs': 'off',

    // disallow the use of ternary operators
    'no-ternary': 'off',

    // disallow trailing whitespace at the end of lines
    'no-trailing-spaces': 'error',

    // disallow dangling underscores in identifiers
    'no-underscore-dangle': ['error', { allowAfterThis: false }],

    // disallow the use of Boolean literals in conditional expressions
    // also, prefer `a || b` over `a ? a : b`
    // http://eslint.org/docs/rules/no-unneeded-ternary
    'no-unneeded-ternary': ['error', { defaultAssignment: false }],

    // disallow whitespace before properties
    // http://eslint.org/docs/rules/no-whitespace-before-property
    'no-whitespace-before-property': 'error',

    // require padding inside curly braces
    'object-curly-spacing': ['error', 'always'],

    // enforce line breaks between braces
    // http://eslint.org/docs/rules/object-curly-newline
    // TODO: enable once https://github.com/eslint/eslint/issues/6488 is resolved
    'object-curly-newline': ['off', {
      ObjectExpression: { minProperties: 0, multiline: true },
      ObjectPattern: { minProperties: 0, multiline: true }
    }],

    // enforce "same line" or "multiple line" on object properties.
    // http://eslint.org/docs/rules/object-property-newline
    'object-property-newline': ['error', {
      allowMultiplePropertiesPerLine: true,
    }],

    // allow just one var statement per function
    'one-var': ['error', 'never'],

    // require a newline around variable declaration
    // http://eslint.org/docs/rules/one-var-declaration-per-line
    'one-var-declaration-per-line': ['error', 'always'],

    // require assignment operator shorthand where possible or prohibit it entirely
    // http://eslint.org/docs/rules/operator-assignment
    'operator-assignment': ['error', 'always'],

    // enforce operators to be placed before or after line breaks
    'operator-linebreak': 'off',

    // enforce padding within blocks
    'padded-blocks': ['error', 'never'],

    // require quotes around object literal property names
    // http://eslint.org/docs/rules/quote-props.html
    'quote-props': ['error', 'as-needed', { keywords: false, unnecessary: true, numbers: false }],

    // specify whether double or single quotes should be used
    quotes: ['error', 'single', { avoidEscape: true }],

    // do not require jsdoc
    // http://eslint.org/docs/rules/require-jsdoc
    'require-jsdoc': 'off',

    // require or disallow use of semicolons instead of ASI
    semi: ['error', 'always'],

    // enforce spacing before and after semicolons
    'semi-spacing': ['error', { before: false, after: true }],

    // requires object keys to be sorted
    'sort-keys': ['off', 'asc', { caseSensitive: false, natural: true }],

    // sort variables within the same declaration block
    'sort-vars': 'off',

    // require or disallow space before blocks
    'space-before-blocks': 'error',

    // require or disallow space before function opening parenthesis
    // http://eslint.org/docs/rules/space-before-function-paren
    'space-before-function-paren': ['error', { anonymous: 'always', named: 'never' }],

    // require or disallow spaces inside parentheses
    'space-in-parens': ['error', 'never'],

    // require spaces around operators
    'space-infix-ops': 'error',

    // Require or disallow spaces before/after unary operators
    // http://eslint.org/docs/rules/space-unary-ops
    'space-unary-ops': ['error', {
      words: true,
      nonwords: false,
      overrides: {
      },
    }],

    // require or disallow a space immediately following the // or /* in a comment
    'spaced-comment': ['error', 'always', {
      exceptions: ['-', '+'],
      markers: ['=', '!']           // space here to support sprockets directives
    }],

    // require or disallow the Unicode Byte Order Mark
    // http://eslint.org/docs/rules/unicode-bom
    'unicode-bom': ['error', 'never'],

    // require regex literals to be wrapped in parentheses
    'wrap-regex': 'off'
  }
};
<commit_msg>[eslint config] [base] [breaking] enable `no-tabs`<commit_after>module.exports = {
  rules: {
    // enforce spacing inside array brackets
    'array-bracket-spacing': ['error', 'never'],

    // enforce spacing inside single-line blocks
    // http://eslint.org/docs/rules/block-spacing
    'block-spacing': ['error', 'always'],

    // enforce one true brace style
    'brace-style': ['error', '1tbs', { allowSingleLine: true }],

    // require camel case names
    camelcase: ['error', { properties: 'never' }],

    // enforce spacing before and after comma
    'comma-spacing': ['error', { before: false, after: true }],

    // enforce one true comma style
    'comma-style': ['error', 'last'],

    // disallow padding inside computed properties
    'computed-property-spacing': ['error', 'never'],

    // enforces consistent naming when capturing the current execution context
    'consistent-this': 'off',

    // enforce newline at the end of file, with no multiple empty lines
    'eol-last': 'error',

    // enforce spacing between functions and their invocations
    // http://eslint.org/docs/rules/func-call-spacing
    'func-call-spacing': ['error', 'never'],

    // require function expressions to have a name
    'func-names': 'warn',

    // enforces use of function declarations or expressions
    'func-style': 'off',

    // Blacklist certain identifiers to prevent them being used
    // http://eslint.org/docs/rules/id-blacklist
    'id-blacklist': 'off',

    // this option enforces minimum and maximum identifier lengths
    // (variable names, property names etc.)
    'id-length': 'off',

    // require identifiers to match the provided regular expression
    'id-match': 'off',

    // this option sets a specific tab width for your code
    // http://eslint.org/docs/rules/indent
    indent: ['error', 2, { SwitchCase: 1, VariableDeclarator: 1, outerIIFEBody: 1 }],

    // specify whether double or single quotes should be used in JSX attributes
    // http://eslint.org/docs/rules/jsx-quotes
    'jsx-quotes': ['off', 'prefer-double'],

    // enforces spacing between keys and values in object literal properties
    'key-spacing': ['error', { beforeColon: false, afterColon: true }],

    // require a space before & after certain keywords
    'keyword-spacing': ['error', {
      before: true,
      after: true,
      overrides: {
        return: { after: true },
        throw: { after: true },
        case: { after: true }
      }
    }],

    // disallow mixed 'LF' and 'CRLF' as linebreaks
    // http://eslint.org/docs/rules/linebreak-style
    'linebreak-style': ['error', 'unix'],

    // enforces empty lines around comments
    'lines-around-comment': 'off',

    // specify the maximum depth that blocks can be nested
    'max-depth': ['off', 4],

    // specify the maximum length of a line in your program
    // http://eslint.org/docs/rules/max-len
    'max-len': ['error', 100, 2, {
      ignoreUrls: true,
      ignoreComments: false
    }],

    // specify the max number of lines in a file
    // http://eslint.org/docs/rules/max-lines
    'max-lines': ['off', {
      max: 300,
      skipBlankLines: true,
      skipComments: true
    }],

    // specify the maximum depth callbacks can be nested
    'max-nested-callbacks': 'off',

    // limits the number of parameters that can be used in the function declaration.
    'max-params': ['off', 3],

    // specify the maximum number of statement allowed in a function
    'max-statements': ['off', 10],

    // restrict the number of statements per line
    // http://eslint.org/docs/rules/max-statements-per-line
    'max-statements-per-line': ['off', { max: 1 }],

    // require multiline ternary
    // http://eslint.org/docs/rules/multiline-ternary
    'multiline-ternary': 'off',

    // require a capital letter for constructors
    'new-cap': ['error', { newIsCap: true }],

    // disallow the omission of parentheses when invoking a constructor with no arguments
    // http://eslint.org/docs/rules/new-parens
    'new-parens': 'error',

    // allow/disallow an empty newline after var statement
    'newline-after-var': 'off',

    // http://eslint.org/docs/rules/newline-before-return
    'newline-before-return': 'off',

    // enforces new line after each method call in the chain to make it
    // more readable and easy to maintain
    // http://eslint.org/docs/rules/newline-per-chained-call
    'newline-per-chained-call': ['error', { ignoreChainWithDepth: 4 }],

    // disallow use of the Array constructor
    'no-array-constructor': 'error',

    // disallow use of bitwise operators
    // http://eslint.org/docs/rules/no-bitwise
    // TODO: enable
    'no-bitwise': 'off',

    // disallow use of the continue statement
    // http://eslint.org/docs/rules/no-continue
    'no-continue': 'error',

    // disallow comments inline after code
    'no-inline-comments': 'off',

    // disallow if as the only statement in an else block
    // http://eslint.org/docs/rules/no-lonely-if
    'no-lonely-if': 'error',

    // disallow un-paren'd mixes of different operators
    // http://eslint.org/docs/rules/no-mixed-operators
    'no-mixed-operators': ['error', {
      groups: [
        ['+', '-', '*', '/', '%', '**'],
        ['&', '|', '^', '~', '<<', '>>', '>>>'],
        ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
        ['&&', '||'],
        ['in', 'instanceof']
      ],
      allowSamePrecedence: false
    }],

    // disallow mixed spaces and tabs for indentation
    'no-mixed-spaces-and-tabs': 'error',

    // disallow multiple empty lines and only one newline at the end
    'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],

    // disallow negated conditions
    // http://eslint.org/docs/rules/no-negated-condition
    'no-negated-condition': 'off',

    // disallow nested ternary expressions
    'no-nested-ternary': 'error',

    // disallow use of the Object constructor
    'no-new-object': 'error',

    // disallow use of unary operators, ++ and --
    'no-plusplus': 'off',

    // disallow certain syntax forms
    // http://eslint.org/docs/rules/no-restricted-syntax
    'no-restricted-syntax': [
      'error',
      'ForInStatement',
      'LabeledStatement',
      'WithStatement',
    ],

    // disallow space between function identifier and application
    'no-spaced-func': 'error',

    // disallow tab characters entirely
    'no-tabs': 'error',

    // disallow the use of ternary operators
    'no-ternary': 'off',

    // disallow trailing whitespace at the end of lines
    'no-trailing-spaces': 'error',

    // disallow dangling underscores in identifiers
    'no-underscore-dangle': ['error', { allowAfterThis: false }],

    // disallow the use of Boolean literals in conditional expressions
    // also, prefer `a || b` over `a ? a : b`
    // http://eslint.org/docs/rules/no-unneeded-ternary
    'no-unneeded-ternary': ['error', { defaultAssignment: false }],

    // disallow whitespace before properties
    // http://eslint.org/docs/rules/no-whitespace-before-property
    'no-whitespace-before-property': 'error',

    // require padding inside curly braces
    'object-curly-spacing': ['error', 'always'],

    // enforce line breaks between braces
    // http://eslint.org/docs/rules/object-curly-newline
    // TODO: enable once https://github.com/eslint/eslint/issues/6488 is resolved
    'object-curly-newline': ['off', {
      ObjectExpression: { minProperties: 0, multiline: true },
      ObjectPattern: { minProperties: 0, multiline: true }
    }],

    // enforce "same line" or "multiple line" on object properties.
    // http://eslint.org/docs/rules/object-property-newline
    'object-property-newline': ['error', {
      allowMultiplePropertiesPerLine: true,
    }],

    // allow just one var statement per function
    'one-var': ['error', 'never'],

    // require a newline around variable declaration
    // http://eslint.org/docs/rules/one-var-declaration-per-line
    'one-var-declaration-per-line': ['error', 'always'],

    // require assignment operator shorthand where possible or prohibit it entirely
    // http://eslint.org/docs/rules/operator-assignment
    'operator-assignment': ['error', 'always'],

    // enforce operators to be placed before or after line breaks
    'operator-linebreak': 'off',

    // enforce padding within blocks
    'padded-blocks': ['error', 'never'],

    // require quotes around object literal property names
    // http://eslint.org/docs/rules/quote-props.html
    'quote-props': ['error', 'as-needed', { keywords: false, unnecessary: true, numbers: false }],

    // specify whether double or single quotes should be used
    quotes: ['error', 'single', { avoidEscape: true }],

    // do not require jsdoc
    // http://eslint.org/docs/rules/require-jsdoc
    'require-jsdoc': 'off',

    // require or disallow use of semicolons instead of ASI
    semi: ['error', 'always'],

    // enforce spacing before and after semicolons
    'semi-spacing': ['error', { before: false, after: true }],

    // requires object keys to be sorted
    'sort-keys': ['off', 'asc', { caseSensitive: false, natural: true }],

    // sort variables within the same declaration block
    'sort-vars': 'off',

    // require or disallow space before blocks
    'space-before-blocks': 'error',

    // require or disallow space before function opening parenthesis
    // http://eslint.org/docs/rules/space-before-function-paren
    'space-before-function-paren': ['error', { anonymous: 'always', named: 'never' }],

    // require or disallow spaces inside parentheses
    'space-in-parens': ['error', 'never'],

    // require spaces around operators
    'space-infix-ops': 'error',

    // Require or disallow spaces before/after unary operators
    // http://eslint.org/docs/rules/space-unary-ops
    'space-unary-ops': ['error', {
      words: true,
      nonwords: false,
      overrides: {
      },
    }],

    // require or disallow a space immediately following the // or /* in a comment
    'spaced-comment': ['error', 'always', {
      exceptions: ['-', '+'],
      markers: ['=', '!']           // space here to support sprockets directives
    }],

    // require or disallow the Unicode Byte Order Mark
    // http://eslint.org/docs/rules/unicode-bom
    'unicode-bom': ['error', 'never'],

    // require regex literals to be wrapped in parentheses
    'wrap-regex': 'off'
  }
};
<|endoftext|>


<commit_before>import React from "react"

exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => {
  setHeadComponents([
    <link
      key={`gatsby-plugin-manifest-link`}
      rel="manifest"
      href="/manifest.json"
    />,
    <meta
      key={`gatsby-plugin-manifest-meta`}
      name="theme-color"
      content={pluginOptions.theme_color}
    />,
  ])
}
<commit_msg>Add pathPrefix support to manifest plugin (#3170)<commit_after>import React from "react"
import { withPrefix } from "gatsby-link"

exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => {
  setHeadComponents([
    <link
      key={`gatsby-plugin-manifest-link`}
      rel="manifest"
      href={withPrefix(`/manifest.json`)}
    />,
    <meta
      key={`gatsby-plugin-manifest-meta`}
      name="theme-color"
      content={pluginOptions.theme_color}
    />,
  ])
}
<|endoftext|>


<commit_before>"use strict";

const immediate = require("../setImmediate");
const StringWriter = require("./StringWriter");

/**
 * Simple wrapper that can be used to wrap a stream
 * to reduce the number of write calls. In Node.js world,
 * each stream.write() becomes a chunk. We can avoid overhead
 * by reducing the number of chunks by buffering the output.
 */
function BufferedWriter(wrappedStream) {
  StringWriter.call(this);
  this._wrapped = wrappedStream;
  this._scheduled = null;
}

BufferedWriter.prototype = Object.assign(
  {
    scheduleFlush() {
      if (!this._scheduled) {
        this._scheduled = immediate.___setImmediate(flush.bind(0, this));
      }
    },

    end: function () {
      flush(this);
      if (!this._wrapped.isTTY) {
        this._wrapped.end();
      }
    }
  },
  StringWriter.prototype
);

function flush(writer) {
  const contents = writer.toString();
  if (contents.length !== 0) {
    writer._wrapped.write(contents);
    writer.clear();
    if (writer._wrapped.flush) {
      writer._wrapped.flush();
    }
  }

  immediate.___clearImmediate(writer._scheduled);
  writer._scheduled = null;
}

module.exports = BufferedWriter;
<commit_msg>fix: setTimeout issue in webworkers<commit_after>"use strict";

const immediate = require("../setImmediate");
const setImmediate = immediate.___setImmediate;
const clearImmediate = immediate.___clearImmediate;
const StringWriter = require("./StringWriter");

/**
 * Simple wrapper that can be used to wrap a stream
 * to reduce the number of write calls. In Node.js world,
 * each stream.write() becomes a chunk. We can avoid overhead
 * by reducing the number of chunks by buffering the output.
 */
function BufferedWriter(wrappedStream) {
  StringWriter.call(this);
  this._wrapped = wrappedStream;
  this._scheduled = null;
}

BufferedWriter.prototype = Object.assign(
  {
    scheduleFlush() {
      if (!this._scheduled) {
        this._scheduled = setImmediate(flush.bind(0, this));
      }
    },

    end: function () {
      flush(this);
      if (!this._wrapped.isTTY) {
        this._wrapped.end();
      }
    }
  },
  StringWriter.prototype
);

function flush(writer) {
  const contents = writer.toString();
  if (contents.length !== 0) {
    writer._wrapped.write(contents);
    writer.clear();
    if (writer._wrapped.flush) {
      writer._wrapped.flush();
    }
  }

  clearImmediate(writer._scheduled);
  writer._scheduled = null;
}

module.exports = BufferedWriter;
<|endoftext|>


<commit_before>/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 */

// flowlint ambiguous-object-type:error

'use strict';

import type {HandlerProvider} from '../handlers/RelayDefaultHandlerProvider';
import type {Disposable} from '../util/RelayRuntimeTypes';
import type {GetDataID} from './RelayResponseNormalizer';
import type {
  MutationParameters,
  OperationDescriptor,
  OptimisticUpdate,
  PublishQueue,
  RecordSource,
  RelayResponsePayload,
  RequestDescriptor,
  SelectorData,
  SelectorStoreUpdater,
  SingularReaderSelector,
  Store,
  StoreUpdater,
} from './RelayStoreTypes';

const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
const RelayRecordSourceSelectorProxy = require('../mutations/RelayRecordSourceSelectorProxy');
const RelayReader = require('./RelayReader');
const RelayRecordSource = require('./RelayRecordSource');
const invariant = require('invariant');
const warning = require('warning');

type PendingCommit<TMutation: MutationParameters> =
  | PendingRelayPayload<TMutation>
  | PendingRecordSource
  | PendingUpdater;
type PendingRelayPayload<TMutation: MutationParameters> = {|
  +kind: 'payload',
  +operation: OperationDescriptor,
  +payload: RelayResponsePayload,
  +updater: ?SelectorStoreUpdater<TMutation['response']>,
|};
type PendingRecordSource = {|
  +kind: 'source',
  +source: RecordSource,
|};
type PendingUpdater = {|
  +kind: 'updater',
  +updater: StoreUpdater,
|};

const applyWithGuard =
  global?.ErrorUtils?.applyWithGuard ??
  ((callback, context, args, onError, name) => callback.apply(context, args));

/**
 * Coordinates the concurrent modification of a `Store` due to optimistic and
 * non-revertable client updates and server payloads:
 * - Applies optimistic updates.
 * - Reverts optimistic updates, rebasing any subsequent updates.
 * - Commits client updates (typically for client schema extensions).
 * - Commits server updates:
 *   - Normalizes query/mutation/subscription responses.
 *   - Executes handlers for "handle" fields.
 *   - Reverts and reapplies pending optimistic updates.
 */
class RelayPublishQueue implements PublishQueue {
  _store: Store;
  _handlerProvider: ?HandlerProvider;
  _getDataID: GetDataID;

  _hasStoreSnapshot: boolean;
  // True if the next `run()` should apply the backup and rerun all optimistic
  // updates performing a rebase.
  _pendingBackupRebase: boolean;
  // Payloads to apply or Sources to publish to the store with the next `run()`.
  // $FlowFixMe[unclear-type] See explanation below.
  _pendingData: Set<PendingCommit<any>>;
  // Optimistic updaters to add with the next `run()`.
  // $FlowFixMe[unclear-type] See explanation below.
  _pendingOptimisticUpdates: Set<OptimisticUpdate<any>>;
  // Optimistic updaters that are already added and might be rerun in order to
  // rebase them.
  // $FlowFixMe[unclear-type] See explanation below.
  _appliedOptimisticUpdates: Set<OptimisticUpdate<any>>;
  // For _pendingOptimisticUpdates, _appliedOptimisticUpdates, and _pendingData,
  // we want to parametrize by "any" since the type is effectively
  // "the union of all T's that PublishQueue's methods were called with".

  // Garbage collection hold, should rerun gc on dispose
  _gcHold: ?Disposable;
  _isRunning: ?boolean;

  constructor(
    store: Store,
    handlerProvider?: ?HandlerProvider,
    getDataID: GetDataID,
  ) {
    this._hasStoreSnapshot = false;
    this._handlerProvider = handlerProvider || null;
    this._pendingBackupRebase = false;
    this._pendingData = new Set();
    this._pendingOptimisticUpdates = new Set();
    this._store = store;
    this._appliedOptimisticUpdates = new Set();
    this._gcHold = null;
    this._getDataID = getDataID;
  }

  /**
   * Schedule applying an optimistic updates on the next `run()`.
   */
  applyUpdate<TMutation: MutationParameters>(
    updater: OptimisticUpdate<TMutation>,
  ): void {
    invariant(
      !this._appliedOptimisticUpdates.has(updater) &&
        !this._pendingOptimisticUpdates.has(updater),
      'RelayPublishQueue: Cannot apply the same update function more than ' +
        'once concurrently.',
    );
    this._pendingOptimisticUpdates.add(updater);
  }

  /**
   * Schedule reverting an optimistic updates on the next `run()`.
   */
  revertUpdate<TMutation: MutationParameters>(
    updater: OptimisticUpdate<TMutation>,
  ): void {
    if (this._pendingOptimisticUpdates.has(updater)) {
      // Reverted before it was applied
      this._pendingOptimisticUpdates.delete(updater);
    } else if (this._appliedOptimisticUpdates.has(updater)) {
      this._pendingBackupRebase = true;
      this._appliedOptimisticUpdates.delete(updater);
    }
  }

  /**
   * Schedule a revert of all optimistic updates on the next `run()`.
   */
  revertAll(): void {
    this._pendingBackupRebase = true;
    this._pendingOptimisticUpdates.clear();
    this._appliedOptimisticUpdates.clear();
  }

  /**
   * Schedule applying a payload to the store on the next `run()`.
   */
  commitPayload<TMutation: MutationParameters>(
    operation: OperationDescriptor,
    payload: RelayResponsePayload,
    updater?: ?SelectorStoreUpdater<TMutation['response']>,
  ): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({
      kind: 'payload',
      operation,
      payload,
      updater,
    });
  }

  /**
   * Schedule an updater to mutate the store on the next `run()` typically to
   * update client schema fields.
   */
  commitUpdate(updater: StoreUpdater): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({
      kind: 'updater',
      updater,
    });
  }

  /**
   * Schedule a publish to the store from the provided source on the next
   * `run()`. As an example, to update the store with substituted fields that
   * are missing in the store.
   */
  commitSource(source: RecordSource): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({kind: 'source', source});
  }

  /**
   * Execute all queued up operations from the other public methods.
   */
  run(
    sourceOperation?: OperationDescriptor,
  ): $ReadOnlyArray<RequestDescriptor> {
    const runWillClearGcHold =
      this._appliedOptimisticUpdates === 0 && !!this._gcHold;
    const runIsANoop =
      // this._pendingBackupRebase is true if an applied optimistic
      // update has potentially been reverted or if this._pendingData is not empty.
      !this._pendingBackupRebase &&
      this._pendingOptimisticUpdates.size === 0 &&
      !runWillClearGcHold;

    if (__DEV__) {
      warning(
        !runIsANoop,
        'RelayPublishQueue.run was called, but the call would have been a noop.',
      );
      warning(
        this._isRunning !== true,
        'A store update was detected within another store update. Please ' +
          "make sure new store updates aren't being executed within an " +
          'updater function for a different update.',
      );
      this._isRunning = true;
    }

    if (runIsANoop) {
      if (__DEV__) {
        this._isRunning = false;
      }
      return [];
    }

    if (this._pendingBackupRebase) {
      if (this._hasStoreSnapshot) {
        this._store.restore();
        this._hasStoreSnapshot = false;
      }
    }
    const invalidatedStore = this._commitData();
    if (
      this._pendingOptimisticUpdates.size ||
      (this._pendingBackupRebase && this._appliedOptimisticUpdates.size)
    ) {
      if (!this._hasStoreSnapshot) {
        this._store.snapshot();
        this._hasStoreSnapshot = true;
      }
      this._applyUpdates();
    }
    this._pendingBackupRebase = false;
    if (this._appliedOptimisticUpdates.size > 0) {
      if (!this._gcHold) {
        this._gcHold = this._store.holdGC();
      }
    } else {
      if (this._gcHold) {
        this._gcHold.dispose();
        this._gcHold = null;
      }
    }
    if (__DEV__) {
      this._isRunning = false;
    }
    return this._store.notify(sourceOperation, invalidatedStore);
  }

  /**
   * _publishSourceFromPayload will return a boolean indicating if the
   * publish caused the store to be globally invalidated.
   */
  _publishSourceFromPayload<TMutation: MutationParameters>(
    pendingPayload: PendingRelayPayload<TMutation>,
  ): boolean {
    const {payload, operation, updater} = pendingPayload;
    const {source, fieldPayloads} = payload;
    const mutator = new RelayRecordSourceMutator(
      this._store.getSource(),
      source,
    );
    const recordSourceProxy = new RelayRecordSourceProxy(
      mutator,
      this._getDataID,
    );
    if (fieldPayloads && fieldPayloads.length) {
      fieldPayloads.forEach(fieldPayload => {
        const handler =
          this._handlerProvider && this._handlerProvider(fieldPayload.handle);
        invariant(
          handler,
          'RelayModernEnvironment: Expected a handler to be provided for ' +
            'handle `%s`.',
          fieldPayload.handle,
        );
        handler.update(recordSourceProxy, fieldPayload);
      });
    }
    if (updater) {
      const selector = operation.fragment;
      invariant(
        selector != null,
        'RelayModernEnvironment: Expected a selector to be provided with updater function.',
      );
      const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
        mutator,
        recordSourceProxy,
        selector,
      );
      const selectorData = lookupSelector(source, selector);
      updater(recordSourceSelectorProxy, selectorData);
    }
    const idsMarkedForInvalidation =
      recordSourceProxy.getIDsMarkedForInvalidation();
    this._store.publish(source, idsMarkedForInvalidation);
    return recordSourceProxy.isStoreMarkedForInvalidation();
  }

  /**
   * _commitData will return a boolean indicating if any of
   * the pending commits caused the store to be globally invalidated.
   */
  _commitData(): boolean {
    if (!this._pendingData.size) {
      return false;
    }
    let invalidatedStore = false;
    this._pendingData.forEach(data => {
      if (data.kind === 'payload') {
        const payloadInvalidatedStore = this._publishSourceFromPayload(data);
        invalidatedStore = invalidatedStore || payloadInvalidatedStore;
      } else if (data.kind === 'source') {
        const source = data.source;
        this._store.publish(source);
      } else {
        const updater = data.updater;
        const sink = RelayRecordSource.create();
        const mutator = new RelayRecordSourceMutator(
          this._store.getSource(),
          sink,
        );
        const recordSourceProxy = new RelayRecordSourceProxy(
          mutator,
          this._getDataID,
        );
        applyWithGuard(
          updater,
          null,
          [recordSourceProxy],
          null,
          'RelayPublishQueue:commitData',
        );
        invalidatedStore =
          invalidatedStore || recordSourceProxy.isStoreMarkedForInvalidation();
        const idsMarkedForInvalidation =
          recordSourceProxy.getIDsMarkedForInvalidation();

        this._store.publish(sink, idsMarkedForInvalidation);
      }
    });
    this._pendingData.clear();
    return invalidatedStore;
  }

  /**
   * Note that unlike _commitData, _applyUpdates will NOT return a boolean
   * indicating if the store was globally invalidated, since invalidating the
   * store during an optimistic update is a no-op.
   */
  _applyUpdates(): void {
    const sink = RelayRecordSource.create();
    const mutator = new RelayRecordSourceMutator(this._store.getSource(), sink);
    const recordSourceProxy = new RelayRecordSourceProxy(
      mutator,
      this._getDataID,
      this._handlerProvider,
    );

    // $FlowFixMe[unclear-type] see explanation above.
    const processUpdate = (optimisticUpdate: OptimisticUpdate<any>) => {
      if (optimisticUpdate.storeUpdater) {
        const {storeUpdater} = optimisticUpdate;
        applyWithGuard(
          storeUpdater,
          null,
          [recordSourceProxy],
          null,
          'RelayPublishQueue:applyUpdates',
        );
      } else {
        const {operation, payload, updater} = optimisticUpdate;
        const {source, fieldPayloads} = payload;
        if (source) {
          recordSourceProxy.publishSource(source, fieldPayloads);
        }
        if (updater) {
          let selectorData;
          if (source) {
            selectorData = lookupSelector(source, operation.fragment);
          }
          const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
            mutator,
            recordSourceProxy,
            operation.fragment,
          );
          applyWithGuard(
            updater,
            null,
            [recordSourceSelectorProxy, selectorData],
            null,
            'RelayPublishQueue:applyUpdates',
          );
        }
      }
    };

    // rerun all updaters in case we are running a rebase
    if (this._pendingBackupRebase && this._appliedOptimisticUpdates.size) {
      this._appliedOptimisticUpdates.forEach(processUpdate);
    }

    // apply any new updaters
    if (this._pendingOptimisticUpdates.size) {
      this._pendingOptimisticUpdates.forEach(optimisticUpdate => {
        processUpdate(optimisticUpdate);
        this._appliedOptimisticUpdates.add(optimisticUpdate);
      });
      this._pendingOptimisticUpdates.clear();
    }

    this._store.publish(sink);
  }
}

function lookupSelector(
  source: RecordSource,
  selector: SingularReaderSelector,
): ?SelectorData {
  const selectorData = RelayReader.read(source, selector).data;
  if (__DEV__) {
    const deepFreeze = require('../util/deepFreeze');
    if (selectorData) {
      deepFreeze(selectorData);
    }
  }
  return selectorData;
}

module.exports = RelayPublishQueue;
<commit_msg>Check for existence of `global` before usage (#3646)<commit_after>/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 */

// flowlint ambiguous-object-type:error

'use strict';

import type {HandlerProvider} from '../handlers/RelayDefaultHandlerProvider';
import type {Disposable} from '../util/RelayRuntimeTypes';
import type {GetDataID} from './RelayResponseNormalizer';
import type {
  MutationParameters,
  OperationDescriptor,
  OptimisticUpdate,
  PublishQueue,
  RecordSource,
  RelayResponsePayload,
  RequestDescriptor,
  SelectorData,
  SelectorStoreUpdater,
  SingularReaderSelector,
  Store,
  StoreUpdater,
} from './RelayStoreTypes';

const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
const RelayRecordSourceSelectorProxy = require('../mutations/RelayRecordSourceSelectorProxy');
const RelayReader = require('./RelayReader');
const RelayRecordSource = require('./RelayRecordSource');
const invariant = require('invariant');
const warning = require('warning');

type PendingCommit<TMutation: MutationParameters> =
  | PendingRelayPayload<TMutation>
  | PendingRecordSource
  | PendingUpdater;
type PendingRelayPayload<TMutation: MutationParameters> = {|
  +kind: 'payload',
  +operation: OperationDescriptor,
  +payload: RelayResponsePayload,
  +updater: ?SelectorStoreUpdater<TMutation['response']>,
|};
type PendingRecordSource = {|
  +kind: 'source',
  +source: RecordSource,
|};
type PendingUpdater = {|
  +kind: 'updater',
  +updater: StoreUpdater,
|};

const _global: typeof global | $FlowFixMe =
  typeof global !== 'undefined'
    ? global
    : typeof window !== 'undefined'
    ? window
    : undefined;

const applyWithGuard =
  _global?.ErrorUtils?.applyWithGuard ??
  ((callback, context, args, onError, name) => callback.apply(context, args));

/**
 * Coordinates the concurrent modification of a `Store` due to optimistic and
 * non-revertable client updates and server payloads:
 * - Applies optimistic updates.
 * - Reverts optimistic updates, rebasing any subsequent updates.
 * - Commits client updates (typically for client schema extensions).
 * - Commits server updates:
 *   - Normalizes query/mutation/subscription responses.
 *   - Executes handlers for "handle" fields.
 *   - Reverts and reapplies pending optimistic updates.
 */
class RelayPublishQueue implements PublishQueue {
  _store: Store;
  _handlerProvider: ?HandlerProvider;
  _getDataID: GetDataID;

  _hasStoreSnapshot: boolean;
  // True if the next `run()` should apply the backup and rerun all optimistic
  // updates performing a rebase.
  _pendingBackupRebase: boolean;
  // Payloads to apply or Sources to publish to the store with the next `run()`.
  // $FlowFixMe[unclear-type] See explanation below.
  _pendingData: Set<PendingCommit<any>>;
  // Optimistic updaters to add with the next `run()`.
  // $FlowFixMe[unclear-type] See explanation below.
  _pendingOptimisticUpdates: Set<OptimisticUpdate<any>>;
  // Optimistic updaters that are already added and might be rerun in order to
  // rebase them.
  // $FlowFixMe[unclear-type] See explanation below.
  _appliedOptimisticUpdates: Set<OptimisticUpdate<any>>;
  // For _pendingOptimisticUpdates, _appliedOptimisticUpdates, and _pendingData,
  // we want to parametrize by "any" since the type is effectively
  // "the union of all T's that PublishQueue's methods were called with".

  // Garbage collection hold, should rerun gc on dispose
  _gcHold: ?Disposable;
  _isRunning: ?boolean;

  constructor(
    store: Store,
    handlerProvider?: ?HandlerProvider,
    getDataID: GetDataID,
  ) {
    this._hasStoreSnapshot = false;
    this._handlerProvider = handlerProvider || null;
    this._pendingBackupRebase = false;
    this._pendingData = new Set();
    this._pendingOptimisticUpdates = new Set();
    this._store = store;
    this._appliedOptimisticUpdates = new Set();
    this._gcHold = null;
    this._getDataID = getDataID;
  }

  /**
   * Schedule applying an optimistic updates on the next `run()`.
   */
  applyUpdate<TMutation: MutationParameters>(
    updater: OptimisticUpdate<TMutation>,
  ): void {
    invariant(
      !this._appliedOptimisticUpdates.has(updater) &&
        !this._pendingOptimisticUpdates.has(updater),
      'RelayPublishQueue: Cannot apply the same update function more than ' +
        'once concurrently.',
    );
    this._pendingOptimisticUpdates.add(updater);
  }

  /**
   * Schedule reverting an optimistic updates on the next `run()`.
   */
  revertUpdate<TMutation: MutationParameters>(
    updater: OptimisticUpdate<TMutation>,
  ): void {
    if (this._pendingOptimisticUpdates.has(updater)) {
      // Reverted before it was applied
      this._pendingOptimisticUpdates.delete(updater);
    } else if (this._appliedOptimisticUpdates.has(updater)) {
      this._pendingBackupRebase = true;
      this._appliedOptimisticUpdates.delete(updater);
    }
  }

  /**
   * Schedule a revert of all optimistic updates on the next `run()`.
   */
  revertAll(): void {
    this._pendingBackupRebase = true;
    this._pendingOptimisticUpdates.clear();
    this._appliedOptimisticUpdates.clear();
  }

  /**
   * Schedule applying a payload to the store on the next `run()`.
   */
  commitPayload<TMutation: MutationParameters>(
    operation: OperationDescriptor,
    payload: RelayResponsePayload,
    updater?: ?SelectorStoreUpdater<TMutation['response']>,
  ): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({
      kind: 'payload',
      operation,
      payload,
      updater,
    });
  }

  /**
   * Schedule an updater to mutate the store on the next `run()` typically to
   * update client schema fields.
   */
  commitUpdate(updater: StoreUpdater): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({
      kind: 'updater',
      updater,
    });
  }

  /**
   * Schedule a publish to the store from the provided source on the next
   * `run()`. As an example, to update the store with substituted fields that
   * are missing in the store.
   */
  commitSource(source: RecordSource): void {
    this._pendingBackupRebase = true;
    this._pendingData.add({kind: 'source', source});
  }

  /**
   * Execute all queued up operations from the other public methods.
   */
  run(
    sourceOperation?: OperationDescriptor,
  ): $ReadOnlyArray<RequestDescriptor> {
    const runWillClearGcHold =
      this._appliedOptimisticUpdates === 0 && !!this._gcHold;
    const runIsANoop =
      // this._pendingBackupRebase is true if an applied optimistic
      // update has potentially been reverted or if this._pendingData is not empty.
      !this._pendingBackupRebase &&
      this._pendingOptimisticUpdates.size === 0 &&
      !runWillClearGcHold;

    if (__DEV__) {
      warning(
        !runIsANoop,
        'RelayPublishQueue.run was called, but the call would have been a noop.',
      );
      warning(
        this._isRunning !== true,
        'A store update was detected within another store update. Please ' +
          "make sure new store updates aren't being executed within an " +
          'updater function for a different update.',
      );
      this._isRunning = true;
    }

    if (runIsANoop) {
      if (__DEV__) {
        this._isRunning = false;
      }
      return [];
    }

    if (this._pendingBackupRebase) {
      if (this._hasStoreSnapshot) {
        this._store.restore();
        this._hasStoreSnapshot = false;
      }
    }
    const invalidatedStore = this._commitData();
    if (
      this._pendingOptimisticUpdates.size ||
      (this._pendingBackupRebase && this._appliedOptimisticUpdates.size)
    ) {
      if (!this._hasStoreSnapshot) {
        this._store.snapshot();
        this._hasStoreSnapshot = true;
      }
      this._applyUpdates();
    }
    this._pendingBackupRebase = false;
    if (this._appliedOptimisticUpdates.size > 0) {
      if (!this._gcHold) {
        this._gcHold = this._store.holdGC();
      }
    } else {
      if (this._gcHold) {
        this._gcHold.dispose();
        this._gcHold = null;
      }
    }
    if (__DEV__) {
      this._isRunning = false;
    }
    return this._store.notify(sourceOperation, invalidatedStore);
  }

  /**
   * _publishSourceFromPayload will return a boolean indicating if the
   * publish caused the store to be globally invalidated.
   */
  _publishSourceFromPayload<TMutation: MutationParameters>(
    pendingPayload: PendingRelayPayload<TMutation>,
  ): boolean {
    const {payload, operation, updater} = pendingPayload;
    const {source, fieldPayloads} = payload;
    const mutator = new RelayRecordSourceMutator(
      this._store.getSource(),
      source,
    );
    const recordSourceProxy = new RelayRecordSourceProxy(
      mutator,
      this._getDataID,
    );
    if (fieldPayloads && fieldPayloads.length) {
      fieldPayloads.forEach(fieldPayload => {
        const handler =
          this._handlerProvider && this._handlerProvider(fieldPayload.handle);
        invariant(
          handler,
          'RelayModernEnvironment: Expected a handler to be provided for ' +
            'handle `%s`.',
          fieldPayload.handle,
        );
        handler.update(recordSourceProxy, fieldPayload);
      });
    }
    if (updater) {
      const selector = operation.fragment;
      invariant(
        selector != null,
        'RelayModernEnvironment: Expected a selector to be provided with updater function.',
      );
      const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
        mutator,
        recordSourceProxy,
        selector,
      );
      const selectorData = lookupSelector(source, selector);
      updater(recordSourceSelectorProxy, selectorData);
    }
    const idsMarkedForInvalidation =
      recordSourceProxy.getIDsMarkedForInvalidation();
    this._store.publish(source, idsMarkedForInvalidation);
    return recordSourceProxy.isStoreMarkedForInvalidation();
  }

  /**
   * _commitData will return a boolean indicating if any of
   * the pending commits caused the store to be globally invalidated.
   */
  _commitData(): boolean {
    if (!this._pendingData.size) {
      return false;
    }
    let invalidatedStore = false;
    this._pendingData.forEach(data => {
      if (data.kind === 'payload') {
        const payloadInvalidatedStore = this._publishSourceFromPayload(data);
        invalidatedStore = invalidatedStore || payloadInvalidatedStore;
      } else if (data.kind === 'source') {
        const source = data.source;
        this._store.publish(source);
      } else {
        const updater = data.updater;
        const sink = RelayRecordSource.create();
        const mutator = new RelayRecordSourceMutator(
          this._store.getSource(),
          sink,
        );
        const recordSourceProxy = new RelayRecordSourceProxy(
          mutator,
          this._getDataID,
        );
        applyWithGuard(
          updater,
          null,
          [recordSourceProxy],
          null,
          'RelayPublishQueue:commitData',
        );
        invalidatedStore =
          invalidatedStore || recordSourceProxy.isStoreMarkedForInvalidation();
        const idsMarkedForInvalidation =
          recordSourceProxy.getIDsMarkedForInvalidation();

        this._store.publish(sink, idsMarkedForInvalidation);
      }
    });
    this._pendingData.clear();
    return invalidatedStore;
  }

  /**
   * Note that unlike _commitData, _applyUpdates will NOT return a boolean
   * indicating if the store was globally invalidated, since invalidating the
   * store during an optimistic update is a no-op.
   */
  _applyUpdates(): void {
    const sink = RelayRecordSource.create();
    const mutator = new RelayRecordSourceMutator(this._store.getSource(), sink);
    const recordSourceProxy = new RelayRecordSourceProxy(
      mutator,
      this._getDataID,
      this._handlerProvider,
    );

    // $FlowFixMe[unclear-type] see explanation above.
    const processUpdate = (optimisticUpdate: OptimisticUpdate<any>) => {
      if (optimisticUpdate.storeUpdater) {
        const {storeUpdater} = optimisticUpdate;
        applyWithGuard(
          storeUpdater,
          null,
          [recordSourceProxy],
          null,
          'RelayPublishQueue:applyUpdates',
        );
      } else {
        const {operation, payload, updater} = optimisticUpdate;
        const {source, fieldPayloads} = payload;
        if (source) {
          recordSourceProxy.publishSource(source, fieldPayloads);
        }
        if (updater) {
          let selectorData;
          if (source) {
            selectorData = lookupSelector(source, operation.fragment);
          }
          const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
            mutator,
            recordSourceProxy,
            operation.fragment,
          );
          applyWithGuard(
            updater,
            null,
            [recordSourceSelectorProxy, selectorData],
            null,
            'RelayPublishQueue:applyUpdates',
          );
        }
      }
    };

    // rerun all updaters in case we are running a rebase
    if (this._pendingBackupRebase && this._appliedOptimisticUpdates.size) {
      this._appliedOptimisticUpdates.forEach(processUpdate);
    }

    // apply any new updaters
    if (this._pendingOptimisticUpdates.size) {
      this._pendingOptimisticUpdates.forEach(optimisticUpdate => {
        processUpdate(optimisticUpdate);
        this._appliedOptimisticUpdates.add(optimisticUpdate);
      });
      this._pendingOptimisticUpdates.clear();
    }

    this._store.publish(sink);
  }
}

function lookupSelector(
  source: RecordSource,
  selector: SingularReaderSelector,
): ?SelectorData {
  const selectorData = RelayReader.read(source, selector).data;
  if (__DEV__) {
    const deepFreeze = require('../util/deepFreeze');
    if (selectorData) {
      deepFreeze(selectorData);
    }
  }
  return selectorData;
}

module.exports = RelayPublishQueue;
<|endoftext|>


<commit_before>/* globals RocketChat */
import _ from 'underscore';

import { UiTextContext } from 'meteor/rocketchat:lib';

Template.roomList.helpers({
	rooms() {
		/*
			modes:
				sortby activity/alphabetical
				merge channels into one list
				show favorites
				show unread
		*/

		if (this.anonymous) {
			return RocketChat.models.Rooms.find({t: 'c'}, {sort: {name: 1}});
		}
		const user = Meteor.user();
		const sortBy = RocketChat.getUserPreference(user, 'sidebarSortby') || 'alphabetical';
		const query = {
			open: true
		};

		const sort = {};

		if (sortBy === 'activity') {
			sort.t = 1;
		} else { // alphabetical
			sort[this.identifier === 'd' && RocketChat.settings.get('UI_Use_Real_Name') ? 'fname' : 'name'] = /descending/.test(sortBy) ? -1 : 1;
		}

		if (this.identifier === 'unread') {
			query.alert = true;
			query.hideUnreadStatus = {$ne: true};
			return ChatSubscription.find(query, {sort});
		}

		const favoritesEnabled = !!(RocketChat.settings.get('Favorite_Rooms') && RocketChat.getUserPreference(user, 'sidebarShowFavorites'));

		if (this.identifier === 'f') {
			query.f = favoritesEnabled;
		} else {
			let types = [this.identifier];

			if (this.identifier === 'merged') {
				types = ['c', 'p', 'd'];
			}

			if (this.identifier === 'unread' || this.identifier === 'tokens') {
				types = ['c', 'p'];
			}

			if (['c', 'p'].includes(this.identifier)) {
				query.tokens = { $exists: false };
			} else if (this.identifier === 'tokens' && user && user.services && user.services.tokenpass) {
				query.tokens = { $exists: true };
			}

			if (RocketChat.getUserPreference(user, 'roomsListExhibitionMode') === 'unread') {

				query.$or = [
					{alert: {$ne: true}},
					{hideUnreadStatus: true}
				];
			}
			query.t = {$in: types};
			if (favoritesEnabled) {
				query.f = {$ne: favoritesEnabled};
			}
		}

		if (sortBy === 'activity') {
			const list = ChatSubscription.find(query, {sort: {rid : 1}}).fetch();
			const ids = list.map(sub => sub.rid);
			const rooms = RocketChat.models.Rooms.find({
				_id: { $in : ids}
			},
			{
				sort : {
					_id: 1
				},
				fields: {_updatedAt: 1}
			}).fetch();

			return _.sortBy(list.map((sub, i) => {
				const lm = rooms[i]._updatedAt;
				return {
					lm: lm && lm.toISOString(),
					...sub
				};
			}), 'lm').reverse();
		}
		return ChatSubscription.find(query, {sort});
	},

	isLivechat() {
		return this.identifier === 'l';
	},

	shouldAppear(group, rooms) {
		/*
		if is a normal group ('channel' 'private' 'direct')
		or is favorite and has one room
		or is unread and has one room
		*/

		return !['unread', 'f'].includes(group.identifier) || (rooms.length || rooms.count && rooms.count());
	},

	roomType(room) {
		if (room.header || room.identifier) {
			return `type-${ room.header || room.identifier }`;
		}
	},

	noSubscriptionText() {
		const instance = Template.instance();
		const roomType = (instance.data.header || instance.data.identifier);
		return RocketChat.roomTypes.roomTypes[roomType].getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet';
	},

	showRoomCounter() {
		return RocketChat.getUserPreference(Meteor.user(), 'roomCounterSidebar');
	}
});

// Template.roomList.onRendered(function() {
// 	$(this.firstNode.parentElement).perfectScrollbar();
// });

Template.roomList.events({
	'click .more'(e, t) {
		if (t.data.identifier === 'p') {
			SideNav.setFlex('listPrivateGroupsFlex');
		} else if (t.data.isCombined) {
			SideNav.setFlex('listCombinedFlex');
		} else {
			SideNav.setFlex('listChannelsFlex');
		}

		return SideNav.openFlex();
	}
});
<commit_msg>Update roomList.js<commit_after>/* globals RocketChat */
import _ from 'underscore';

import { UiTextContext } from 'meteor/rocketchat:lib';

Template.roomList.helpers({
	rooms() {
		/*
			modes:
				sortby activity/alphabetical
				merge channels into one list
				show favorites
				show unread
		*/

		if (this.anonymous) {
			return RocketChat.models.Rooms.find({t: 'c'}, {sort: {name: 1}});
		}
		const user = Meteor.user();
		const sortBy = RocketChat.getUserPreference(user, 'sidebarSortby') || 'alphabetical';
		const query = {
			open: true
		};

		const sort = {};

		if (sortBy === 'activity') {
			sort.t = 1;
		} else { // alphabetical
			sort[this.identifier === 'd' && RocketChat.settings.get('UI_Use_Real_Name') ? 'fname' : 'name'] = /descending/.test(sortBy) ? -1 : 1;
		}

		if (this.identifier === 'unread') {
			query.alert = true;
			query.hideUnreadStatus = {$ne: true};
			return ChatSubscription.find(query, {sort});
		}

		const favoritesEnabled = !!(RocketChat.settings.get('Favorite_Rooms') && RocketChat.getUserPreference(user, 'sidebarShowFavorites'));

		if (this.identifier === 'f') {
			query.f = favoritesEnabled;
		} else {
			let types = [this.identifier];

			if (this.identifier === 'merged') {
				types = ['c', 'p', 'd'];
			}

			if (this.identifier === 'unread' || this.identifier === 'tokens') {
				types = ['c', 'p'];
			}

			if (['c', 'p'].includes(this.identifier)) {
				query.tokens = { $exists: false };
			} else if (this.identifier === 'tokens' && user && user.services && user.services.tokenpass) {
				query.tokens = { $exists: true };
			}

			if (RocketChat.getUserPreference(user, 'roomsListExhibitionMode') === 'unread') {

				query.$or = [
					{alert: {$ne: true}},
					{hideUnreadStatus: true}
				];
			}
			query.t = {$in: types};
			if (favoritesEnabled) {
				query.f = {$ne: favoritesEnabled};
			}
		}

		if (sortBy === 'activity') {
			const list = ChatSubscription.find(query, {sort: {rid : 1}}).fetch();
			const ids = list.map(sub => sub.rid);
			const rooms = RocketChat.models.Rooms.find({
				_id: { $in : ids}
			},
			{
				sort : {
					_id: 1
				},
				fields: {_updatedAt: 1}
			}).fetch();

			return _.sortBy(list.map((sub, i) => {
				const lm = rooms[i]._updatedAt;
				return {
					...sub,
					lm: lm && lm.toISOString()
				};
			}), 'lm').reverse();
		}
		return ChatSubscription.find(query, {sort});
	},

	isLivechat() {
		return this.identifier === 'l';
	},

	shouldAppear(group, rooms) {
		/*
		if is a normal group ('channel' 'private' 'direct')
		or is favorite and has one room
		or is unread and has one room
		*/

		return !['unread', 'f'].includes(group.identifier) || (rooms.length || rooms.count && rooms.count());
	},

	roomType(room) {
		if (room.header || room.identifier) {
			return `type-${ room.header || room.identifier }`;
		}
	},

	noSubscriptionText() {
		const instance = Template.instance();
		const roomType = (instance.data.header || instance.data.identifier);
		return RocketChat.roomTypes.roomTypes[roomType].getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet';
	},

	showRoomCounter() {
		return RocketChat.getUserPreference(Meteor.user(), 'roomCounterSidebar');
	}
});

// Template.roomList.onRendered(function() {
// 	$(this.firstNode.parentElement).perfectScrollbar();
// });

Template.roomList.events({
	'click .more'(e, t) {
		if (t.data.identifier === 'p') {
			SideNav.setFlex('listPrivateGroupsFlex');
		} else if (t.data.isCombined) {
			SideNav.setFlex('listCombinedFlex');
		} else {
			SideNav.setFlex('listChannelsFlex');
		}

		return SideNav.openFlex();
	}
});
<|endoftext|>


<commit_before>const {
  FILTER_ITEMS,
  FILTER_KEYS,
  DATA_HUB_ACTIVITY,
  EXTERNAL_ACTIVITY,
  DATA_HUB_AND_EXTERNAL_ACTIVITY,
} = require('./constants')

const { getGlobalUltimateHierarchy } = require('../../repos')
const urls = require('../../../../lib/urls')
const { fetchActivityFeed } = require('./repos')
const config = require('../../../../config')

const {
  myActivityQuery,
  dataHubActivityQuery,
  externalActivityQuery,
  maxemailCampaignQuery,
  maxemailEmailSentQuery,
} = require('./es-queries')

async function renderActivityFeed(req, res, next) {
  const { company, dnbHierarchyCount, dnbRelatedCompaniesCount } = res.locals

  res.locals.title = `Activities - ${company.name} - Companies`

  const breadcrumbs = [
    { link: urls.dashboard(), text: 'Home' },
    {
      link: urls.companies.index(),
      text: 'Companies',
    },
    { link: urls.companies.detail(company.id), text: company.name },
    { text: 'Activity Feed' },
  ]

  try {
    const contentProps = company.archived
      ? {
          company,
          breadcrumbs,
          flashMessages: res.locals.getMessages(),
        }
      : {
          company,
          breadcrumbs,
          flashMessages: res.locals.getMessages(),
          activityTypeFilter: FILTER_KEYS.dataHubActivity,
          activityTypeFilters: FILTER_ITEMS,
          isGlobalUltimate: company.is_global_ultimate,
          dnbHierarchyCount,
          dnbRelatedCompaniesCount,
          showMatchingPrompt:
            !company.duns_number && !company.pending_dnb_investigation,
        }

    const props = {
      ...contentProps,
      apiEndpoint: urls.companies.activity.data(company.id),
    }

    res.render('companies/apps/activity-feed/views/client-container', { props })
  } catch (error) {
    next(error)
  }
}

function getQueries(options) {
  return {
    [FILTER_KEYS.myActivity]: myActivityQuery({
      ...options,
      types: DATA_HUB_ACTIVITY,
    }),
    [FILTER_KEYS.dataHubActivity]: dataHubActivityQuery({
      ...options,
      types: DATA_HUB_ACTIVITY,
    }),
    [FILTER_KEYS.externalActivity]: externalActivityQuery({
      ...options,
      types: EXTERNAL_ACTIVITY,
    }),
    [FILTER_KEYS.dataHubAndExternalActivity]: externalActivityQuery({
      ...options,
      types: DATA_HUB_AND_EXTERNAL_ACTIVITY,
    }),
  }
}

function isExternalFilter(activityTypeFilter) {
  return (
    activityTypeFilter === FILTER_KEYS.externalActivity ||
    activityTypeFilter === FILTER_KEYS.dataHubAndExternalActivity
  )
}

function getContactFromEmailAddress(emailAddress, contacts) {
  const contact = contacts.find((contact) => contact.email === emailAddress)
  return contact
    ? {
        ...contact,
        url: urls.contacts.details(contact.id),
      }
    : null
}

async function getMaxemailCampaigns(req, next, contacts) {
  try {
    // Fetch Maxemail campaigns
    const campaignQuery = maxemailCampaignQuery()
    const campaignsResults = await fetchActivityFeed(req, campaignQuery)
    const campaignActivities = campaignsResults.hits.hits.map(
      (hit) => hit._source
    )

    // Fetch all Maxemail emails sent to Data Hub company contacts as part of a campaign
    const emailSentQuery = maxemailEmailSentQuery(contacts)
    const emailSentResults = await fetchActivityFeed(req, emailSentQuery)
    const emailSentActivities = emailSentResults.hits.hits.map(
      (hit) => hit._source
    )

    // Group Data Hub company contacts to a campaign
    campaignActivities.forEach((campaign) => {
      campaign.object.contacts = []
      const campaignId = campaign.object['dit:maxemail:Campaign:id']
      emailSentActivities.forEach((emailSent) => {
        const sentEmailCampaignId =
          emailSent.object.attributedTo['dit:maxemail:Campaign:id']
        if (campaignId === sentEmailCampaignId) {
          const emailAddress = emailSent.object['dit:emailAddress']
          const contact = getContactFromEmailAddress(emailAddress, contacts)
          if (contact) {
            campaign.object.contacts.push(contact)
          }
        }
      })
    })

    // We only want campaigns that have at least one contact
    return campaignActivities.filter(
      (campaign) => campaign.object.contacts.length
    )
  } catch (error) {
    next(error)
  }
}

async function fetchActivityFeedHandler(req, res, next) {
  try {
    const { company, user } = res.locals
    const {
      from = 0,
      size = config.activityFeed.paginationSize,
      activityTypeFilter = FILTER_KEYS.dataHubActivity,
      showDnbHierarchy = false,
    } = req.query

    let dnbHierarchyIds = []
    if (company.is_global_ultimate && showDnbHierarchy) {
      const { results } = await getGlobalUltimateHierarchy(
        req,
        company.global_ultimate_duns_number
      )
      dnbHierarchyIds = results
        .filter((company) => !company.is_global_ultimate)
        .map((company) => company.id)
    }

    const queries = getQueries({
      from,
      size,
      companyIds: [company.id, ...dnbHierarchyIds],
      contacts: company.contacts,
      user,
    })

    const results = await fetchActivityFeed(
      req,
      queries[activityTypeFilter] || queries[FILTER_KEYS.dataHubActivity]
    )

    let activities = results.hits.hits.map((hit) => hit._source)
    let total = results.hits.total.value

    if (isExternalFilter(activityTypeFilter)) {
      const campaigns = await getMaxemailCampaigns(req, next, company.contacts)
      activities = [...activities, ...campaigns]
      total += campaigns.length
    }

    res.json({
      total,
      activities,
    })
  } catch (error) {
    next(error)
  }
}

module.exports = {
  renderActivityFeed,
  fetchActivityFeedHandler,
}
<commit_msg>DET-85 Add a controller function for activities by contact<commit_after>const {
  FILTER_ITEMS,
  FILTER_KEYS,
  DATA_HUB_ACTIVITY,
  EXTERNAL_ACTIVITY,
  DATA_HUB_AND_EXTERNAL_ACTIVITY,
} = require('./constants')

const { getGlobalUltimateHierarchy } = require('../../repos')
const urls = require('../../../../lib/urls')
const { fetchActivityFeed } = require('./repos')
const config = require('../../../../config')

const {
  myActivityQuery,
  dataHubActivityQuery,
  externalActivityQuery,
  maxemailCampaignQuery,
  maxemailEmailSentQuery,
} = require('./es-queries')
const { contactActivityQuery } = require('./es-queries/contact-activity-query')

async function renderActivityFeed(req, res, next) {
  const { company, dnbHierarchyCount, dnbRelatedCompaniesCount } = res.locals

  res.locals.title = `Activities - ${company.name} - Companies`

  const breadcrumbs = [
    { link: urls.dashboard(), text: 'Home' },
    {
      link: urls.companies.index(),
      text: 'Companies',
    },
    { link: urls.companies.detail(company.id), text: company.name },
    { text: 'Activity Feed' },
  ]

  try {
    const contentProps = company.archived
      ? {
          company,
          breadcrumbs,
          flashMessages: res.locals.getMessages(),
        }
      : {
          company,
          breadcrumbs,
          flashMessages: res.locals.getMessages(),
          activityTypeFilter: FILTER_KEYS.dataHubActivity,
          activityTypeFilters: FILTER_ITEMS,
          isGlobalUltimate: company.is_global_ultimate,
          dnbHierarchyCount,
          dnbRelatedCompaniesCount,
          showMatchingPrompt:
            !company.duns_number && !company.pending_dnb_investigation,
        }

    const props = {
      ...contentProps,
      apiEndpoint: urls.companies.activity.data(company.id),
    }

    res.render('companies/apps/activity-feed/views/client-container', { props })
  } catch (error) {
    next(error)
  }
}

function getQueries(options) {
  return {
    [FILTER_KEYS.myActivity]: myActivityQuery({
      ...options,
      types: DATA_HUB_ACTIVITY,
    }),
    [FILTER_KEYS.dataHubActivity]: dataHubActivityQuery({
      ...options,
      types: DATA_HUB_ACTIVITY,
    }),
    [FILTER_KEYS.externalActivity]: externalActivityQuery({
      ...options,
      types: EXTERNAL_ACTIVITY,
    }),
    [FILTER_KEYS.dataHubAndExternalActivity]: externalActivityQuery({
      ...options,
      types: DATA_HUB_AND_EXTERNAL_ACTIVITY,
    }),
  }
}

function isExternalFilter(activityTypeFilter) {
  return (
    activityTypeFilter === FILTER_KEYS.externalActivity ||
    activityTypeFilter === FILTER_KEYS.dataHubAndExternalActivity
  )
}

function getContactFromEmailAddress(emailAddress, contacts) {
  const contact = contacts.find((contact) => contact.email === emailAddress)
  return contact
    ? {
        ...contact,
        url: urls.contacts.details(contact.id),
      }
    : null
}

async function getMaxemailCampaigns(req, next, contacts) {
  try {
    // Fetch Maxemail campaigns
    const campaignQuery = maxemailCampaignQuery()
    const campaignsResults = await fetchActivityFeed(req, campaignQuery)
    const campaignActivities = campaignsResults.hits.hits.map(
      (hit) => hit._source
    )

    // Fetch all Maxemail emails sent to Data Hub company contacts as part of a campaign
    const emailSentQuery = maxemailEmailSentQuery(contacts)
    const emailSentResults = await fetchActivityFeed(req, emailSentQuery)
    const emailSentActivities = emailSentResults.hits.hits.map(
      (hit) => hit._source
    )

    // Group Data Hub company contacts to a campaign
    campaignActivities.forEach((campaign) => {
      campaign.object.contacts = []
      const campaignId = campaign.object['dit:maxemail:Campaign:id']
      emailSentActivities.forEach((emailSent) => {
        const sentEmailCampaignId =
          emailSent.object.attributedTo['dit:maxemail:Campaign:id']
        if (campaignId === sentEmailCampaignId) {
          const emailAddress = emailSent.object['dit:emailAddress']
          const contact = getContactFromEmailAddress(emailAddress, contacts)
          if (contact) {
            campaign.object.contacts.push(contact)
          }
        }
      })
    })

    // We only want campaigns that have at least one contact
    return campaignActivities.filter(
      (campaign) => campaign.object.contacts.length
    )
  } catch (error) {
    next(error)
  }
}

async function fetchActivitiesForContact(req, res, next) {
  const { contact } = res.locals
  const results = await fetchActivityFeed(
    req,
    contactActivityQuery(
      contact.email,
      contact.id,
      DATA_HUB_AND_EXTERNAL_ACTIVITY
    )
  ).catch((error) => {
    next(error)
  })

  let activities = results.hits.hits.map((hit) => hit._source)

  res.json({ activities })
}

async function fetchActivityFeedHandler(req, res, next) {
  try {
    const { company, user } = res.locals
    const {
      from = 0,
      size = config.activityFeed.paginationSize,
      activityTypeFilter = FILTER_KEYS.dataHubActivity,
      showDnbHierarchy = false,
    } = req.query

    let dnbHierarchyIds = []
    if (company.is_global_ultimate && showDnbHierarchy) {
      const { results } = await getGlobalUltimateHierarchy(
        req,
        company.global_ultimate_duns_number
      )
      dnbHierarchyIds = results
        .filter((company) => !company.is_global_ultimate)
        .map((company) => company.id)
    }

    const queries = getQueries({
      from,
      size,
      companyIds: [company.id, ...dnbHierarchyIds],
      contacts: company.contacts,
      user,
    })

    const results = await fetchActivityFeed(
      req,
      queries[activityTypeFilter] || queries[FILTER_KEYS.dataHubActivity]
    )

    let activities = results.hits.hits.map((hit) => hit._source)
    let total = results.hits.total.value

    if (isExternalFilter(activityTypeFilter)) {
      const campaigns = await getMaxemailCampaigns(req, next, company.contacts)
      activities = [...activities, ...campaigns]
      total += campaigns.length
    }

    res.json({
      total,
      activities,
    })
  } catch (error) {
    next(error)
  }
}

module.exports = {
  renderActivityFeed,
  fetchActivityFeedHandler,
  fetchActivitiesForContact,
}
<|endoftext|>


<commit_before>const assert = require("assert");
const CommandRunner = require("../commandrunner");
const MemoryLogger = require("../memorylogger");
const sandbox = require("../sandbox");
const path = require("path");

describe("truffle build", () => {
  const logger = new MemoryLogger();
  let config, project;

  describe("when there is no build script in config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithoutBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });

    it("should not error", done => {
      CommandRunner.run("build", config, error => {
        assert(typeof error === "undefined");
        done();
      });
    }).timeout(20000);

    it("whines about having no build config", done => {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(output.includes("No build configuration found."));
        done();
      });
    }).timeout(10000);
  });

  describe("when there is a proper build config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });
    it("runs the build script", function(done) {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(output.includes("'this is the build script'"));
        done();
      });
    });
  });

  describe("when there is an object in the build config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithObjectInBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });
    it("tells the user it shouldn't use an object", function(done) {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(
          output.includes(
            "Build configuration can no longer be specified as an object."
          )
        );
        done();
      });
    });
  });
});
<commit_msg>up timeout for test<commit_after>const assert = require("assert");
const CommandRunner = require("../commandrunner");
const MemoryLogger = require("../memorylogger");
const sandbox = require("../sandbox");
const path = require("path");

describe("truffle build", () => {
  const logger = new MemoryLogger();
  let config, project;

  describe("when there is no build script in config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithoutBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });

    it("should not error", done => {
      CommandRunner.run("build", config, error => {
        assert(typeof error === "undefined");
        done();
      });
    }).timeout(30000);

    it("whines about having no build config", done => {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(output.includes("No build configuration found."));
        done();
      });
    }).timeout(10000);
  });

  describe("when there is a proper build config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });
    it("runs the build script", function(done) {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(output.includes("'this is the build script'"));
        done();
      });
    });
  });

  describe("when there is an object in the build config", () => {
    beforeEach("set up sandbox", function() {
      this.timeout(10000);
      project = path.join(
        __dirname,
        "../../sources/build/projectWithObjectInBuildScript"
      );
      return sandbox.create(project).then(conf => {
        config = conf;
        config.logger = logger;
      });
    });
    it("tells the user it shouldn't use an object", function(done) {
      CommandRunner.run("build", config, () => {
        const output = logger.contents();
        assert(
          output.includes(
            "Build configuration can no longer be specified as an object."
          )
        );
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>describe('ima.page.renderer.ServerPageRenderer', function() {

	var param1 = 'param1';
	var param2 = 'param2';
	var params = {
		param1: param1,
		param2: Promise.resolve(param2)
	};

	var controller = new ns.ima.controller.Controller();
	controller.getMetaManager = function() {};
	var view = function() {};
	var expressResponse = {
		status: function() {},
		send: function() {}
	};

	var pageRenderer = null;
	var $Helper = oc.get('$Helper');
	var rendererFactory = oc.get('$PageRendererFactory');
	var ReactDOMServer = {
		renderToString: function() {},
		renderToStaticMarkup: function() {}
	};
	var settings = oc.get('$Settings');
	var response = oc.get('$Response');
	var cache = oc.get('$Cache');
	var routeOptions = {
		onlyUpdate: false,
		autoScroll: false,
		allowSPA: false,
		documentView: null
	};

	beforeEach(function() {
		response.init(expressResponse);
		pageRenderer = oc.create('ima.page.renderer.ServerPageRenderer', [rendererFactory, $Helper, ReactDOMServer, settings, response, cache, oc]);
	});

	it('should be wrap each key to promise', function() {
		spyOn(Promise, 'resolve')
			.and
			.callThrough();

		pageRenderer._wrapEachKeyToPromise(params);

		expect(Promise.resolve).toHaveBeenCalledWith(param1);
		expect(Promise.resolve.calls.count()).toEqual(1);
	});

	describe('update method', function() {

		it('should reject promise with error', function(done) {
			spyOn(pageRenderer, 'mount')
				.and
				.stub();

			pageRenderer
				.update(controller, params)
				.catch(function(error) {
					expect(error instanceof ns.ima.error.GenericError).toEqual(true);
					done();
				});
		});

	});

	describe('mount method', function() {

		var loadedPageState = {
			param1: 'param1',
			param2: Promise.resolve('param2')
		};

		it('should return already sent data to the client', function(done) {
			var responseParams = {
				content: '',
				status: 200
			};

			spyOn(response, 'isResponseSent')
				.and
				.returnValue(true);
			spyOn(response, 'getResponseParams')
				.and
				.returnValue(responseParams);

			pageRenderer
				.mount(controller, view, loadedPageState, routeOptions)
				.then(function(page) {
					expect(page).toEqual(responseParams);
					done();
				});
		});

		it('should call _renderPage method', function(done) {
			spyOn(pageRenderer, '_renderPage')
				.and
				.stub();

			pageRenderer
				.mount(controller, view, loadedPageState, routeOptions)
				.then(function(page) {
					expect(pageRenderer._renderPage).toHaveBeenCalled();
					done();
				});
		});

	});

	describe('_renderPage method', function() {
		var fetchedResource = {
			resource: 'json'
		};

		it('should return already sent data to client', function() {
			var responseParams = {
				content: '',
				status: 200
			};

			spyOn(response, 'isResponseSent')
				.and
				.returnValue(true);
			spyOn(response, 'getResponseParams')
				.and
				.returnValue(responseParams);

			expect(pageRenderer._renderPage(controller, view, fetchedResource)).toEqual(responseParams);
		});

		describe('render new page', function() {

			var responseParams = { status: 200, content: '' };
			var pageRenderResponse = null;

			beforeEach(function() {
				spyOn(controller, 'setState')
					.and
					.stub();
				spyOn(controller, 'setMetaParams')
					.and
					.stub();
				spyOn(controller, 'getHttpStatus')
					.and
					.stub();
				spyOn(pageRenderer, '_renderPageContentToString')
					.and
					.stub();
				spyOn(response, 'status')
					.and
					.returnValue(response);
				spyOn(response, 'send')
					.and
					.returnValue(response);
				spyOn(response, 'getResponseParams')
					.and
					.returnValue(responseParams);

				pageRenderResponse = pageRenderer._renderPage(controller, view, fetchedResource, routeOptions);
			});

			it('should set controller state', function() {
				expect(controller.setState).toHaveBeenCalledWith(fetchedResource);
			});

			it('should set meta params', function() {
				expect(controller.setMetaParams).toHaveBeenCalledWith(fetchedResource);
			});

			it('should send response for request', function() {
				expect(response.status).toHaveBeenCalled();
				expect(response.send).toHaveBeenCalled();
				expect(controller.getHttpStatus).toHaveBeenCalled();
				expect(pageRenderer._renderPageContentToString).toHaveBeenCalledWith(controller, view, routeOptions);
			});

			it('should return response params', function() {
				expect(pageRenderResponse).toEqual(responseParams);
			});
		});
	});

	describe('_renderPageContentToString method', function() {

		var utils = { $Utils: 'utils' };
		var state = { state: 'state' };
		var propsView = { view: view };
		var props = Object.assign({}, state, utils, propsView);
		var wrapedPageViewElement = { wrapElementView: 'wrapedPageViewElement' };
		var pageMarkup = '';
		var documentView = function() {};
		var documentViewElement = function() {};
		var documentViewFactory = function() {
			return documentViewElement;
		};
		var appMarkup = '' + pageMarkup + '';
		var revivalSettings = { revivalSettings: 'revivalSettings' };
		var metaManager = { metaManager: 'metaManager' };
		var pageContent = null;

		beforeEach(function() {
			spyOn(pageRenderer, '_generateViewProps')
				.and
				.returnValue(props);
			spyOn(controller, 'getState')
				.and
				.returnValue(state);
			spyOn(rendererFactory, 'wrapView')
				.and
				.returnValue(wrapedPageViewElement);
			spyOn(ReactDOMServer, 'renderToString')
				.and
				.returnValue(pageMarkup);
			spyOn(rendererFactory, 'reactCreateFactory')
				.and
				.returnValue(documentViewFactory);
			spyOn(rendererFactory, 'getDocumentView')
				.and
				.returnValue(documentView);
			spyOn(ReactDOMServer, 'renderToStaticMarkup')
				.and
				.returnValue(appMarkup);
			spyOn(pageRenderer, '_getRevivalSettings')
				.and
				.returnValue(revivalSettings);
			spyOn(controller, 'getMetaManager')
				.and
				.returnValue(metaManager);
			spyOn(rendererFactory, 'getUtils')
				.and
				.returnValue(utils);

			pageContent = pageRenderer._renderPageContentToString(controller, view, routeOptions);
		});

		it('should generate view props from controller state', function() {
			expect(pageRenderer._generateViewProps).toHaveBeenCalledWith(view, state);
		});

		it('should wrap page view', function() {
			expect(rendererFactory.wrapView).toHaveBeenCalledWith(props);
		});

		it('should render page view to string', function() {
			expect(ReactDOMServer.renderToString).toHaveBeenCalledWith(wrapedPageViewElement);
		});

		it('should create factory for creating React element from document view', function() {
			expect(rendererFactory.reactCreateFactory).toHaveBeenCalledWith(documentView);
		});

		it('should create factory for creating React element from route options documentView property', function() {
			var routeOptionsWithDocument = Object.assign({}, routeOptions, { documentView: ns.ima.page.AbstractDocumentView });
			pageContent = pageRenderer._renderPageContentToString(controller, view, routeOptionsWithDocument);

			expect(rendererFactory.getDocumentView).toHaveBeenCalledWith(ns.ima.page.AbstractDocumentView);
		});

		it('should render static markup from document view', function() {
			expect(rendererFactory.getUtils).toHaveBeenCalled();
			expect(controller.getMetaManager).toHaveBeenCalled();
			expect(pageRenderer._getRevivalSettings).toHaveBeenCalled();
			expect(ReactDOMServer.renderToStaticMarkup).toHaveBeenCalledWith(documentViewElement);
		});

		it('should return page content', function() {
			expect(pageContent).toEqual('<!doctype html>\n' + appMarkup);
		});
	});

});
<commit_msg>#! remove unuse dependency object container<commit_after>describe('ima.page.renderer.ServerPageRenderer', function() {

	var param1 = 'param1';
	var param2 = 'param2';
	var params = {
		param1: param1,
		param2: Promise.resolve(param2)
	};

	var controller = new ns.ima.controller.Controller();
	controller.getMetaManager = function() {};
	var view = function() {};
	var expressResponse = {
		status: function() {},
		send: function() {}
	};

	var pageRenderer = null;
	var $Helper = oc.get('$Helper');
	var rendererFactory = oc.get('$PageRendererFactory');
	var ReactDOMServer = {
		renderToString: function() {},
		renderToStaticMarkup: function() {}
	};
	var settings = oc.get('$Settings');
	var response = oc.get('$Response');
	var cache = oc.get('$Cache');
	var routeOptions = {
		onlyUpdate: false,
		autoScroll: false,
		allowSPA: false,
		documentView: null
	};

	beforeEach(function() {
		response.init(expressResponse);
		pageRenderer = oc.create('ima.page.renderer.ServerPageRenderer', [rendererFactory, $Helper, ReactDOMServer, settings, response, cache]);
	});

	it('should be wrap each key to promise', function() {
		spyOn(Promise, 'resolve')
			.and
			.callThrough();

		pageRenderer._wrapEachKeyToPromise(params);

		expect(Promise.resolve).toHaveBeenCalledWith(param1);
		expect(Promise.resolve.calls.count()).toEqual(1);
	});

	describe('update method', function() {

		it('should reject promise with error', function(done) {
			spyOn(pageRenderer, 'mount')
				.and
				.stub();

			pageRenderer
				.update(controller, params)
				.catch(function(error) {
					expect(error instanceof ns.ima.error.GenericError).toEqual(true);
					done();
				});
		});

	});

	describe('mount method', function() {

		var loadedPageState = {
			param1: 'param1',
			param2: Promise.resolve('param2')
		};

		it('should return already sent data to the client', function(done) {
			var responseParams = {
				content: '',
				status: 200
			};

			spyOn(response, 'isResponseSent')
				.and
				.returnValue(true);
			spyOn(response, 'getResponseParams')
				.and
				.returnValue(responseParams);

			pageRenderer
				.mount(controller, view, loadedPageState, routeOptions)
				.then(function(page) {
					expect(page).toEqual(responseParams);
					done();
				});
		});

		it('should call _renderPage method', function(done) {
			spyOn(pageRenderer, '_renderPage')
				.and
				.stub();

			pageRenderer
				.mount(controller, view, loadedPageState, routeOptions)
				.then(function(page) {
					expect(pageRenderer._renderPage).toHaveBeenCalled();
					done();
				});
		});

	});

	describe('_renderPage method', function() {
		var fetchedResource = {
			resource: 'json'
		};

		it('should return already sent data to client', function() {
			var responseParams = {
				content: '',
				status: 200
			};

			spyOn(response, 'isResponseSent')
				.and
				.returnValue(true);
			spyOn(response, 'getResponseParams')
				.and
				.returnValue(responseParams);

			expect(pageRenderer._renderPage(controller, view, fetchedResource)).toEqual(responseParams);
		});

		describe('render new page', function() {

			var responseParams = { status: 200, content: '' };
			var pageRenderResponse = null;

			beforeEach(function() {
				spyOn(controller, 'setState')
					.and
					.stub();
				spyOn(controller, 'setMetaParams')
					.and
					.stub();
				spyOn(controller, 'getHttpStatus')
					.and
					.stub();
				spyOn(pageRenderer, '_renderPageContentToString')
					.and
					.stub();
				spyOn(response, 'status')
					.and
					.returnValue(response);
				spyOn(response, 'send')
					.and
					.returnValue(response);
				spyOn(response, 'getResponseParams')
					.and
					.returnValue(responseParams);

				pageRenderResponse = pageRenderer._renderPage(controller, view, fetchedResource, routeOptions);
			});

			it('should set controller state', function() {
				expect(controller.setState).toHaveBeenCalledWith(fetchedResource);
			});

			it('should set meta params', function() {
				expect(controller.setMetaParams).toHaveBeenCalledWith(fetchedResource);
			});

			it('should send response for request', function() {
				expect(response.status).toHaveBeenCalled();
				expect(response.send).toHaveBeenCalled();
				expect(controller.getHttpStatus).toHaveBeenCalled();
				expect(pageRenderer._renderPageContentToString).toHaveBeenCalledWith(controller, view, routeOptions);
			});

			it('should return response params', function() {
				expect(pageRenderResponse).toEqual(responseParams);
			});
		});
	});

	describe('_renderPageContentToString method', function() {

		var utils = { $Utils: 'utils' };
		var state = { state: 'state' };
		var propsView = { view: view };
		var props = Object.assign({}, state, utils, propsView);
		var wrapedPageViewElement = { wrapElementView: 'wrapedPageViewElement' };
		var pageMarkup = '';
		var documentView = function() {};
		var documentViewElement = function() {};
		var documentViewFactory = function() {
			return documentViewElement;
		};
		var appMarkup = '' + pageMarkup + '';
		var revivalSettings = { revivalSettings: 'revivalSettings' };
		var metaManager = { metaManager: 'metaManager' };
		var pageContent = null;

		beforeEach(function() {
			spyOn(pageRenderer, '_generateViewProps')
				.and
				.returnValue(props);
			spyOn(controller, 'getState')
				.and
				.returnValue(state);
			spyOn(rendererFactory, 'wrapView')
				.and
				.returnValue(wrapedPageViewElement);
			spyOn(ReactDOMServer, 'renderToString')
				.and
				.returnValue(pageMarkup);
			spyOn(rendererFactory, 'reactCreateFactory')
				.and
				.returnValue(documentViewFactory);
			spyOn(rendererFactory, 'getDocumentView')
				.and
				.returnValue(documentView);
			spyOn(ReactDOMServer, 'renderToStaticMarkup')
				.and
				.returnValue(appMarkup);
			spyOn(pageRenderer, '_getRevivalSettings')
				.and
				.returnValue(revivalSettings);
			spyOn(controller, 'getMetaManager')
				.and
				.returnValue(metaManager);
			spyOn(rendererFactory, 'getUtils')
				.and
				.returnValue(utils);

			pageContent = pageRenderer._renderPageContentToString(controller, view, routeOptions);
		});

		it('should generate view props from controller state', function() {
			expect(pageRenderer._generateViewProps).toHaveBeenCalledWith(view, state);
		});

		it('should wrap page view', function() {
			expect(rendererFactory.wrapView).toHaveBeenCalledWith(props);
		});

		it('should render page view to string', function() {
			expect(ReactDOMServer.renderToString).toHaveBeenCalledWith(wrapedPageViewElement);
		});

		it('should create factory for creating React element from document view', function() {
			expect(rendererFactory.reactCreateFactory).toHaveBeenCalledWith(documentView);
		});

		it('should create factory for creating React element from route options documentView property', function() {
			var routeOptionsWithDocument = Object.assign({}, routeOptions, { documentView: ns.ima.page.AbstractDocumentView });
			pageContent = pageRenderer._renderPageContentToString(controller, view, routeOptionsWithDocument);

			expect(rendererFactory.getDocumentView).toHaveBeenCalledWith(ns.ima.page.AbstractDocumentView);
		});

		it('should render static markup from document view', function() {
			expect(rendererFactory.getUtils).toHaveBeenCalled();
			expect(controller.getMetaManager).toHaveBeenCalled();
			expect(pageRenderer._getRevivalSettings).toHaveBeenCalled();
			expect(ReactDOMServer.renderToStaticMarkup).toHaveBeenCalledWith(documentViewElement);
		});

		it('should return page content', function() {
			expect(pageContent).toEqual('<!doctype html>\n' + appMarkup);
		});
	});

});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { List, ListItem } from 'material-ui/List'
import Subheader from 'material-ui/Subheader'
import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper'
import { white } from 'material-ui/styles/colors'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { linkForResourceKind } from '../../routes'
import HelpText from '../../i18n/help-text'

const mapStateToProps = function(store) {
  return {
    selectedNamespaces: store.usersettings.selectedNamespaces,
    workloadResources: store.workloads.resources,
    clusterResources: store.cluster.resources,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
  }
}

export default connect(mapStateToProps, mapDispatchToProps) (
class ResourceCounts extends React.PureComponent {

  static propTypes = {
    selectedNamespaces: PropTypes.object,
    workloadResources: PropTypes.object,
    clusterResources: PropTypes.object,
  }

  render() {

    let { props } = this
    let { clusterResources, workloadResources, selectedNamespaces } = props

    const styles = {
      wrapper: {
        marginTop: 0,
        backgroundColor: 'rgb(80,80,80)',
        border: '1px solid rgba(0,0,0,0.5)',
      },
      subheader: {
        fontSize: 18,
        color: white,
        backgroundColor: 'rgba(41, 121, 255, 0.8)',
        borderBottom: '1px solid rgba(0,0,0,0.5)',
        lineHeight: '30px',
      },
      secondary: {
        overflow: 'visible',
        paddingBottom: 20,
        height: 'inherit',
        whitespace: 'normal'
      },
      message: {
        overflow: 'visible',
        whiteSpace: 'normal',
        paddingBottom: 10,
      },
      type: {
        fontWeight: 600,
      },
      item: {
        wordWrap: 'break-word',
        padding: 8,
        color: 'rgb(240,240,240)',
        fontSize: 14,
      },
      count: {
        float: 'right',
        paddingRight: 15,
      },
      divider: {
        backgroundColor: 'rgba(224, 224, 224, 0.2)',
      },
      link: {
        color: 'rgb(240, 240, 240)',
      }
    }

    let countsByKind = {}
    let namespacesFiltered = (Object.keys(selectedNamespaces).length > 0)
    for (let key in workloadResources) {
      let resource = workloadResources[key]
      if (!namespacesFiltered || resource.metadata.namespace in selectedNamespaces) {
        countsByKind[resource.kind] = (countsByKind[resource.kind] || 0) + 1
      }
    }
    if (!namespacesFiltered) {
      for (let key in clusterResources) {
        let resource = clusterResources[key]
        countsByKind[resource.kind] = (countsByKind[resource.kind] || 0) + 1
      }
    }

    let items = []
    for (let kind in countsByKind) {
      if (kind === 'Endpoints' || kind === 'ReplicaSet') {
        continue
      }
      let name = kind
      if (!(name.endsWith('s'))) {
        name += 's'
      } else if (name.endsWith('ss')) {
        name += 'es'
      }
      let link = linkForResourceKind(kind)

      items.push(

          <ListItem
            disabled={true}
            leftIcon={null}
            primaryText={

              <Link to={link} style={styles.link}>{name}</Link>
              {countsByKind[kind]}

            }
            style={styles.item}
          />
          <Divider inset={false} style={styles.divider}/>

      )
    }

    return (
      <Paper style={styles.wrapper}>
        <HelpText style={{position: 'absolute', top: 7, right: 25}} locale={'en'} textId={'ResourceCounts'}/>
        <Subheader style={styles.subheader}>Resource Counts</Subheader>
        <List className={'list-contents'}>
          {items}
        </List>
      </Paper>
    )
  }
})
<commit_msg>pass namespace selection to links for resource counts<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { List, ListItem } from 'material-ui/List'
import Subheader from 'material-ui/Subheader'
import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper'
import { white } from 'material-ui/styles/colors'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { linkForResourceKind } from '../../routes'
import HelpText from '../../i18n/help-text'

const mapStateToProps = function(store) {
  return {
    selectedNamespaces: store.usersettings.selectedNamespaces,
    workloadResources: store.workloads.resources,
    clusterResources: store.cluster.resources,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
  }
}

export default connect(mapStateToProps, mapDispatchToProps) (
class ResourceCounts extends React.PureComponent {

  static propTypes = {
    selectedNamespaces: PropTypes.object,
    workloadResources: PropTypes.object,
    clusterResources: PropTypes.object,
  }

  render() {

    let { props } = this
    let { clusterResources, workloadResources, selectedNamespaces } = props

    const styles = {
      wrapper: {
        marginTop: 0,
        backgroundColor: 'rgb(80,80,80)',
        border: '1px solid rgba(0,0,0,0.5)',
      },
      subheader: {
        fontSize: 18,
        color: white,
        backgroundColor: 'rgba(41, 121, 255, 0.8)',
        borderBottom: '1px solid rgba(0,0,0,0.5)',
        lineHeight: '30px',
      },
      secondary: {
        overflow: 'visible',
        paddingBottom: 20,
        height: 'inherit',
        whitespace: 'normal'
      },
      message: {
        overflow: 'visible',
        whiteSpace: 'normal',
        paddingBottom: 10,
      },
      type: {
        fontWeight: 600,
      },
      item: {
        wordWrap: 'break-word',
        padding: 8,
        color: 'rgb(240,240,240)',
        fontSize: 14,
      },
      count: {
        float: 'right',
        paddingRight: 15,
      },
      divider: {
        backgroundColor: 'rgba(224, 224, 224, 0.2)',
      },
      link: {
        color: 'rgb(240, 240, 240)',
      }
    }

    let countsByKind = {}
    let namespacesFiltered = (Object.keys(selectedNamespaces).length > 0)
    for (let key in workloadResources) {
      let resource = workloadResources[key]
      if (!namespacesFiltered || resource.metadata.namespace in selectedNamespaces) {
        countsByKind[resource.kind] = (countsByKind[resource.kind] || 0) + 1
      }
    }
    if (!namespacesFiltered) {
      for (let key in clusterResources) {
        let resource = clusterResources[key]
        countsByKind[resource.kind] = (countsByKind[resource.kind] || 0) + 1
      }
    }

    let items = []
    for (let kind in countsByKind) {
      if (kind === 'Endpoints' || kind === 'ReplicaSet') {
        continue
      }
      let name = kind
      if (!(name.endsWith('s'))) {
        name += 's'
      } else if (name.endsWith('ss')) {
        name += 'es'
      }
      let link = linkForResourceKind(kind, selectedNamespaces)

      items.push(

          <ListItem
            disabled={true}
            leftIcon={null}
            primaryText={

              <Link to={link} style={styles.link}>{name}</Link>
              {countsByKind[kind]}

            }
            style={styles.item}
          />
          <Divider inset={false} style={styles.divider}/>

      )
    }

    return (
      <Paper style={styles.wrapper}>
        <HelpText style={{position: 'absolute', top: 7, right: 25}} locale={'en'} textId={'ResourceCounts'}/>
        <Subheader style={styles.subheader}>Resource Counts</Subheader>
        <List className={'list-contents'}>
          {items}
        </List>
      </Paper>
    )
  }
})
<|endoftext|>