<commit_before>/* global ArangoServerState, ERRORS */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / @brief Replication management
// /
// / @file
// /
// / DISCLAIMER
// /
// / Copyright 2012 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is triAGENS GmbH, Cologne, Germany
// /
// / @author Jan Steemann
// / @author Copyright 2013, triAGENS GmbH, Cologne, Germany
// //////////////////////////////////////////////////////////////////////////////

var internal = require('internal');
var endpointToURL = require('@arangodb/cluster').endpointToURL;
var request;
if (ArangoServerState.role() === 'PRIMARY') {
  request = require('@arangodb/request').clusterRequest;
} else {
  request = require('@arangodb/request').request;
}

var logger = { };
var applier = { };

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the replication logger state
// //////////////////////////////////////////////////////////////////////////////

logger.state = function () {
  return internal.getStateReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the tick ranges provided by the replication logger
// //////////////////////////////////////////////////////////////////////////////

logger.tickRanges = function () {
  return internal.tickRangesReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the first tick that can be provided by the replication logger
// //////////////////////////////////////////////////////////////////////////////

logger.firstTick = function () {
  return internal.firstTickReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief starts the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.start = function (initialTick, barrierId) {
  if (initialTick === undefined) {
    return internal.startReplicationApplier();
  }

  return internal.startReplicationApplier(initialTick, barrierId);
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief shuts down the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.shutdown = applier.stop = function () {
  return internal.shutdownReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the replication applier state
// //////////////////////////////////////////////////////////////////////////////

applier.state = function () {
  return internal.getStateReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief stop the applier and "forget" all configuration
// //////////////////////////////////////////////////////////////////////////////

applier.forget = function () {
  return internal.forgetStateReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief returns the configuration of the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.properties = function (config) {
  if (config === undefined) {
    return internal.configureReplicationApplier();
  }

  return internal.configureReplicationApplier(config);
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief performs a one-time synchronization with a remote endpoint
// //////////////////////////////////////////////////////////////////////////////

function sync (config) {
  return internal.synchronizeReplication(config);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief performs a one-time synchronization with a remote endpoint
// //////////////////////////////////////////////////////////////////////////////

function syncCollection (collection, config) {
  config = config || { };
  config.restrictType = 'include';
  config.restrictCollections = [ collection ];
  config.includeSystem = true;
  if (!config.hasOwnProperty('verbose')) {
    config.verbose = false;
  }

  return internal.synchronizeReplication(config);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief sets up the replication (all-in-one function for initial
// / synchronization and continuous replication)
// //////////////////////////////////////////////////////////////////////////////

function setupReplication (config) {
  config = config || { };
  if (!config.hasOwnProperty('autoStart')) {
    config.autoStart = true;
  }
  if (!config.hasOwnProperty('includeSystem')) {
    config.includeSystem = true;
  }
  if (!config.hasOwnProperty('verbose')) {
    config.verbose = false;
  }
  config.keepBarrier = true;

  try {
    // stop previous instance
    applier.stop();
  } catch (err) {}
  // remove existing configuration
  applier.forget();

  // run initial sync
  var result = internal.synchronizeReplication(config);

  // store applier configuration
  applier.properties(config);

  applier.start(result.lastLogTick, result.barrierId);
  return applier.state();
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief returns the server's id
// //////////////////////////////////////////////////////////////////////////////

function serverId () {
  return internal.serverId();
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief finishes off a synchronization of a single collection
// / `collection` is the name of a collection on the other server that should
// / have been synchronized using syncCollection (incremental or not)
// / recently. `from` is the tick as a string of that synchronization.
// / `config` can contain the following attributes:
// /   - "endpoint": endpoint of the other server
// / If one ensures that there is a read transaction ongoing on that collection
// / on the other server, then this function is guaranteed to bring the two
// / collections in perfect sync, until that read transaction is aborted.
// //////////////////////////////////////////////////////////////////////////////

var mType = {
  REPLICATION_COLLECTION_CREATE: 2000,
  REPLICATION_COLLECTION_DROP: 2001,
  REPLICATION_COLLECTION_RENAME: 2002,
  REPLICATION_COLLECTION_CHANGE: 2003,

  REPLICATION_INDEX_CREATE: 2100,
  REPLICATION_INDEX_DROP: 2101,

  REPLICATION_TRANSACTION_START: 2200,
  REPLICATION_TRANSACTION_COMMIT: 2201,
  REPLICATION_TRANSACTION_ABORT: 2202,

  REPLICATION_MARKER_DOCUMENT: 2300,
  REPLICATION_MARKER_EDGE: 2301,
  REPLICATION_MARKER_REMOVE: 2302
};

function syncCollectionFinalize (database, collname, from, config) {
  var url = endpointToURL(config.endpoint) + '/_db/' + database +
    '/_api/replication/logger-follow?collection=' + collname + '&from=';

  var coll = require('internal').db[collname];

  var transactions = {};

  function apply (entry) {
    var todo;

    function tryPostpone (entry) {
      if (entry.tid !== '0') {
        todo = transactions[entry.tid];
        if (Array.isArray(todo)) {
          entry.tid = '0';
          todo.push(entry);
          return true;
        }
      }
      return false;
    }

    if (entry.type === mType.REPLICATION_MARKER_DOCUMENT) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.insert(entry.data, {isRestore: true});
        return;
      } catch (err) {
        console.debug('syncCollectionFinalize: insert1', entry, JSON.stringify(err));
      }
      try {
        coll.replace(entry.data._key, entry.data, {isRestore: true});
      } catch (errx) {
        console.error('syncCollectionFinalize: replace1', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_MARKER_EDGE) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.insert(entry.data, {isRestore: true});
        return;
      } catch (err) {
        console.debug('syncCollectionFinalize: insert2', entry, JSON.stringify(err));
      }
      try {
        coll.replace(entry.data._key, entry.data, {isRestore: true});
      } catch (errx) {
        console.error('syncCollectionFinalize: replace2', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_MARKER_REMOVE) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.remove(entry.data._key);
      } catch (errx) {
        console.error('syncCollectionFinalize: remove', entry, JSON.stringify(errx));
        if (errx.errorNum !== ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) {
          throw errx;
        }
        // We swallow the NOT FOUND error here. It is possible that
        // a follower tries to synchronize to a leader with whom it
        // is already in sync. In that case there could have been a
        // synchronously replicated removal operation that has happened
        // whilst we were resynchronizing the shard. In this case, the
        // removal would have happened already.
      }
    } else if (entry.type === mType.REPLICATION_TRANSACTION_START) {
      transactions[entry.tid] = [];
    } else if (entry.type === mType.REPLICATION_TRANSACTION_COMMIT) {
      todo = transactions[entry.tid];
      if (Array.isArray(todo)) {
        for (var i = 0; i < todo.length; i++) {
          apply(todo[i]);
        }
      }
      delete transactions[entry.tid];
    } else if (entry.type === mType.REPLICATION_TRANSACTION_ABORT) {
      delete transactions[entry.tid];
    } else if (entry.type === mType.REPLICATION_INDEX_CREATE) {
      try {
        coll.ensureIndex(entry.data);
      } catch(errx) {
        console.error('syncCollectionFinalize: ensureIndex', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_INDEX_DROP) {
      try {
        coll.dropIndex(entry.data.id);
      } catch(errx) {
        console.error('syncCollectionFinalize: dropIndex', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_COLLECTION_CHANGE) {
      try {
        coll.properties(entry.data);
      } catch(errx) {
        console.error('syncCollectionFinalize: properties', entry, JSON.stringify(errx));
        throw errx;
      }
    } else {
      // all else, including dropping and creating the collection
      throw 'Found collection drop, create or rename marker.';
    }
  }

  while (true) {
    var chunk = request(url + from);
    if (chunk.statusCode !== 200 &&
      chunk.statusCode !== 204) {
      return {error: true, errorMessage: 'could not contact leader',
      response: chunk};
    }
    if (chunk.statusCode === 204 || chunk.body.length === 0) {
      break; // all done
    }

    var l = chunk.body.split('\n');
    l.pop();
    try {
      l = l.map(JSON.parse);
    } catch (err) {
      return {error: true, errorMessage: 'could not parse body',
      response: chunk, exception: err};
    }

    console.debug('Applying chunk:', l);
    try {
      for (var i = 0; i < l.length; i++) {
        apply(l[i]);
      }
    } catch (err2) {
      return {error: true, errorMessage: 'could not replicate body ops',
      response: chunk, exception: err2};
    }
    if (chunk.headers['x-arango-replication-checkmore'] !== 'true') {
      break; // all done
    }

    from = chunk.headers['x-arango-replication-lastincluded'];
  }
  return {error: false};
}

exports.logger = logger;
exports.applier = applier;
exports.sync = sync;
exports.syncCollection = syncCollection;
exports.setupReplication = setupReplication;
exports.serverId = serverId;
exports.syncCollectionFinalize = syncCollectionFinalize;
<commit_msg>Fix access to global ERRORS table.<commit_after>/* global ArangoServerState */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / @brief Replication management
// /
// / @file
// /
// / DISCLAIMER
// /
// / Copyright 2012 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is triAGENS GmbH, Cologne, Germany
// /
// / @author Jan Steemann
// / @author Copyright 2013, triAGENS GmbH, Cologne, Germany
// //////////////////////////////////////////////////////////////////////////////

var internal = require('internal');
var ERRORS = internal.errors;
var endpointToURL = require('@arangodb/cluster').endpointToURL;
var request;
if (ArangoServerState.role() === 'PRIMARY') {
  request = require('@arangodb/request').clusterRequest;
} else {
  request = require('@arangodb/request').request;
}

var logger = { };
var applier = { };

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the replication logger state
// //////////////////////////////////////////////////////////////////////////////

logger.state = function () {
  return internal.getStateReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the tick ranges provided by the replication logger
// //////////////////////////////////////////////////////////////////////////////

logger.tickRanges = function () {
  return internal.tickRangesReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the first tick that can be provided by the replication logger
// //////////////////////////////////////////////////////////////////////////////

logger.firstTick = function () {
  return internal.firstTickReplicationLogger();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief starts the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.start = function (initialTick, barrierId) {
  if (initialTick === undefined) {
    return internal.startReplicationApplier();
  }

  return internal.startReplicationApplier(initialTick, barrierId);
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief shuts down the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.shutdown = applier.stop = function () {
  return internal.shutdownReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief return the replication applier state
// //////////////////////////////////////////////////////////////////////////////

applier.state = function () {
  return internal.getStateReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief stop the applier and "forget" all configuration
// //////////////////////////////////////////////////////////////////////////////

applier.forget = function () {
  return internal.forgetStateReplicationApplier();
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief returns the configuration of the replication applier
// //////////////////////////////////////////////////////////////////////////////

applier.properties = function (config) {
  if (config === undefined) {
    return internal.configureReplicationApplier();
  }

  return internal.configureReplicationApplier(config);
};

// //////////////////////////////////////////////////////////////////////////////
// / @brief performs a one-time synchronization with a remote endpoint
// //////////////////////////////////////////////////////////////////////////////

function sync (config) {
  return internal.synchronizeReplication(config);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief performs a one-time synchronization with a remote endpoint
// //////////////////////////////////////////////////////////////////////////////

function syncCollection (collection, config) {
  config = config || { };
  config.restrictType = 'include';
  config.restrictCollections = [ collection ];
  config.includeSystem = true;
  if (!config.hasOwnProperty('verbose')) {
    config.verbose = false;
  }

  return internal.synchronizeReplication(config);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief sets up the replication (all-in-one function for initial
// / synchronization and continuous replication)
// //////////////////////////////////////////////////////////////////////////////

function setupReplication (config) {
  config = config || { };
  if (!config.hasOwnProperty('autoStart')) {
    config.autoStart = true;
  }
  if (!config.hasOwnProperty('includeSystem')) {
    config.includeSystem = true;
  }
  if (!config.hasOwnProperty('verbose')) {
    config.verbose = false;
  }
  config.keepBarrier = true;

  try {
    // stop previous instance
    applier.stop();
  } catch (err) {}
  // remove existing configuration
  applier.forget();

  // run initial sync
  var result = internal.synchronizeReplication(config);

  // store applier configuration
  applier.properties(config);

  applier.start(result.lastLogTick, result.barrierId);
  return applier.state();
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief returns the server's id
// //////////////////////////////////////////////////////////////////////////////

function serverId () {
  return internal.serverId();
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief finishes off a synchronization of a single collection
// / `collection` is the name of a collection on the other server that should
// / have been synchronized using syncCollection (incremental or not)
// / recently. `from` is the tick as a string of that synchronization.
// / `config` can contain the following attributes:
// /   - "endpoint": endpoint of the other server
// / If one ensures that there is a read transaction ongoing on that collection
// / on the other server, then this function is guaranteed to bring the two
// / collections in perfect sync, until that read transaction is aborted.
// //////////////////////////////////////////////////////////////////////////////

var mType = {
  REPLICATION_COLLECTION_CREATE: 2000,
  REPLICATION_COLLECTION_DROP: 2001,
  REPLICATION_COLLECTION_RENAME: 2002,
  REPLICATION_COLLECTION_CHANGE: 2003,

  REPLICATION_INDEX_CREATE: 2100,
  REPLICATION_INDEX_DROP: 2101,

  REPLICATION_TRANSACTION_START: 2200,
  REPLICATION_TRANSACTION_COMMIT: 2201,
  REPLICATION_TRANSACTION_ABORT: 2202,

  REPLICATION_MARKER_DOCUMENT: 2300,
  REPLICATION_MARKER_EDGE: 2301,
  REPLICATION_MARKER_REMOVE: 2302
};

function syncCollectionFinalize (database, collname, from, config) {
  var url = endpointToURL(config.endpoint) + '/_db/' + database +
    '/_api/replication/logger-follow?collection=' + collname + '&from=';

  var coll = require('internal').db[collname];

  var transactions = {};

  function apply (entry) {
    var todo;

    function tryPostpone (entry) {
      if (entry.tid !== '0') {
        todo = transactions[entry.tid];
        if (Array.isArray(todo)) {
          entry.tid = '0';
          todo.push(entry);
          return true;
        }
      }
      return false;
    }

    if (entry.type === mType.REPLICATION_MARKER_DOCUMENT) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.insert(entry.data, {isRestore: true});
        return;
      } catch (err) {
        console.debug('syncCollectionFinalize: insert1', entry, JSON.stringify(err));
      }
      try {
        coll.replace(entry.data._key, entry.data, {isRestore: true});
      } catch (errx) {
        console.error('syncCollectionFinalize: replace1', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_MARKER_EDGE) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.insert(entry.data, {isRestore: true});
        return;
      } catch (err) {
        console.debug('syncCollectionFinalize: insert2', entry, JSON.stringify(err));
      }
      try {
        coll.replace(entry.data._key, entry.data, {isRestore: true});
      } catch (errx) {
        console.error('syncCollectionFinalize: replace2', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_MARKER_REMOVE) {
      if (tryPostpone(entry)) {
        return;
      }
      try {
        coll.remove(entry.data._key);
      } catch (errx) {
        console.error('syncCollectionFinalize: remove', entry, JSON.stringify(errx));
        if (errx.errorNum !== ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) {
          throw errx;
        }
        // We swallow the NOT FOUND error here. It is possible that
        // a follower tries to synchronize to a leader with whom it
        // is already in sync. In that case there could have been a
        // synchronously replicated removal operation that has happened
        // whilst we were resynchronizing the shard. In this case, the
        // removal would have happened already.
      }
    } else if (entry.type === mType.REPLICATION_TRANSACTION_START) {
      transactions[entry.tid] = [];
    } else if (entry.type === mType.REPLICATION_TRANSACTION_COMMIT) {
      todo = transactions[entry.tid];
      if (Array.isArray(todo)) {
        for (var i = 0; i < todo.length; i++) {
          apply(todo[i]);
        }
      }
      delete transactions[entry.tid];
    } else if (entry.type === mType.REPLICATION_TRANSACTION_ABORT) {
      delete transactions[entry.tid];
    } else if (entry.type === mType.REPLICATION_INDEX_CREATE) {
      try {
        coll.ensureIndex(entry.data);
      } catch(errx) {
        console.error('syncCollectionFinalize: ensureIndex', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_INDEX_DROP) {
      try {
        coll.dropIndex(entry.data.id);
      } catch(errx) {
        console.error('syncCollectionFinalize: dropIndex', entry, JSON.stringify(errx));
        throw errx;
      }
    } else if (entry.type === mType.REPLICATION_COLLECTION_CHANGE) {
      try {
        coll.properties(entry.data);
      } catch(errx) {
        console.error('syncCollectionFinalize: properties', entry, JSON.stringify(errx));
        throw errx;
      }
    } else {
      // all else, including dropping and creating the collection
      throw 'Found collection drop, create or rename marker.';
    }
  }

  while (true) {
    var chunk = request(url + from);
    if (chunk.statusCode !== 200 &&
      chunk.statusCode !== 204) {
      return {error: true, errorMessage: 'could not contact leader',
      response: chunk};
    }
    if (chunk.statusCode === 204 || chunk.body.length === 0) {
      break; // all done
    }

    var l = chunk.body.split('\n');
    l.pop();
    try {
      l = l.map(JSON.parse);
    } catch (err) {
      return {error: true, errorMessage: 'could not parse body',
      response: chunk, exception: err};
    }

    console.debug('Applying chunk:', l);
    try {
      for (var i = 0; i < l.length; i++) {
        apply(l[i]);
      }
    } catch (err2) {
      return {error: true, errorMessage: 'could not replicate body ops',
      response: chunk, exception: err2};
    }
    if (chunk.headers['x-arango-replication-checkmore'] !== 'true') {
      break; // all done
    }

    from = chunk.headers['x-arango-replication-lastincluded'];
  }
  return {error: false};
}

exports.logger = logger;
exports.applier = applier;
exports.sync = sync;
exports.syncCollection = syncCollection;
exports.setupReplication = setupReplication;
exports.serverId = serverId;
exports.syncCollectionFinalize = syncCollectionFinalize;
<|endoftext|>


<commit_before>// utils
var deepMerge = require('../utils/deepMerge');

// config
var overrides = require('../../config/browser-sync');

/**
 * BrowserSync
 * configuration
 * object
 *
 */
module.exports = deepMerge({
	logSnippet: false,
	open: false,
	ghostMode: false
}, overrides);<commit_msg>reorganize bs props, bz.<commit_after>// utils
var deepMerge = require('../utils/deepMerge');

// config
var overrides = require('../../config/browser-sync');

/**
 * BrowserSync
 * configuration
 * object
 *
 */
module.exports = deepMerge({
	logSnippet: false,
	ghostMode: false,
	open: false
}, overrides);<|endoftext|>


<commit_before>// @flow

import { green, blue } from 'chalk';
import inquirer from 'inquirer';
import R from 'ramda';
import { table } from 'table';
import urlRegex from 'url-regex';

import gql from '../../gql';
import { printGraphQLErrors } from '../../printErrors';
import { runGQLQuery } from '../../query';

import typeof Yargs from 'yargs';
import type { BaseArgs } from '..';

export const command = 'create';
export const description = 'Create new project';

export function builder(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${command} - ${description}`)
    .example('$0', 'Create new project');
}

type createProjectArgs = {
  clog: typeof console.log,
  cerr: typeof console.error,
};

export async function createProject({
  clog,
  cerr,
}:
createProjectArgs): Promise<number> {
  const customersAndOpenshiftsResults = await runGQLQuery({
    query: gql`
      query AllCustomersAndOpenshiftsForProjectCreate {
        allCustomers {
          value: id
          name
        }
        allOpenshifts {
          value: id
          name
        }
      }
    `,
    cerr,
  });

  const { errors } = customersAndOpenshiftsResults;
  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  const allCustomers = R.path(
    ['data', 'allCustomers'],
    customersAndOpenshiftsResults,
  );
  const allOpenshifts = R.path(
    ['data', 'allOpenshifts'],
    customersAndOpenshiftsResults,
  );

  const projectInput = await inquirer.prompt([
    {
      type: 'list',
      name: 'customer',
      message: 'Customer:',
      choices: allCustomers,
      // Using the `when` method of the question object, decide whether to skip the question and trigger some side effects based on the number of customers returned
      // https://github.com/SBoudrias/Inquirer.js/issues/517#issuecomment-288964496
      when(answers) {
        return R.ifElse(
          R.compose(R.gte(1), R.length),
          // If there is only one customer in the customers list, use that customer as the answer to the question and tell the user, not prompting them to choose.
          (customers) => {
            const firstCustomer = R.head(customers);
            clog(`${blue('!')} Using only authorized customer "${R.prop(
              'name',
              firstCustomer,
            )}"`);
            // eslint-disable-next-line no-param-reassign
            answers.customer = R.prop('value', firstCustomer);
          },
          // If there is more than one customer, return true in order to trigger the list prompt
          R.T,
        )(allCustomers);
      },
    },
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      validate: input => Boolean(input) || 'Please enter a project name.',
    },
    {
      type: 'input',
      name: 'git_url',
      message: 'Git URL:',
      validate: input =>
        // Verify that it is a valid URL and...
        (urlRegex({ exact: true }).test(input) &&
          // ...that it is either a URL from the big three git hosts or includes `.git` at the end of the string.
          /(github\.com|bitbucket\.org|gitlab\.com|\.git$)/.test(input)) ||
        // If the input is invalid, prompt the user to enter a valid Git URL
        'Please enter a valid Git URL.',
    },
    {
      type: 'list',
      name: 'openshift',
      message: 'Openshift:',
      choices: allOpenshifts,
      // Using the `when` method of the question object, decide whether to skip the question and trigger some side effects based on the number of openshifts returned
      // https://github.com/SBoudrias/Inquirer.js/issues/517#issuecomment-288964496
      when(answers) {
        return R.ifElse(
          R.compose(R.gte(1), R.length),
          // If there is only one openshift in the openshifts list, use that openshift as the answer to the question and tell the user, not prompting them to choose.
          (openshifts) => {
            const firstOpenshift = R.head(openshifts);
            clog(`${blue('!')} Using only authorized openshift "${R.prop(
              'name',
              firstOpenshift,
            )}"`);
            // eslint-disable-next-line no-param-reassign
            answers.openshift = R.prop('value', firstOpenshift);
          },
          // If there is more than one openshift, return true in order to trigger the list prompt
          R.T,
        )(allOpenshifts);
      },
    },
    {
      type: 'input',
      name: 'active_systems_deploy',
      message: 'Active system for task "deploy":',
      default: 'lagoon_openshiftBuildDeploy',
    },
    {
      type: 'input',
      name: 'active_systems_remove',
      message: 'Active system for task "remove":',
      default: 'lagoon_openshiftRemove',
    },
    {
      type: 'input',
      name: 'branches',
      message: 'Deploy branches:',
      default: 'true',
    },
    {
      type: 'input',
      name: 'pullrequests',
      message: 'Pull requests:',
      default: null,
    },
    {
      type: 'input',
      name: 'production_environment',
      message: 'Production environment:',
      default: null,
    },
  ]);

  const addProjectResult = await runGQLQuery({
    query: gql`
      mutation AddProject($input: ProjectInput!) {
        addProject(input: $input) {
          id
          name
          customer {
            name
          }
          git_url
          active_systems_deploy
          active_systems_remove
          branches
          pullrequests
          openshift {
            name
          }
          created
        }
      }
    `,
    cerr,
    variables: {
      input: projectInput,
    },
  });

  const { errors: addProjectErrors } = addProjectResult;
  if (addProjectErrors != null) {
    return printGraphQLErrors(cerr, ...addProjectErrors);
  }

  const project = R.path(['data', 'addProject'], addProjectResult);

  const projectName = R.prop('name', project);

  clog(green(`Project "${projectName}" created successfully:`));

  clog(table([
    ['Project Name', projectName],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.path(['created'], project)],
  ]));

  return 0;
}

export async function handler({
  clog,
  cerr,
  config,
}:
BaseArgs): Promise<number> {
  return createProject({ clog, cerr });
}
<commit_msg>Use options from config file<commit_after>// @flow

import { green, blue } from 'chalk';
import inquirer from 'inquirer';
import R from 'ramda';
import { table } from 'table';
import urlRegex from 'url-regex';

import gql from '../../gql';
import { printGraphQLErrors, printErrors } from '../../printErrors';
import { runGQLQuery } from '../../query';

import type Inquirer from 'inquirer';
import typeof Yargs from 'yargs';
import type { BaseArgs } from '..';

export const command = 'create';
export const description = 'Create new project';

export function builder(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${command} - ${description}`)
    .example('$0', 'Create new project');
}

export async function getAllowedCustomersAndOpenshifts(cerr: typeof console.error) {
  const customersAndOpenshiftsResults = await runGQLQuery({
    query: gql`
      query AllCustomersAndOpenshiftsForProjectCreate {
        allCustomers {
          value: id
          name
        }
        allOpenshifts {
          value: id
          name
        }
      }
    `,
    cerr,
  });

  return {
    allCustomers: R.path(
      ['data', 'allCustomers'],
      customersAndOpenshiftsResults,
    ),
    allOpenshifts: R.path(
      ['data', 'allOpenshifts'],
      customersAndOpenshiftsResults,
    ),
    errors: R.prop('errors', customersAndOpenshiftsResults),
  };
}

export function answerFromOptionsPropCond(
  option: string,
  answers: Inquirer.answers,
  clog: typeof console.log,
) {
  return [
    R.compose(
      // Option is set
      R.has(option),
      R.prop('options'),
    ),
    (objectWithOptions: { options: Object }) => {
      const propVal = R.path(['options', option], objectWithOptions);
      clog(`${blue('!')} Using "${option}" option from arguments or config`);
      // eslint-disable-next-line no-param-reassign
      answers[option] = propVal;
    },
  ];
}

export function answerFromOptions(
  option: string,
  options: Object,
  clog: typeof console.log,
) {
  return (answers: Inquirer.answers) => {
    const [predicate, transform] = answerFromOptionsPropCond(
      option,
      answers,
      clog,
    );
    return R.ifElse(predicate, transform, R.T)({ options });
  };
}

type createProjectArgs = {
  clog: typeof console.log,
  cerr: typeof console.error,
  options: {
    customer: ?string,
  },
};

export async function createProject({
  clog,
  cerr,
  options,
}:
createProjectArgs): Promise<number> {
  const {
    allCustomers,
    allOpenshifts,
    errors,
  } = await getAllowedCustomersAndOpenshifts(cerr);

  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  if (R.equals(R.length(allCustomers), 0)) {
    return printErrors(cerr, 'No authorized customers found!');
  }

  if (R.equals(R.length(allOpenshifts), 0)) {
    return printErrors(cerr, 'No authorized openshifts found!');
  }

  const projectInput = await inquirer.prompt([
    {
      type: 'list',
      name: 'customer',
      message: 'Customer:',
      choices: allCustomers,
      // Using the `when` method of the question object, decide where to get the customer based on conditions
      // https://github.com/SBoudrias/Inquirer.js/issues/517#issuecomment-288964496
      when(answers) {
        return R.cond([
          // 1. If the `customer` is set in the command line arguments or the config, use that customer
          answerFromOptionsPropCond('customer', answers, clog),
          // 2. If only one customer was returned from the allCustomers query, use that customer as the answer to the question and tell the user, not prompting them to choose.
          [
            R.compose(R.equals(1), R.length, R.prop('allCustomers')),
            (customersAndOptions) => {
              const firstCustomer = R.compose(R.head, R.prop('allCustomers'))(customersAndOptions);
              clog(`${blue('!')} Using only authorized customer "${R.prop(
                'name',
                firstCustomer,
              )}"`);
              // eslint-disable-next-line no-param-reassign
              answers.customer = R.prop('value', firstCustomer);
            },
          ],
          // 3. If more than one customer was returned from the allCustomers query, return true to prompt the user to choose from a list
          [R.T, R.T],
        ])({ allCustomers, options });
      },
    },
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      validate: input => Boolean(input) || 'Please enter a project name.',
    },
    {
      type: 'input',
      name: 'git_url',
      message: 'Git URL:',
      validate: input =>
        // Verify that it is a valid URL and...
        (urlRegex({ exact: true }).test(input) &&
          // ...that it is either a URL from the big three git hosts or includes `.git` at the end of the string.
          /(github\.com|bitbucket\.org|gitlab\.com|\.git$)/.test(input)) ||
        // If the input is invalid, prompt the user to enter a valid Git URL
        'Please enter a valid Git URL.',
      when: answerFromOptions('git_url', options, clog),
    },
    {
      type: 'list',
      name: 'openshift',
      message: 'Openshift:',
      choices: allOpenshifts,
      // Using the `when` method of the question object, decide where to get the openshift based on conditions
      // https://github.com/SBoudrias/Inquirer.js/issues/517#issuecomment-288964496
      when(answers) {
        return R.cond([
          // 1. If the `openshift` is set in the command line arguments or the config, use that openshift
          answerFromOptionsPropCond('openshift', answers, clog),
          // 2. If only one openshift was returned from the allOpenshifts query, use that openshift as the answer to the question and tell the user, not prompting them to choose.
          [
            R.compose(R.equals(1), R.length, R.prop('allOpenshifts')),
            (openshiftsAndOptions) => {
              const firstOpenshift = R.compose(R.head, R.prop('allOpenshifts'))(openshiftsAndOptions);
              clog(`${blue('!')} Using only authorized openshift "${R.prop(
                'name',
                firstOpenshift,
              )}"`);
              // eslint-disable-next-line no-param-reassign
              answers.openshift = R.prop('value', firstOpenshift);
            },
          ],
          // 3. If more than one openshift was returned from the allOpenshifts query, return true to prompt the user to choose from a list
          [R.T, R.T],
        ])({ allOpenshifts, options });
      },
    },
    {
      type: 'input',
      name: 'active_systems_deploy',
      message: 'Active system for task "deploy":',
      default: 'lagoon_openshiftBuildDeploy',
      when: answerFromOptions('active_systems_deploy', options, clog),
    },
    {
      type: 'input',
      name: 'active_systems_remove',
      message: 'Active system for task "remove":',
      default: 'lagoon_openshiftRemove',
      when: answerFromOptions('active_systems_remove', options, clog),
    },
    {
      type: 'input',
      name: 'branches',
      message: 'Deploy branches:',
      default: 'true',
      when: answerFromOptions('branches', options, clog),
    },
    {
      type: 'input',
      name: 'pullrequests',
      message: 'Pull requests:',
      default: null,
      when: answerFromOptions('pullrequests', options, clog),
    },
    {
      type: 'input',
      name: 'production_environment',
      message: 'Production environment:',
      default: null,
      when: answerFromOptions('production_environment', options, clog),
    },
  ]);

  const addProjectResult = await runGQLQuery({
    query: gql`
      mutation AddProject($input: ProjectInput!) {
        addProject(input: $input) {
          id
          name
          customer {
            name
          }
          git_url
          active_systems_deploy
          active_systems_remove
          branches
          pullrequests
          openshift {
            name
          }
          created
        }
      }
    `,
    cerr,
    variables: {
      input: projectInput,
    },
  });

  const { errors: addProjectErrors } = addProjectResult;
  if (addProjectErrors != null) {
    return printGraphQLErrors(cerr, ...addProjectErrors);
  }

  const project = R.path(['data', 'addProject'], addProjectResult);

  const projectName = R.prop('name', project);

  clog(green(`Project "${projectName}" created successfully:`));

  clog(table([
    ['Project Name', projectName],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.prop('created', project)],
  ]));

  return 0;
}

type Args = BaseArgs & {
  argv: {
    customer: ?string,
  },
};

export async function handler({
  clog,
  cerr,
  config,
  argv,
}:
Args): Promise<number> {
  const options = {
    ...config,
    ...argv,
  };
  return createProject({ clog, cerr, options });
}
<|endoftext|>


<commit_before>export const ADD_SLIDE_TO_DECK = 'ADD_SLIDE_TO_DECK';
export const DELETE_SLIDE_FROM_DECK = 'DELETE_SLIDE_FROM_DECK';

export const UPDATE_DECK = 'UPDATE_DECK';
export const UPDATE_DECK_SUCCESS = 'UPDATE_DECK_SUCCESS';
export const UPDATE_DECK_FAILURE = 'UPDATE_DECK_FAILURE';

export const FETCH_DECK = 'FETCH_DECK';
export const FETCH_DECK_SUCCESS = 'FETCH_DECK_SUCCESS';
export const FETCH_DECK_FAILURE = 'FETCH_DECK_FAILURE';

export const ADD_DECK_METADATA = 'ADD_DECK_METADATA';

export const DECK_DELETION_SUCCESS = 'DECK_DELETION_SUCCESS';

export function addSlideToDeck(deckId, previousSlideId) {
  return {
    type: ADD_SLIDE_TO_DECK,
    meta: { deckId, previousSlideId },
  };
}

export function deleteSlideFromDeck(deckId, slideId) {
  return {
    type: DELETE_SLIDE_FROM_DECK,
    meta: { deckId, slideId },
  };
}

export function updateDeck() {
  return {
    type: UPDATE_DECK,
  };
}

export function fetchDeck(deckId) {
  return {
    type: FETCH_DECK,
    meta: { deckId },
  };
}

export function fetchDeckSuccess(deckId, meta, slidesById, contentItemsById) {
  return {
    type: FETCH_DECK_SUCCESS,
    payload: { deckId, meta, slidesById, contentItemsById },
  };
}
<commit_msg>Added TODO to updateDeck action creator: this needs to take the deckId as a parameter instead of simply updating the active deck (both for consistency as for being future-proof).<commit_after>export const ADD_SLIDE_TO_DECK = 'ADD_SLIDE_TO_DECK';
export const DELETE_SLIDE_FROM_DECK = 'DELETE_SLIDE_FROM_DECK';

export const UPDATE_DECK = 'UPDATE_DECK';
export const UPDATE_DECK_SUCCESS = 'UPDATE_DECK_SUCCESS';
export const UPDATE_DECK_FAILURE = 'UPDATE_DECK_FAILURE';

export const FETCH_DECK = 'FETCH_DECK';
export const FETCH_DECK_SUCCESS = 'FETCH_DECK_SUCCESS';
export const FETCH_DECK_FAILURE = 'FETCH_DECK_FAILURE';

export const ADD_DECK_METADATA = 'ADD_DECK_METADATA';

export const DECK_DELETION_SUCCESS = 'DECK_DELETION_SUCCESS';

export function addSlideToDeck(deckId, previousSlideId) {
  return {
    type: ADD_SLIDE_TO_DECK,
    meta: { deckId, previousSlideId },
  };
}

export function deleteSlideFromDeck(deckId, slideId) {
  return {
    type: DELETE_SLIDE_FROM_DECK,
    meta: { deckId, slideId },
  };
}

export function updateDeck() {
  return {
    type: UPDATE_DECK,
    // #TODO meta: { deckid }
  };
}

export function fetchDeck(deckId) {
  return {
    type: FETCH_DECK,
    meta: { deckId },
  };
}

export function fetchDeckSuccess(deckId, meta, slidesById, contentItemsById) {
  return {
    type: FETCH_DECK_SUCCESS,
    payload: { deckId, meta, slidesById, contentItemsById },
  };
}
<|endoftext|>


<commit_before>'use strict';

(function(exports){

  var contentElem = undefined;
  var moveDialog = undefined;
  var gmClientLoggedIn = undefined;

  var rootTagKey = undefined;
  var keyToTag = {};

  function init(_gmClient, _contentElem, srcDir, queryParams, curTabData) {
    contentElem = _contentElem;

    moveDialog = contentElem.find('#move_dialog')
    moveDialog.dialog({
      dialogClass: "no-close",
      buttons: [
        {
          id: "move-button-ok",
          text: "Move!",
          click: function() {
            var self = this;

            var node = $(this).data("node");
            var data = $(this).data("data");

            var subj = data.otherNode;

            var leafPolicyVal = $('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Move" button
            $("#move-button-ok").button("disable");

            gmClientLoggedIn.updateTag(subj.key, {
              parentTagID: data.node.key,
              newLeafPolicy: leafPolicy,
            }, function(status, resp) {
              // Enable "Move" button back
              $("#move-button-ok").button("enable");
              if (status == 200) {
                // move succeeded, so move the node visually, and close the
                // dialog
                subj.moveTo(node, data.hitMode);
                subj.makeVisible();
                $( self ).dialog( "close" );
              } else {
                // TODO: show error
                alert(JSON.stringify(resp));
              }

              $(data.node.span).removeClass("pending");
            });

          }
        },
        {
          id: "move-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Move tag",
    });
    moveDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      var data = $(this).data("data");
      var subj = data.otherNode;
      moveDialog.dialog(
        "option", "title",
        'Move "' + subj.title + '" under "' + data.node.title + '"'
      );
      $('input:radio[name=new_leaf_policy]')
        .filter('[value=del]')
        .prop('checked', true);
    });

    moveDialog.find('#move_dialog_details_link').click(function() {
      moveDialog.find('#move_dialog_details').toggle();
      return false;
    });

    _gmClient.createGMClientLoggedIn().then(function(instance) {
      if (instance) {
        initLoggedIn(instance, contentElem, srcDir);
      } else {
        gmPageWrapper.openPageLogin("openPageTagsTree", []);
        gmPageWrapper.closeCurrentWindow();
      }
    });

  }

  function initLoggedIn(instance, contentElem, srcDir) {
    gmClientLoggedIn = instance;
    var tagsTreeDiv = contentElem.find('#tags_tree_div')

    gmClientLoggedIn.getTagsTree(function(status, resp) {
      if (status == 200) {
        var treeData = convertTreeData(resp, true);

        tagsTreeDiv.fancytree({
          extensions: ["edit", "table", "dnd"],
          edit: {
            adjustWidthOfs: 4,   // null: don't adjust input size to content
            inputCss: { minWidth: "3em" },
            triggerStart: ["f2", "shift+click", "mac+enter"],
            beforeEdit: function(event, data) {
              if (data.node.key === rootTagKey) {
                return false;
              }
              return true;
            },
            edit: $.noop,        // Editor was opened (available as data.input)
            beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
            save: saveTag,       // Save data.input.val() or return false to keep editor open
            close: $.noop,       // Editor was removed
          },
          table: {
          },
          dnd: {
            // Available options with their default:
            autoExpandMS: 1000,   // Expand nodes after n milliseconds of hovering
            draggable: null,      // Additional options passed to jQuery UI draggable
            droppable: null,      // Additional options passed to jQuery UI droppable
            focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
            preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
            preventVoidMoves: true,      // Prevent dropping nodes 'before self', etc.
            smartRevert: true,    // set draggable.revert = true if drop was rejected

            // Events that make tree nodes draggable
            dragStart: function(node, data) {
              return true;
            },
            dragStop: null,       // Callback(sourceNode, data)
            initHelper: null,     // Callback(sourceNode, data)
            updateHelper: null,   // Callback(sourceNode, data)

            // Events that make tree nodes accept draggables
            dragEnter: function(node, data) {
              // allow only moving nodes under other nodes; do not allow
              // reordering.
              // (to allow reordering, the returned array should also contain
              // "before", "after".)
              return ["over"];
            },
            dragExpand: null,     // Callback(targetNode, data), return false to prevent autoExpand
            dragOver: null,       // Callback(targetNode, data)
            dragDrop: function(node, data) {
              // This function MUST be defined to enable dropping of items on the tree.
              // data.hitMode is 'before', 'after', or 'over'.
              // We could for example move the source to the new target:
              var oldParent = data.otherNode.parent;
              var subj = data.otherNode;

              // Open the move dialog
              moveDialog
                .data("node", node)
                .data("data", data)
                .dialog("open");
            },
            dragLeave: null       // Callback(targetNode, data)
          },
          source: {
            children: [treeData],
          },
          renderColumns: function(event, data) {
            if (data.node.key !== rootTagKey) {
              var node = data.node;
              var $tdList = $(node.tr).find(">td");
              var $ctrlCol = $tdList.eq(2);
              $ctrlCol.text("");
              $("<a/>", {
                href: "#",
                text: "[edit]",
                click: function() {
                  gmPageWrapper.openPageEditTag(data.node.key);
                },
              }).appendTo($ctrlCol);
            }
          },
        });

        var tree = tagsTreeDiv.fancytree("getTree");
        var rootNode = tree.getNodeByKey(rootTagKey);
        rootNode.setExpanded(true);
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
      }
    })
  }

  function convertTreeData(tagsTree, isRoot) {
    var key = String(tagsTree.id);

    var ret = {
      title: tagsTree.names.join(", "),
      key: key,
    };

    keyToTag[key] = tagsTree;

    if (isRoot) {
      ret.title = "my tags";
      rootTagKey = key;
    }
    if ("subtags" in tagsTree) {
      ret.children = tagsTree.subtags.map(function(a) {
        return convertTreeData(a, false);
      });
      ret.folder = true;
    }
    return ret;
  }

  // see https://github.com/mar10/fancytree/wiki/ExtEdit for argument details
  function saveTag(event, data) {
    $(data.node.span).addClass("pending");
    var val = data.input.val();
    var prevVal = data.orgTitle;
    //console.log('saveTag event', event)
    //console.log('saveTag data', data)

    gmClientLoggedIn.updateTag(String(data.node.key), {
      names: val.split(",").map(function(a) {
        return a.trim();
      }),
    }, function(status, resp) {
      if (status == 200) {
        // update succeeded, do nothing here
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
        data.node.setTitle(prevVal);
      }

      $(data.node.span).removeClass("pending");
    });

    // Optimistically assume that save will succeed. Accept the user input
    return true;
  }

  exports.init = init;

})(typeof exports === 'undefined' ? this['gmTagsTree']={} : exports);
<commit_msg>Show tag controls only onmouseover<commit_after>'use strict';

(function(exports){

  var contentElem = undefined;
  var moveDialog = undefined;
  var gmClientLoggedIn = undefined;

  var rootTagKey = undefined;
  var keyToTag = {};

  function init(_gmClient, _contentElem, srcDir, queryParams, curTabData) {
    contentElem = _contentElem;

    moveDialog = contentElem.find('#move_dialog')
    moveDialog.dialog({
      dialogClass: "no-close",
      buttons: [
        {
          id: "move-button-ok",
          text: "Move!",
          click: function() {
            var self = this;

            var node = $(this).data("node");
            var data = $(this).data("data");

            var subj = data.otherNode;

            var leafPolicyVal = $('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Move" button
            $("#move-button-ok").button("disable");

            gmClientLoggedIn.updateTag(subj.key, {
              parentTagID: data.node.key,
              newLeafPolicy: leafPolicy,
            }, function(status, resp) {
              // Enable "Move" button back
              $("#move-button-ok").button("enable");
              if (status == 200) {
                // move succeeded, so move the node visually, and close the
                // dialog
                subj.moveTo(node, data.hitMode);
                subj.makeVisible();
                $( self ).dialog( "close" );
              } else {
                // TODO: show error
                alert(JSON.stringify(resp));
              }

              $(data.node.span).removeClass("pending");
            });

          }
        },
        {
          id: "move-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Move tag",
    });
    moveDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      var data = $(this).data("data");
      var subj = data.otherNode;
      moveDialog.dialog(
        "option", "title",
        'Move "' + subj.title + '" under "' + data.node.title + '"'
      );
      $('input:radio[name=new_leaf_policy]')
        .filter('[value=del]')
        .prop('checked', true);
    });

    moveDialog.find('#move_dialog_details_link').click(function() {
      moveDialog.find('#move_dialog_details').toggle();
      return false;
    });

    _gmClient.createGMClientLoggedIn().then(function(instance) {
      if (instance) {
        initLoggedIn(instance, contentElem, srcDir);
      } else {
        gmPageWrapper.openPageLogin("openPageTagsTree", []);
        gmPageWrapper.closeCurrentWindow();
      }
    });

  }

  function initLoggedIn(instance, contentElem, srcDir) {
    gmClientLoggedIn = instance;
    var tagsTreeDiv = contentElem.find('#tags_tree_div')

    gmClientLoggedIn.getTagsTree(function(status, resp) {
      if (status == 200) {
        var treeData = convertTreeData(resp, true);

        tagsTreeDiv.fancytree({
          extensions: ["edit", "table", "dnd"],
          edit: {
            adjustWidthOfs: 4,   // null: don't adjust input size to content
            inputCss: { minWidth: "3em" },
            triggerStart: ["f2", "shift+click", "mac+enter"],
            beforeEdit: function(event, data) {
              if (data.node.key === rootTagKey) {
                return false;
              }
              return true;
            },
            edit: $.noop,        // Editor was opened (available as data.input)
            beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
            save: saveTag,       // Save data.input.val() or return false to keep editor open
            close: $.noop,       // Editor was removed
          },
          table: {
          },
          dnd: {
            // Available options with their default:
            autoExpandMS: 1000,   // Expand nodes after n milliseconds of hovering
            draggable: null,      // Additional options passed to jQuery UI draggable
            droppable: null,      // Additional options passed to jQuery UI droppable
            focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
            preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
            preventVoidMoves: true,      // Prevent dropping nodes 'before self', etc.
            smartRevert: true,    // set draggable.revert = true if drop was rejected

            // Events that make tree nodes draggable
            dragStart: function(node, data) {
              return true;
            },
            dragStop: null,       // Callback(sourceNode, data)
            initHelper: null,     // Callback(sourceNode, data)
            updateHelper: null,   // Callback(sourceNode, data)

            // Events that make tree nodes accept draggables
            dragEnter: function(node, data) {
              // allow only moving nodes under other nodes; do not allow
              // reordering.
              // (to allow reordering, the returned array should also contain
              // "before", "after".)
              return ["over"];
            },
            dragExpand: null,     // Callback(targetNode, data), return false to prevent autoExpand
            dragOver: null,       // Callback(targetNode, data)
            dragDrop: function(node, data) {
              // This function MUST be defined to enable dropping of items on the tree.
              // data.hitMode is 'before', 'after', or 'over'.
              // We could for example move the source to the new target:
              var oldParent = data.otherNode.parent;
              var subj = data.otherNode;

              // Open the move dialog
              moveDialog
                .data("node", node)
                .data("data", data)
                .dialog("open");
            },
            dragLeave: null       // Callback(targetNode, data)
          },
          source: {
            children: [treeData],
          },
          renderColumns: function(event, data) {
            if (data.node.key !== rootTagKey) {
              var node = data.node;
              var $tdList = $(node.tr).find(">td");
              var $mainCol = $tdList.eq(0);

              // Add controls if not already
              if (!$mainCol.data("ctrlAdded")) {
                $mainCol.data("ctrlAdded", true);

                var $ctrlSpan = $("<span/>", {
                  class: "hidden",
                  id: "ctrl_" + data.node.key,
                });

                $("<a/>", {
                  href: "#",
                  text: "[edit]",
                  click: function() {
                    gmPageWrapper.openPageEditTag(data.node.key);
                  },
                }).appendTo($ctrlSpan);

                $ctrlSpan.appendTo($mainCol.find(".fancytree-node"));

              }

              $tdList.mouseover(function() {
                $tdList.find("#ctrl_" + data.node.key).show();
              })

              $tdList.mouseout(function() {
                $tdList.find("#ctrl_" + data.node.key).hide();
              })
            }
          },
        });

        var tree = tagsTreeDiv.fancytree("getTree");
        var rootNode = tree.getNodeByKey(rootTagKey);
        rootNode.setExpanded(true);
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
      }
    })
  }

  function convertTreeData(tagsTree, isRoot) {
    var key = String(tagsTree.id);

    var ret = {
      title: tagsTree.names.join(", "),
      key: key,
    };

    keyToTag[key] = tagsTree;

    if (isRoot) {
      ret.title = "my tags";
      rootTagKey = key;
    }
    if ("subtags" in tagsTree) {
      ret.children = tagsTree.subtags.map(function(a) {
        return convertTreeData(a, false);
      });
      ret.folder = true;
    }
    return ret;
  }

  // see https://github.com/mar10/fancytree/wiki/ExtEdit for argument details
  function saveTag(event, data) {
    $(data.node.span).addClass("pending");
    var val = data.input.val();
    var prevVal = data.orgTitle;
    //console.log('saveTag event', event)
    //console.log('saveTag data', data)

    gmClientLoggedIn.updateTag(String(data.node.key), {
      names: val.split(",").map(function(a) {
        return a.trim();
      }),
    }, function(status, resp) {
      if (status == 200) {
        // update succeeded, do nothing here
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
        data.node.setTitle(prevVal);
      }

      $(data.node.span).removeClass("pending");
    });

    // Optimistically assume that save will succeed. Accept the user input
    return true;
  }

  exports.init = init;

})(typeof exports === 'undefined' ? this['gmTagsTree']={} : exports);
<|endoftext|>


<commit_before>import DOMPurify from 'dompurify';

const activitiesPerPage = 500;

BlazeComponent.extendComponent({
  onCreated() {
    // XXX Should we use ReactiveNumber?
    this.page = new ReactiveVar(1);
    this.loadNextPageLocked = false;
    // TODO is sidebar always available? E.g. on small screens/mobile devices
    const sidebar = Sidebar;
    sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
    this.autorun(() => {
      let mode = this.data().mode;
      const capitalizedMode = Utils.capitalize(mode);
      let searchId;
      if (mode === 'linkedcard' || mode === 'linkedboard') {
        searchId = Utils.getCurrentCard().linkedId;
        mode = mode.replace('linked', '');
      } else if (mode == "card" ) {
        searchId = Utils.getCurrentCardId();
      } else {
        searchId = Session.get(`current${capitalizedMode}`);
      }
      const limit = this.page.get() * activitiesPerPage;
      const user = Meteor.user();
      const hideSystem = user ? user.hasHiddenSystemMessages() : false;
      if (searchId === null) return;

      this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
        this.loadNextPageLocked = false;

        // TODO the guard can be removed as soon as the TODO above is resolved
        if (!sidebar) return;
        // If the sibear peak hasn't increased, that mean that there are no more
        // activities, and we can stop calling new subscriptions.
        // XXX This is hacky! We need to know excatly and reactively how many
        // activities there are, we probably want to denormalize this number
        // dirrectly into card and board documents.
        const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
        sidebar.calculateNextPeak();
        const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
        if (nextPeakBefore === nextPeakAfter) {
          sidebar.callFirstWith(null, 'resetNextPeak');
        }
      });
    });
  },
  loadNextPage() {
    if (this.loadNextPageLocked === false) {
      this.page.set(this.page.get() + 1);
      this.loadNextPageLocked = true;
    }
  },
}).register('activities');

Template.activities.helpers({
  activities() {
    const ret = this.card.activities();
    return ret;
  },
});

BlazeComponent.extendComponent({
  checkItem() {
    const checkItemId = this.currentData().activity.checklistItemId;
    const checkItem = ChecklistItems.findOne({ _id: checkItemId });
    return checkItem && checkItem.title;
  },

  boardLabelLink() {
    const data = this.currentData();
    if (data.mode !== 'board') {
      return createBoardLink(data.activity.board(), data.activity.listName);
    }
    return TAPi18n.__('this-board');
  },

  cardLabelLink() {
    const data = this.currentData();
    if (data.mode !== 'card') {
      return createCardLink(data.activity.card());
    }
    return TAPi18n.__('this-card');
  },

  cardLink() {
    return createCardLink(this.currentData().activity.card());
  },

  receivedDate() {
    const receivedDate = this.currentData().activity.card();
    if (!receivedDate) return null;
    return receivedDate.receivedAt;
  },

  startDate() {
    const startDate = this.currentData().activity.card();
    if (!startDate) return null;
    return startDate.startAt;
  },

  dueDate() {
    const dueDate = this.currentData().activity.card();
    if (!dueDate) return null;
    return dueDate.dueAt;
  },

  endDate() {
    const endDate = this.currentData().activity.card();
    if (!endDate) return null;
    return endDate.endAt;
  },

  lastLabel() {
    const lastLabelId = this.currentData().activity.labelId;
    if (!lastLabelId) return null;
    const lastLabel = Boards.findOne(
      this.currentData().activity.boardId,
    ).getLabelById(lastLabelId);
    if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
      return lastLabel.color;
    } else if (lastLabel.name !== undefined && lastLabel.name !== '') {
      return lastLabel.name;
    } else {
      return null;
    }
  },

  lastCustomField() {
    const lastCustomField = CustomFields.findOne(
      this.currentData().activity.customFieldId,
    );
    if (!lastCustomField) return null;
    return lastCustomField.name;
  },

  lastCustomFieldValue() {
    const lastCustomField = CustomFields.findOne(
      this.currentData().activity.customFieldId,
    );
    if (!lastCustomField) return null;
    const value = this.currentData().activity.value;
    if (
      lastCustomField.settings.dropdownItems &&
      lastCustomField.settings.dropdownItems.length > 0
    ) {
      const dropDownValue = _.find(
        lastCustomField.settings.dropdownItems,
        item => {
          return item._id === value;
        },
      );
      if (dropDownValue) return dropDownValue.name;
    }
    return value;
  },

  listLabel() {
    const activity = this.currentData().activity;
    const list = activity.list();
    return (list && list.title) || activity.title;
  },

  sourceLink() {
    const source = this.currentData().activity.source;
    if (source) {
      if (source.url) {
        return Blaze.toHTML(
          HTML.A(
            {
              href: source.url,
            },
            DOMPurify.sanitize(source.system, {
              ALLOW_UNKNOWN_PROTOCOLS: true,
            }),
          ),
        );
      } else {
        return DOMPurify.sanitize(source.system, {
          ALLOW_UNKNOWN_PROTOCOLS: true,
        });
      }
    }
    return null;
  },

  memberLink() {
    return Blaze.toHTMLWithData(Template.memberName, {
      user: this.currentData().activity.member(),
    });
  },

  attachmentLink() {
    const attachment = this.currentData().activity.attachment();
    // trying to display url before file is stored generates js errors
    return (
      (attachment &&
        attachment.url({ download: true }) &&
        Blaze.toHTML(
          HTML.A(
            {
              href: attachment.url({ download: true }),
              target: '_blank',
            },
            DOMPurify.sanitize(attachment.name()),
          ),
        )) ||
      DOMPurify.sanitize(this.currentData().activity.attachmentName)
    );
  },

  customField() {
    const customField = this.currentData().activity.customField();
    if (!customField) return null;
    return customField.name;
  },

  events() {
    return [
      {
        // XXX We should use Popup.afterConfirmation here
        'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
          const commentId = this.data().activity.commentId;
          CardComments.remove(commentId);
          Popup.back();
        }),
        'submit .js-edit-comment'(evt) {
          evt.preventDefault();
          const commentText = this.currentComponent()
            .getValue()
            .trim();
          const commentId = Template.parentData().activity.commentId;
          if (commentText) {
            CardComments.update(commentId, {
              $set: {
                text: commentText,
              },
            });
          }
        },
      },
    ];
  },
}).register('activity');

Template.activity.helpers({
  sanitize(value) {
    return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true });
  },
});

Template.commentReactions.events({
  'click .reaction'(event) {
    if (Meteor.user().isBoardMember()) {
      const codepoint = event.currentTarget.dataset['codepoint'];
      const commentId = Template.instance().data.commentId;
      const cardComment = CardComments.findOne({_id: commentId});
      cardComment.toggleReaction(codepoint);
    }
  },
  'click .open-comment-reaction-popup': Popup.open('addReaction'),
})

Template.addReactionPopup.events({
  'click .add-comment-reaction'(event) {
    if (Meteor.user().isBoardMember()) {
      const codepoint = event.currentTarget.dataset['codepoint'];
      const commentId = Template.instance().data.commentId;
      const cardComment = CardComments.findOne({_id: commentId});
      cardComment.toggleReaction(codepoint);
    }
    Popup.back();
  },
})

Template.addReactionPopup.helpers({
  codepoints() {
    // Starting set of unicode codepoints as comment reactions
    return [
      '👍',
      '👎',
      '👀',
      '✅',
      '❌',
      '🙏',
      '👏',
      '🎉',
      '🚀',
      '😊',
      '🤔',
      '😔'];
  }
})

Template.commentReactions.helpers({
  isSelected(userIds) {
    return userIds.includes(Meteor.user()._id);
  },
  userNames(userIds) {
    return Users.find({_id: {$in: userIds}})
                .map(user => user.profile.fullname)
                .join(', ');
  }
})

function createCardLink(card) {
  if (!card) return '';
  return (
    card &&
    Blaze.toHTML(
      HTML.A(
        {
          href: card.originRelativeUrl(),
          class: 'action-card',
        },
        DOMPurify.sanitize(card.title, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      ),
    )
  );
}

function createBoardLink(board, list) {
  let text = board.title;
  if (list) text += `: ${list}`;
  return (
    board &&
    Blaze.toHTML(
      HTML.A(
        {
          href: board.originRelativeUrl(),
          class: 'action-board',
        },
        DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      ),
    )
  );
}
<commit_msg>Fixed typo.<commit_after>import DOMPurify from 'dompurify';

const activitiesPerPage = 500;

BlazeComponent.extendComponent({
  onCreated() {
    // XXX Should we use ReactiveNumber?
    this.page = new ReactiveVar(1);
    this.loadNextPageLocked = false;
    // TODO is sidebar always available? E.g. on small screens/mobile devices
    const sidebar = Sidebar;
    sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
    this.autorun(() => {
      let mode = this.data().mode;
      const capitalizedMode = Utils.capitalize(mode);
      let searchId;
      if (mode === 'linkedcard' || mode === 'linkedboard') {
        searchId = Utils.getCurrentCard().linkedId;
        mode = mode.replace('linked', '');
      } else if (mode === 'card') {
        searchId = Utils.getCurrentCardId();
      } else {
        searchId = Session.get(`current${capitalizedMode}`);
      }
      const limit = this.page.get() * activitiesPerPage;
      const user = Meteor.user();
      const hideSystem = user ? user.hasHiddenSystemMessages() : false;
      if (searchId === null) return;

      this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
        this.loadNextPageLocked = false;

        // TODO the guard can be removed as soon as the TODO above is resolved
        if (!sidebar) return;
        // If the sibear peak hasn't increased, that mean that there are no more
        // activities, and we can stop calling new subscriptions.
        // XXX This is hacky! We need to know excatly and reactively how many
        // activities there are, we probably want to denormalize this number
        // dirrectly into card and board documents.
        const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
        sidebar.calculateNextPeak();
        const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
        if (nextPeakBefore === nextPeakAfter) {
          sidebar.callFirstWith(null, 'resetNextPeak');
        }
      });
    });
  },
  loadNextPage() {
    if (this.loadNextPageLocked === false) {
      this.page.set(this.page.get() + 1);
      this.loadNextPageLocked = true;
    }
  },
}).register('activities');

Template.activities.helpers({
  activities() {
    const ret = this.card.activities();
    return ret;
  },
});

BlazeComponent.extendComponent({
  checkItem() {
    const checkItemId = this.currentData().activity.checklistItemId;
    const checkItem = ChecklistItems.findOne({ _id: checkItemId });
    return checkItem && checkItem.title;
  },

  boardLabelLink() {
    const data = this.currentData();
    if (data.mode !== 'board') {
      return createBoardLink(data.activity.board(), data.activity.listName);
    }
    return TAPi18n.__('this-board');
  },

  cardLabelLink() {
    const data = this.currentData();
    if (data.mode !== 'card') {
      return createCardLink(data.activity.card());
    }
    return TAPi18n.__('this-card');
  },

  cardLink() {
    return createCardLink(this.currentData().activity.card());
  },

  receivedDate() {
    const receivedDate = this.currentData().activity.card();
    if (!receivedDate) return null;
    return receivedDate.receivedAt;
  },

  startDate() {
    const startDate = this.currentData().activity.card();
    if (!startDate) return null;
    return startDate.startAt;
  },

  dueDate() {
    const dueDate = this.currentData().activity.card();
    if (!dueDate) return null;
    return dueDate.dueAt;
  },

  endDate() {
    const endDate = this.currentData().activity.card();
    if (!endDate) return null;
    return endDate.endAt;
  },

  lastLabel() {
    const lastLabelId = this.currentData().activity.labelId;
    if (!lastLabelId) return null;
    const lastLabel = Boards.findOne(
      this.currentData().activity.boardId,
    ).getLabelById(lastLabelId);
    if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
      return lastLabel.color;
    } else if (lastLabel.name !== undefined && lastLabel.name !== '') {
      return lastLabel.name;
    } else {
      return null;
    }
  },

  lastCustomField() {
    const lastCustomField = CustomFields.findOne(
      this.currentData().activity.customFieldId,
    );
    if (!lastCustomField) return null;
    return lastCustomField.name;
  },

  lastCustomFieldValue() {
    const lastCustomField = CustomFields.findOne(
      this.currentData().activity.customFieldId,
    );
    if (!lastCustomField) return null;
    const value = this.currentData().activity.value;
    if (
      lastCustomField.settings.dropdownItems &&
      lastCustomField.settings.dropdownItems.length > 0
    ) {
      const dropDownValue = _.find(
        lastCustomField.settings.dropdownItems,
        item => {
          return item._id === value;
        },
      );
      if (dropDownValue) return dropDownValue.name;
    }
    return value;
  },

  listLabel() {
    const activity = this.currentData().activity;
    const list = activity.list();
    return (list && list.title) || activity.title;
  },

  sourceLink() {
    const source = this.currentData().activity.source;
    if (source) {
      if (source.url) {
        return Blaze.toHTML(
          HTML.A(
            {
              href: source.url,
            },
            DOMPurify.sanitize(source.system, {
              ALLOW_UNKNOWN_PROTOCOLS: true,
            }),
          ),
        );
      } else {
        return DOMPurify.sanitize(source.system, {
          ALLOW_UNKNOWN_PROTOCOLS: true,
        });
      }
    }
    return null;
  },

  memberLink() {
    return Blaze.toHTMLWithData(Template.memberName, {
      user: this.currentData().activity.member(),
    });
  },

  attachmentLink() {
    const attachment = this.currentData().activity.attachment();
    // trying to display url before file is stored generates js errors
    return (
      (attachment &&
        attachment.url({ download: true }) &&
        Blaze.toHTML(
          HTML.A(
            {
              href: attachment.url({ download: true }),
              target: '_blank',
            },
            DOMPurify.sanitize(attachment.name()),
          ),
        )) ||
      DOMPurify.sanitize(this.currentData().activity.attachmentName)
    );
  },

  customField() {
    const customField = this.currentData().activity.customField();
    if (!customField) return null;
    return customField.name;
  },

  events() {
    return [
      {
        // XXX We should use Popup.afterConfirmation here
        'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
          const commentId = this.data().activity.commentId;
          CardComments.remove(commentId);
          Popup.back();
        }),
        'submit .js-edit-comment'(evt) {
          evt.preventDefault();
          const commentText = this.currentComponent()
            .getValue()
            .trim();
          const commentId = Template.parentData().activity.commentId;
          if (commentText) {
            CardComments.update(commentId, {
              $set: {
                text: commentText,
              },
            });
          }
        },
      },
    ];
  },
}).register('activity');

Template.activity.helpers({
  sanitize(value) {
    return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true });
  },
});

Template.commentReactions.events({
  'click .reaction'(event) {
    if (Meteor.user().isBoardMember()) {
      const codepoint = event.currentTarget.dataset['codepoint'];
      const commentId = Template.instance().data.commentId;
      const cardComment = CardComments.findOne({_id: commentId});
      cardComment.toggleReaction(codepoint);
    }
  },
  'click .open-comment-reaction-popup': Popup.open('addReaction'),
})

Template.addReactionPopup.events({
  'click .add-comment-reaction'(event) {
    if (Meteor.user().isBoardMember()) {
      const codepoint = event.currentTarget.dataset['codepoint'];
      const commentId = Template.instance().data.commentId;
      const cardComment = CardComments.findOne({_id: commentId});
      cardComment.toggleReaction(codepoint);
    }
    Popup.back();
  },
})

Template.addReactionPopup.helpers({
  codepoints() {
    // Starting set of unicode codepoints as comment reactions
    return [
      '👍',
      '👎',
      '👀',
      '✅',
      '❌',
      '🙏',
      '👏',
      '🎉',
      '🚀',
      '😊',
      '🤔',
      '😔'];
  }
})

Template.commentReactions.helpers({
  isSelected(userIds) {
    return userIds.includes(Meteor.user()._id);
  },
  userNames(userIds) {
    return Users.find({_id: {$in: userIds}})
                .map(user => user.profile.fullname)
                .join(', ');
  }
})

function createCardLink(card) {
  if (!card) return '';
  return (
    card &&
    Blaze.toHTML(
      HTML.A(
        {
          href: card.originRelativeUrl(),
          class: 'action-card',
        },
        DOMPurify.sanitize(card.title, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      ),
    )
  );
}

function createBoardLink(board, list) {
  let text = board.title;
  if (list) text += `: ${list}`;
  return (
    board &&
    Blaze.toHTML(
      HTML.A(
        {
          href: board.originRelativeUrl(),
          class: 'action-board',
        },
        DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      ),
    )
  );
}
<|endoftext|>


<commit_before>import React from "react";
import { connect } from "react-redux";
import { push } from "connected-react-router";
import { routerLocationHasState } from "../../utils/utils";
import { removeSample } from "../actions";
import { RemoveModal } from "../../base";

export const RemoveSample = ({ id, name, show, onHide, onConfirm }) => (
    <RemoveModal noun="OTU" name={name} show={show} onConfirm={() => onConfirm(id)} onHide={onHide} />
);

const mapStateToProps = state => {
    const { id, name } = state.samples.detail;
    return {
        show: routerLocationHasState(state, "removeSample"),
        id,
        name
    };
};

const mapDispatchToProps = dispatch => ({
    onConfirm: sampleId => {
        dispatch(removeSample(sampleId));
    },
    onHide: () => {
        dispatch(push({ state: { removeSample: false } }));
    }
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(RemoveSample);
<commit_msg>Use correct noun for sample remove modal<commit_after>import React from "react";
import { connect } from "react-redux";
import { push } from "connected-react-router";
import { routerLocationHasState } from "../../utils/utils";
import { removeSample } from "../actions";
import { RemoveModal } from "../../base";

export const RemoveSample = ({ id, name, show, onHide, onConfirm }) => (
    <RemoveModal noun="Sample" name={name} show={show} onConfirm={() => onConfirm(id)} onHide={onHide} />
);

const mapStateToProps = state => {
    const { id, name } = state.samples.detail;
    return {
        show: routerLocationHasState(state, "removeSample"),
        id,
        name
    };
};

const mapDispatchToProps = dispatch => ({
    onConfirm: sampleId => {
        dispatch(removeSample(sampleId));
    },
    onHide: () => {
        dispatch(push({ state: { removeSample: false } }));
    }
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(RemoveSample);
<|endoftext|>


<commit_before>Template.compareWrapper.helpers({

  // 订阅recheckItem，并且推动compareData和baseData
  itemData: function(){
    // 订阅recheckItem
    var item = Session.get('recheckItem');

    // 当前状态为列表内容被修改, compareStatus为全局变量, set前应先更新状态！
    // compareStatus = 0;

    // 推动compareData和baseData
    Session.set('recheckBaseVersion', 'master');
    Session.set('recheckCompareVersion', 'current');

    return 1;
  },

  //提供对照用的数据
  baseData: function(){
    // 订阅对照版本号baseVersion
    var baseVersion = Session.get('recheckBaseVersion');

    // 线上版本时 (baseVersion == 'master')
    var item = Session.get('recheckItem');
    var db = item.ns.split('.')[0];
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var conn = getMongoClient(db, coll);
    var detailInfo = conn.findOne({_id: new Mongo.ObjectID(mid)});

    var baseData = [];

    console.log(detailInfo);

    // 处理原始的base数据
    review(coll, detailInfo, baseData);

    // 添加readonly标志
    for(i = 0;i < baseData.length;i++){
      baseData[i].notWrite = true;
    }

    console.log(baseData);

    // 存储base数据到session中
    Session.set('recheckBaseData', baseData);
    return baseData;
  },

  basePic: function(){
    // 订阅对照版本号baseVersion
    var baseVersion = Session.get('recheckBaseVersion');

    // 线上版本时 (baseVersion == 'master')
    var item = Session.get('recheckItem');
    var db = item.ns.split('.')[0];
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var conn = getMongoClient(db, coll);
    var detailInfo = conn.findOne({_id: new Mongo.ObjectID(mid)});

    var images = detailInfo.images;
    if (images){
      for (i = 0;i < images.length;i++){
        images[i].url = pictures_host + images[i].key;
      }
    }
    return images;
  },

  //提供编辑过的数据
	compareData: function(){
    // 订阅编辑版本号compareVersion
    var compareVersion = Session.get('recheckCompareVersion');

    // 当前版本时 (compareVersion == 'current')
    var item = Session.get('recheckItem');
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    // 订阅数据，以供snapshot使用,snapshot只会订阅cmsoplog，这部分应该放在rechek.js中，因为订阅一次即可?

    // 所有的数据库发布detail时，命名应为 coll.toLowerCase + "Detail" 的形式！
    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var detailInfo = storageEngine.snapshot(item.ns, new Mongo.ObjectID(item.pk));
    var compareData = [];

    console.log(detailInfo);

    // 处理原始的compare数据
    review(coll, detailInfo, compareData);

    console.log(compareData);

    // 存储compare数据到session中
    Session.set('recheckCompareData', compareData);

    // 存储数据的MD5值，在提交修改时判重使用
    createOriginTextMD5(compareData);

    return compareData;
  },

  comparePic: function(){
    var compareVersion = Session.get('recheckCompareVersion');

    // 当前版本时 (compareVersion == 'current')
    var item = Session.get('recheckItem');
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    // 订阅数据，以供snapshot使用,snapshot只会订阅cmsoplog，这部分应该放在rechek.js中，因为订阅一次即可?

    // 所有的数据库发布detail时，命名应为 coll.toLowerCase + "Detail" 的形式！
    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var detailInfo = storageEngine.snapshot(item.ns, new Mongo.ObjectID(item.pk));

    var images = detailInfo.images;
    if (images){
      for (i = 0;i < images.length;i++){
        images[i].url = pictures_host + images[i].key;
      }
    }
    return images;
  },

  // 提供diff数据
  diffData: function(){
    // 取出两组数据
    var baseData =  Session.get('recheckBaseData');
    var compareData = Session.get('recheckCompareData');

    // 获取两个数据的区别，以html的形式返回字符串
    var dataLength = baseData.length;
    var diff, diffData = [];
    var tempBase, tempCompare;
    for (var i = 0;i < dataLength;i++){

      //初始化diffData
      diffData[i] = {};
      diffData[i].value = [];
      if (compareData[i].zhLabel)
        diffData[i].zhLabel = compareData[i].zhLabel;

      tempBase = baseData[i].value;
      tempCompare = compareData[i].value;

      //如果是str_Array
      if (_.isArray(baseData[i].value)){
        //字符串数组转字符串
        console.log(baseData[i].value);
        tempBase = baseData[i].value.join();
        console.log(compareData[i].value);
        tempCompare = compareData[i].value.join();
      }

      //整型转字符串 may be need
      //
      //

      // 调用第三方模块jsDiff
      diff = JsDiff.diffChars(tempBase, tempCompare);
      diff.forEach(function(part){
        var className =
              part.added ? 'added' :
                part.removed ? 'removed' : 'common';
        diffData[i].value.push({
          diffClass: className,
          diffValue: part.value
        });
      });
    }
    return diffData;
  }
});

Template.compareWrapper.events({
  //保存非富文本框的修改
  "blur textarea": function(e){
    var recheckCompareData = Session.get("recheckCompareData");
    for (i = 0;i < recheckCompareData.length;i++) {
      if (recheckCompareData[i].keyChain == this.keyChain) {
        var curText = (this.strArray) ? $(e.target).val().split(',') : $(e.target).val();
        recheckCompareData[i].value = curText;
        Session.set("recheckCompareData", recheckCompareData);
        break;
      }
    }
  },

  //保存富文本框的修改
  'blur .froala-view': function(e){
    var recheckCompareData = Session.get("recheckCompareData");
    for (i = 0;i < recheckCompareData.length;i++) {
      if (recheckCompareData[i].keyChain == this.keyChain) {
        var curHTML = $(e.target).html();
        recheckCompareData[i].value = curHTML;
        Session.set("recheckCompareData", recheckCompareData);
        break;
      }
    }
  },

  'click #submit-info': function(e) {
    e.preventDefault();
    log('上传数据!');
    var item = Session.get('recheckItem');
    submitOplog(item.ns, item.pk);
    //将线上数据进行修改！
  },

  'click #edit-pic-btn': function(e) {
    var item = Session.get('recheckItem');
    window.location.href = "/" + item.ns.split('.')[1] + "/" + item.pk;
  }
})<commit_msg>window open<commit_after>Template.compareWrapper.helpers({

  // 订阅recheckItem，并且推动compareData和baseData
  itemData: function(){
    // 订阅recheckItem
    var item = Session.get('recheckItem');

    // 当前状态为列表内容被修改, compareStatus为全局变量, set前应先更新状态！
    // compareStatus = 0;

    // 推动compareData和baseData
    Session.set('recheckBaseVersion', 'master');
    Session.set('recheckCompareVersion', 'current');

    return 1;
  },

  //提供对照用的数据
  baseData: function(){
    // 订阅对照版本号baseVersion
    var baseVersion = Session.get('recheckBaseVersion');

    // 线上版本时 (baseVersion == 'master')
    var item = Session.get('recheckItem');
    var db = item.ns.split('.')[0];
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var conn = getMongoClient(db, coll);
    var detailInfo = conn.findOne({_id: new Mongo.ObjectID(mid)});

    var baseData = [];

    console.log(detailInfo);

    // 处理原始的base数据
    review(coll, detailInfo, baseData);

    // 添加readonly标志
    for(i = 0;i < baseData.length;i++){
      baseData[i].notWrite = true;
    }

    console.log(baseData);

    // 存储base数据到session中
    Session.set('recheckBaseData', baseData);
    return baseData;
  },

  basePic: function(){
    // 订阅对照版本号baseVersion
    var baseVersion = Session.get('recheckBaseVersion');

    // 线上版本时 (baseVersion == 'master')
    var item = Session.get('recheckItem');
    var db = item.ns.split('.')[0];
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var conn = getMongoClient(db, coll);
    var detailInfo = conn.findOne({_id: new Mongo.ObjectID(mid)});

    var images = detailInfo.images;
    if (images){
      for (i = 0;i < images.length;i++){
        images[i].url = pictures_host + images[i].key;
      }
    }
    return images;
  },

  //提供编辑过的数据
	compareData: function(){
    // 订阅编辑版本号compareVersion
    var compareVersion = Session.get('recheckCompareVersion');

    // 当前版本时 (compareVersion == 'current')
    var item = Session.get('recheckItem');
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    // 订阅数据，以供snapshot使用,snapshot只会订阅cmsoplog，这部分应该放在rechek.js中，因为订阅一次即可?

    // 所有的数据库发布detail时，命名应为 coll.toLowerCase + "Detail" 的形式！
    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var detailInfo = storageEngine.snapshot(item.ns, new Mongo.ObjectID(item.pk));
    var compareData = [];

    console.log(detailInfo);

    // 处理原始的compare数据
    review(coll, detailInfo, compareData);

    console.log(compareData);

    // 存储compare数据到session中
    Session.set('recheckCompareData', compareData);

    // 存储数据的MD5值，在提交修改时判重使用
    createOriginTextMD5(compareData);

    return compareData;
  },

  comparePic: function(){
    var compareVersion = Session.get('recheckCompareVersion');

    // 当前版本时 (compareVersion == 'current')
    var item = Session.get('recheckItem');
    var coll = item.ns.split('.')[1];
    var mid = item.pk;

    // 订阅数据，以供snapshot使用,snapshot只会订阅cmsoplog，这部分应该放在rechek.js中，因为订阅一次即可?

    // 所有的数据库发布detail时，命名应为 coll.toLowerCase + "Detail" 的形式！
    Meteor.subscribe(coll.toLowerCase() + "Detail", mid);

    var detailInfo = storageEngine.snapshot(item.ns, new Mongo.ObjectID(item.pk));

    var images = detailInfo.images;
    if (images){
      for (i = 0;i < images.length;i++){
        images[i].url = pictures_host + images[i].key;
      }
    }
    return images;
  },

  // 提供diff数据
  diffData: function(){
    // 取出两组数据
    var baseData =  Session.get('recheckBaseData');
    var compareData = Session.get('recheckCompareData');

    // 获取两个数据的区别，以html的形式返回字符串
    var dataLength = baseData.length;
    var diff, diffData = [];
    var tempBase, tempCompare;
    for (var i = 0;i < dataLength;i++){

      //初始化diffData
      diffData[i] = {};
      diffData[i].value = [];
      if (compareData[i].zhLabel)
        diffData[i].zhLabel = compareData[i].zhLabel;

      tempBase = baseData[i].value;
      tempCompare = compareData[i].value;

      //如果是str_Array
      if (_.isArray(baseData[i].value)){
        //字符串数组转字符串
        console.log(baseData[i].value);
        tempBase = baseData[i].value.join();
        console.log(compareData[i].value);
        tempCompare = compareData[i].value.join();
      }

      //整型转字符串 may be need
      //
      //

      // 调用第三方模块jsDiff
      diff = JsDiff.diffChars(tempBase, tempCompare);
      diff.forEach(function(part){
        var className =
              part.added ? 'added' :
                part.removed ? 'removed' : 'common';
        diffData[i].value.push({
          diffClass: className,
          diffValue: part.value
        });
      });
    }
    return diffData;
  }
});

Template.compareWrapper.events({
  //保存非富文本框的修改
  "blur textarea": function(e){
    var recheckCompareData = Session.get("recheckCompareData");
    for (i = 0;i < recheckCompareData.length;i++) {
      if (recheckCompareData[i].keyChain == this.keyChain) {
        var curText = (this.strArray) ? $(e.target).val().split(',') : $(e.target).val();
        recheckCompareData[i].value = curText;
        Session.set("recheckCompareData", recheckCompareData);
        break;
      }
    }
  },

  //保存富文本框的修改
  'blur .froala-view': function(e){
    var recheckCompareData = Session.get("recheckCompareData");
    for (i = 0;i < recheckCompareData.length;i++) {
      if (recheckCompareData[i].keyChain == this.keyChain) {
        var curHTML = $(e.target).html();
        recheckCompareData[i].value = curHTML;
        Session.set("recheckCompareData", recheckCompareData);
        break;
      }
    }
  },

  'click #submit-info': function(e) {
    e.preventDefault();
    log('上传数据!');
    var item = Session.get('recheckItem');
    submitOplog(item.ns, item.pk);
    //将线上数据进行修改！
  },

  'click #edit-pic-btn': function(e) {
    var item = Session.get('recheckItem');
    // window.location.href = "/" + item.ns.split('.')[1] + "/" + item.pk;
    window.open("/" + item.ns.split('.')[1] + "/" + item.pk);
  }
})<|endoftext|>