<commit_before>/**
@module ember
@submodule ember-runtime
*/

// require('ember-runtime/mixins/array');
// require('ember-runtime/mixins/mutable_enumerable');

// ..........................................................
// CONSTANTS
//

var OUT_OF_RANGE_EXCEPTION = "Index out of range";
var EMPTY = [];

// ..........................................................
// HELPERS
//

import { get } from "ember-metal/property_get";
import { set } from "ember-metal/property_set";
import { isArray } from "ember-metal/utils";
import EmberError from "ember-metal/error";
import {
  Mixin,
  required
} from "ember-metal/mixin";
import EmberArray from "ember-runtime/mixins/array";
import MutableEnumerable from "ember-runtime/mixins/mutable_enumerable";
import Enumerable from "ember-runtime/mixins/enumerable";
/**
  This mixin defines the API for modifying array-like objects. These methods
  can be applied only to a collection that keeps its items in an ordered set.
  It builds upon the Array mixin and adds methods to modify the array.
  Concrete implementations of this class include ArrayProxy and ArrayController.

  It is important to use the methods in this class to modify arrays so that
  changes are observable. This allows the binding system in Ember to function
  correctly.

  Note that an Array can change even if it does not implement this mixin.
  For example, one might implement a SparseArray that cannot be directly
  modified, but if its underlying enumerable changes, it will change also.

  @class MutableArray
  @namespace Ember
  @uses Ember.Array
  @uses Ember.MutableEnumerable
*/
export default Mixin.create(EmberArray, MutableEnumerable, {

  /**
    __Required.__ You must implement this method to apply this mixin.

    This is one of the primitives you must implement to support `Ember.Array`.
    You should replace amt objects started at idx with the objects in the
    passed array. You should also call `this.enumerableContentDidChange()`

    @method replace
    @param {Number} idx Starting index in the array to replace. If
      idx >= length, then append to the end of the array.
    @param {Number} amt Number of elements that should be removed from
      the array, starting at *idx*.
    @param {Array} objects An array of zero or more objects that should be
      inserted into the array at *idx*
  */
  replace: required(),

  /**
    Remove all elements from the array. This is useful if you
    want to reuse an existing array without having to recreate it.

    ```javascript
    var colors = ["red", "green", "blue"];
    color.length();   //  3
    colors.clear();   //  []
    colors.length();  //  0
    ```

    @method clear
    @return {Ember.Array} An empty Array.
  */
  clear: function () {
    var len = get(this, 'length');
    if (len === 0) return this;
    this.replace(0, len, EMPTY);
    return this;
  },

  /**
    This will use the primitive `replace()` method to insert an object at the
    specified index.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.insertAt(2, "yellow");  // ["red", "green", "yellow", "blue"]
    colors.insertAt(5, "orange");  // Error: Index out of range
    ```

    @method insertAt
    @param {Number} idx index of insert the object at.
    @param {Object} object object to insert
    @return {Ember.Array} receiver
  */
  insertAt: function(idx, object) {
    if (idx > get(this, 'length')) throw new EmberError(OUT_OF_RANGE_EXCEPTION);
    this.replace(idx, 0, [object]);
    return this;
  },

  /**
    Remove an object at the specified index using the `replace()` primitive
    method. You can pass either a single index, or a start and a length.

    If you pass a start and length that is beyond the
    length this method will throw an `OUT_OF_RANGE_EXCEPTION`.

    ```javascript
    var colors = ["red", "green", "blue", "yellow", "orange"];
    colors.removeAt(0);     // ["green", "blue", "yellow", "orange"]
    colors.removeAt(2, 2);  // ["green", "blue"]
    colors.removeAt(4, 2);  // Error: Index out of range
    ```

    @method removeAt
    @param {Number} start index, start of range
    @param {Number} len length of passing range
    @return {Ember.Array} receiver
  */
  removeAt: function(start, len) {
    if ('number' === typeof start) {

      if ((start < 0) || (start >= get(this, 'length'))) {
        throw new EmberError(OUT_OF_RANGE_EXCEPTION);
      }

      // fast case
      if (len === undefined) len = 1;
      this.replace(start, len, EMPTY);
    }

    return this;
  },

  /**
    Push the object onto the end of the array. Works just like `push()` but it
    is KVO-compliant.

    ```javascript
    var colors = ["red", "green"];
    colors.pushObject("black");     // ["red", "green", "black"]
    colors.pushObject(["yellow"]);  // ["red", "green", ["yellow"]]
    ```

    @method pushObject
    @param {*} obj object to push
    @return object same object passed as a param
  */
  pushObject: function(obj) {
    this.insertAt(get(this, 'length'), obj);
    return obj;
  },

  /**
    Add the objects in the passed numerable to the end of the array. Defers
    notifying observers of the change until all objects are added.

    ```javascript
    var colors = ["red"];
    colors.pushObjects(["yellow", "orange"]);  // ["red", "yellow", "orange"]
    ```

    @method pushObjects
    @param {Ember.Enumerable} objects the objects to add
    @return {Ember.Array} receiver
  */
  pushObjects: function(objects) {
    if (!(Enumerable.detect(objects) || isArray(objects))) {
      throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
    }
    this.replace(get(this, 'length'), 0, objects);
    return this;
  },

  /**
    Pop object from array or nil if none are left. Works just like `pop()` but
    it is KVO-compliant.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.popObject();   // "blue"
    console.log(colors);  // ["red", "green"]
    ```

    @method popObject
    @return object
  */
  popObject: function() {
    var len = get(this, 'length');
    if (len === 0) return null;

    var ret = this.objectAt(len-1);
    this.removeAt(len-1, 1);
    return ret;
  },

  /**
    Shift an object from start of array or nil if none are left. Works just
    like `shift()` but it is KVO-compliant.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.shiftObject();  // "red"
    console.log(colors);   // ["green", "blue"]
    ```

    @method shiftObject
    @return object
  */
  shiftObject: function() {
    if (get(this, 'length') === 0) return null;
    var ret = this.objectAt(0);
    this.removeAt(0);
    return ret;
  },

  /**
    Unshift an object to start of array. Works just like `unshift()` but it is
    KVO-compliant.

    ```javascript
    var colors = ["red"];
    colors.unshiftObject("yellow");    // ["yellow", "red"]
    colors.unshiftObject(["black"]);   // [["black"], "yellow", "red"]
    ```

    @method unshiftObject
    @param {*} obj object to unshift
    @return object same object passed as a param
  */
  unshiftObject: function(obj) {
    this.insertAt(0, obj);
    return obj;
  },

  /**
    Adds the named objects to the beginning of the array. Defers notifying
    observers until all objects have been added.

    ```javascript
    var colors = ["red"];
    colors.unshiftObjects(["black", "white"]);   // ["black", "white", "red"]
    colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
    ```

    @method unshiftObjects
    @param {Ember.Enumerable} objects the objects to add
    @return {Ember.Array} receiver
  */
  unshiftObjects: function(objects) {
    this.replace(0, 0, objects);
    return this;
  },

  /**
    Reverse objects in the array. Works just like `reverse()` but it is
    KVO-compliant.

    @method reverseObjects
    @return {Ember.Array} receiver
   */
  reverseObjects: function() {
    var len = get(this, 'length');
    if (len === 0) return this;
    var objects = this.toArray().reverse();
    this.replace(0, len, objects);
    return this;
  },

  /**
    Replace all the the receiver's content with content of the argument.
    If argument is an empty array receiver will be cleared.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.setObjects(["black", "white"]);  // ["black", "white"]
    colors.setObjects([]);                  // []
    ```

    @method setObjects
    @param {Ember.Array} objects array whose content will be used for replacing
        the content of the receiver
    @return {Ember.Array} receiver with the new content
   */
  setObjects: function(objects) {
    if (objects.length === 0) return this.clear();

    var len = get(this, 'length');
    this.replace(0, len, objects);
    return this;
  },

  // ..........................................................
  // IMPLEMENT Ember.MutableEnumerable
  //

  /**
    Remove all occurances of an object in the array.

    ```javascript
    var cities = ["Chicago", "Berlin", "Lima", "Chicago"];
    cities.removeObject("Chicago");  // ["Berlin", "Lima"]
    cities.removeObject("Lima");     // ["Berlin"]
    cities.removeObject("Tokyo")     // ["Berlin"]
    ```

    @method removeObject
    @param {*} obj object to remove
    @return {Ember.Array} receiver
  */
  removeObject: function(obj) {
    var loc = get(this, 'length') || 0;
    while(--loc >= 0) {
      var curObject = this.objectAt(loc);
      if (curObject === obj) this.removeAt(loc);
    }
    return this;
  },

  /**
    Push the object onto the end of the array if it is not already
    present in the array.

    ```javascript
    var cities = ["Chicago", "Berlin"];
    cities.addObject("Lima");    // ["Chicago", "Berlin", "Lima"]
    cities.addObject("Berlin");  // ["Chicago", "Berlin", "Lima"]
    ```

    @method addObject
    @param {*} obj object to add, if not already present
    @return {Ember.Array} receiver
  */
  addObject: function(obj) {
    if (!this.contains(obj)) this.pushObject(obj);
    return this;
  }

});
<commit_msg>[DOC] Change 'the the' to 'the'<commit_after>/**
@module ember
@submodule ember-runtime
*/

// require('ember-runtime/mixins/array');
// require('ember-runtime/mixins/mutable_enumerable');

// ..........................................................
// CONSTANTS
//

var OUT_OF_RANGE_EXCEPTION = "Index out of range";
var EMPTY = [];

// ..........................................................
// HELPERS
//

import { get } from "ember-metal/property_get";
import { set } from "ember-metal/property_set";
import { isArray } from "ember-metal/utils";
import EmberError from "ember-metal/error";
import {
  Mixin,
  required
} from "ember-metal/mixin";
import EmberArray from "ember-runtime/mixins/array";
import MutableEnumerable from "ember-runtime/mixins/mutable_enumerable";
import Enumerable from "ember-runtime/mixins/enumerable";
/**
  This mixin defines the API for modifying array-like objects. These methods
  can be applied only to a collection that keeps its items in an ordered set.
  It builds upon the Array mixin and adds methods to modify the array.
  Concrete implementations of this class include ArrayProxy and ArrayController.

  It is important to use the methods in this class to modify arrays so that
  changes are observable. This allows the binding system in Ember to function
  correctly.

  Note that an Array can change even if it does not implement this mixin.
  For example, one might implement a SparseArray that cannot be directly
  modified, but if its underlying enumerable changes, it will change also.

  @class MutableArray
  @namespace Ember
  @uses Ember.Array
  @uses Ember.MutableEnumerable
*/
export default Mixin.create(EmberArray, MutableEnumerable, {

  /**
    __Required.__ You must implement this method to apply this mixin.

    This is one of the primitives you must implement to support `Ember.Array`.
    You should replace amt objects started at idx with the objects in the
    passed array. You should also call `this.enumerableContentDidChange()`

    @method replace
    @param {Number} idx Starting index in the array to replace. If
      idx >= length, then append to the end of the array.
    @param {Number} amt Number of elements that should be removed from
      the array, starting at *idx*.
    @param {Array} objects An array of zero or more objects that should be
      inserted into the array at *idx*
  */
  replace: required(),

  /**
    Remove all elements from the array. This is useful if you
    want to reuse an existing array without having to recreate it.

    ```javascript
    var colors = ["red", "green", "blue"];
    color.length();   //  3
    colors.clear();   //  []
    colors.length();  //  0
    ```

    @method clear
    @return {Ember.Array} An empty Array.
  */
  clear: function () {
    var len = get(this, 'length');
    if (len === 0) return this;
    this.replace(0, len, EMPTY);
    return this;
  },

  /**
    This will use the primitive `replace()` method to insert an object at the
    specified index.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.insertAt(2, "yellow");  // ["red", "green", "yellow", "blue"]
    colors.insertAt(5, "orange");  // Error: Index out of range
    ```

    @method insertAt
    @param {Number} idx index of insert the object at.
    @param {Object} object object to insert
    @return {Ember.Array} receiver
  */
  insertAt: function(idx, object) {
    if (idx > get(this, 'length')) throw new EmberError(OUT_OF_RANGE_EXCEPTION);
    this.replace(idx, 0, [object]);
    return this;
  },

  /**
    Remove an object at the specified index using the `replace()` primitive
    method. You can pass either a single index, or a start and a length.

    If you pass a start and length that is beyond the
    length this method will throw an `OUT_OF_RANGE_EXCEPTION`.

    ```javascript
    var colors = ["red", "green", "blue", "yellow", "orange"];
    colors.removeAt(0);     // ["green", "blue", "yellow", "orange"]
    colors.removeAt(2, 2);  // ["green", "blue"]
    colors.removeAt(4, 2);  // Error: Index out of range
    ```

    @method removeAt
    @param {Number} start index, start of range
    @param {Number} len length of passing range
    @return {Ember.Array} receiver
  */
  removeAt: function(start, len) {
    if ('number' === typeof start) {

      if ((start < 0) || (start >= get(this, 'length'))) {
        throw new EmberError(OUT_OF_RANGE_EXCEPTION);
      }

      // fast case
      if (len === undefined) len = 1;
      this.replace(start, len, EMPTY);
    }

    return this;
  },

  /**
    Push the object onto the end of the array. Works just like `push()` but it
    is KVO-compliant.

    ```javascript
    var colors = ["red", "green"];
    colors.pushObject("black");     // ["red", "green", "black"]
    colors.pushObject(["yellow"]);  // ["red", "green", ["yellow"]]
    ```

    @method pushObject
    @param {*} obj object to push
    @return object same object passed as a param
  */
  pushObject: function(obj) {
    this.insertAt(get(this, 'length'), obj);
    return obj;
  },

  /**
    Add the objects in the passed numerable to the end of the array. Defers
    notifying observers of the change until all objects are added.

    ```javascript
    var colors = ["red"];
    colors.pushObjects(["yellow", "orange"]);  // ["red", "yellow", "orange"]
    ```

    @method pushObjects
    @param {Ember.Enumerable} objects the objects to add
    @return {Ember.Array} receiver
  */
  pushObjects: function(objects) {
    if (!(Enumerable.detect(objects) || isArray(objects))) {
      throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
    }
    this.replace(get(this, 'length'), 0, objects);
    return this;
  },

  /**
    Pop object from array or nil if none are left. Works just like `pop()` but
    it is KVO-compliant.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.popObject();   // "blue"
    console.log(colors);  // ["red", "green"]
    ```

    @method popObject
    @return object
  */
  popObject: function() {
    var len = get(this, 'length');
    if (len === 0) return null;

    var ret = this.objectAt(len-1);
    this.removeAt(len-1, 1);
    return ret;
  },

  /**
    Shift an object from start of array or nil if none are left. Works just
    like `shift()` but it is KVO-compliant.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.shiftObject();  // "red"
    console.log(colors);   // ["green", "blue"]
    ```

    @method shiftObject
    @return object
  */
  shiftObject: function() {
    if (get(this, 'length') === 0) return null;
    var ret = this.objectAt(0);
    this.removeAt(0);
    return ret;
  },

  /**
    Unshift an object to start of array. Works just like `unshift()` but it is
    KVO-compliant.

    ```javascript
    var colors = ["red"];
    colors.unshiftObject("yellow");    // ["yellow", "red"]
    colors.unshiftObject(["black"]);   // [["black"], "yellow", "red"]
    ```

    @method unshiftObject
    @param {*} obj object to unshift
    @return object same object passed as a param
  */
  unshiftObject: function(obj) {
    this.insertAt(0, obj);
    return obj;
  },

  /**
    Adds the named objects to the beginning of the array. Defers notifying
    observers until all objects have been added.

    ```javascript
    var colors = ["red"];
    colors.unshiftObjects(["black", "white"]);   // ["black", "white", "red"]
    colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
    ```

    @method unshiftObjects
    @param {Ember.Enumerable} objects the objects to add
    @return {Ember.Array} receiver
  */
  unshiftObjects: function(objects) {
    this.replace(0, 0, objects);
    return this;
  },

  /**
    Reverse objects in the array. Works just like `reverse()` but it is
    KVO-compliant.

    @method reverseObjects
    @return {Ember.Array} receiver
   */
  reverseObjects: function() {
    var len = get(this, 'length');
    if (len === 0) return this;
    var objects = this.toArray().reverse();
    this.replace(0, len, objects);
    return this;
  },

  /**
    Replace all the receiver's content with content of the argument.
    If argument is an empty array receiver will be cleared.

    ```javascript
    var colors = ["red", "green", "blue"];
    colors.setObjects(["black", "white"]);  // ["black", "white"]
    colors.setObjects([]);                  // []
    ```

    @method setObjects
    @param {Ember.Array} objects array whose content will be used for replacing
        the content of the receiver
    @return {Ember.Array} receiver with the new content
   */
  setObjects: function(objects) {
    if (objects.length === 0) return this.clear();

    var len = get(this, 'length');
    this.replace(0, len, objects);
    return this;
  },

  // ..........................................................
  // IMPLEMENT Ember.MutableEnumerable
  //

  /**
    Remove all occurances of an object in the array.

    ```javascript
    var cities = ["Chicago", "Berlin", "Lima", "Chicago"];
    cities.removeObject("Chicago");  // ["Berlin", "Lima"]
    cities.removeObject("Lima");     // ["Berlin"]
    cities.removeObject("Tokyo")     // ["Berlin"]
    ```

    @method removeObject
    @param {*} obj object to remove
    @return {Ember.Array} receiver
  */
  removeObject: function(obj) {
    var loc = get(this, 'length') || 0;
    while(--loc >= 0) {
      var curObject = this.objectAt(loc);
      if (curObject === obj) this.removeAt(loc);
    }
    return this;
  },

  /**
    Push the object onto the end of the array if it is not already
    present in the array.

    ```javascript
    var cities = ["Chicago", "Berlin"];
    cities.addObject("Lima");    // ["Chicago", "Berlin", "Lima"]
    cities.addObject("Berlin");  // ["Chicago", "Berlin", "Lima"]
    ```

    @method addObject
    @param {*} obj object to add, if not already present
    @return {Ember.Array} receiver
  */
  addObject: function(obj) {
    if (!this.contains(obj)) this.pushObject(obj);
    return this;
  }

});
<|endoftext|>


<commit_before>/**
 * External dependencies
 */
import { useMount } from 'react-use';
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
const { useSelect } = Data;

// TODO - in execution: https://github.com/google/site-kit-wp/issues/3355
const triggerSurvey = ( args ) => {
	// eslint-disable-next-line no-console
	console.log( ...args );
};

const SurveyViewTrigger = ( { triggerID, ttl } ) => {
	const usingProxy = useSelect( ( select ) => select( CORE_SITE ).isUsingProxy() );

	useMount( () => {
		if ( usingProxy ) {
			triggerSurvey( { triggerID, ttl } );
		}
	} );

	return null;
};

SurveyViewTrigger.propTypes = {
	triggerID: PropTypes.string.isRequired,
	ttl: PropTypes.number,
};

SurveyViewTrigger.defaultProps = {
	ttl: null,
};

export default SurveyViewTrigger;
<commit_msg>Dispatch real action now have rebased on WIP data branch.<commit_after>/**
 * External dependencies
 */
import { useMount } from 'react-use';
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';

const { useSelect, useDispatch } = Data;

const SurveyViewTrigger = ( { triggerID, ttl } ) => {
	const usingProxy = useSelect( ( select ) => select( CORE_SITE ).isUsingProxy() );

	const { triggerSurvey } = useDispatch( CORE_USER );

	useMount( () => {
		if ( usingProxy ) {
			triggerSurvey( { triggerID, ttl } );
		}
	} );

	return null;
};

SurveyViewTrigger.propTypes = {
	triggerID: PropTypes.string.isRequired,
	ttl: PropTypes.number,
};

SurveyViewTrigger.defaultProps = {
	ttl: null,
};

export default SurveyViewTrigger;
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: profiles.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { isValidAccountID, isValidPropertyID } from '../util';
import { PROFILE_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROFILE = 'FETCH_CREATE_PROFILE';
const FETCH_CREATE_PROFILE_STARTED = 'FETCH_CREATE_PROFILE_STARTED';
const FETCH_PROFILES = 'FETCH_PROFILES';
const FETCH_PROFILES_STARTED = 'FETCH_PROFILES_STARTED';
const RECEIVE_CREATE_PROFILE = 'RECEIVE_CREATE_PROFILE';
const RECEIVE_CREATE_PROFILE_FAILED = 'RECEIVE_CREATE_PROFILE_FAILED';
const RECEIVE_PROFILES = 'RECEIVE_PROFILES';
const RECEIVE_PROFILES_COMPLETED = 'RECEIVE_PROFILES_COMPLETED';
const RECEIVE_PROFILES_FAILED = 'RECEIVE_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProfile: {},
	isFetchingProfiles: {},
	profiles: {},
};

export const actions = {
	/**
	 * Creates a new Analytics profile.
	 *
	 * Creates a new Analytics profile for an existing Google Analytics
	 * account + property combination.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string} accountID  Google Analytics account ID.
	 * @param {string} propertyID Google Analytics property ID.
	 * @return {Function} Generator function action.
	 */
	*createProfile( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		try {
			const profile = yield actions.fetchCreateProfile( accountID, propertyID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setProfileID( profile.id );

			return actions.receiveCreateProfile( { accountID, propertyID, profile } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreateProfileFailed( { accountID, error, propertyID } );
		}
	},

	*fetchCreateProfile( accountID, propertyID ) {
		yield {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE_STARTED,
		};

		return {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE,
		};
	},

	*fetchProfiles( accountID, propertyID ) {
		yield {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES_STARTED,
		};

		return {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics profile ID.
	 * @param {Object} args.profile    Google Analytics profile object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfile( { accountID, propertyID, profile } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( profile, 'profile is required.' );

		return {
			payload: { accountID, propertyID, profile },
			type: RECEIVE_CREATE_PROFILE,
		};
	},

	/**
	 * Logs an error with profile creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics property ID.
	 * @param {Object} args.error      Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfileFailed( { accountID, propertyID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_CREATE_PROFILE_FAILED,
		};
	},

	receiveProfiles( profiles ) {
		invariant( Array.isArray( profiles ), 'profiles must be an array.' );

		return {
			payload: { profiles },
			type: RECEIVE_PROFILES,
		};
	},

	receiveProfilesCompleted( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, propertyID },
			type: RECEIVE_PROFILES_COMPLETED,
		};
	},

	receiveProfilesFailed( { accountID, error, propertyID } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_PROFILES_FAILED,
		};
	},

	/**
	 * Sets a profile based on given account and property IDs.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {?Object} args Optional supporting arguments.
	 * @param {?string} args.accountID Account ID.
	 * @param {?string} args.property Property ID.
	 */
	*setProfileForProperty( { accountID, propertyID } = {} ) {
		const registry = yield Data.commonActions.getRegistry();

		if ( isValidAccountID( accountID ) && isValidPropertyID( propertyID ) ) {
			const profiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID ) || [];
			const matchedProfile = profiles.find( ( { webPropertyId } ) => webPropertyId === propertyID ) || { id: PROFILE_CREATE };

			registry.dispatch( STORE_NAME ).setProfileID( matchedProfile.id );
		} else {
			registry.dispatch( STORE_NAME ).setProfileID( PROFILE_CREATE );
		}
	},
};

export const controls = {
	[ FETCH_CREATE_PROFILE ]: ( { payload: { accountID, propertyID } } ) => {
		return API.set( 'modules', 'analytics', 'create-profile', {
			accountID,
			propertyID,
		} );
	},
	[ FETCH_PROFILES ]: ( { payload: { accountID, propertyID } } ) => {
		return API.get( 'modules', 'analytics', 'profiles', {
			accountID,
			propertyID,
		}, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROFILE_STARTED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case FETCH_PROFILES_STARTED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROFILE: {
			const { accountID, propertyID, profile } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
				profiles: {
					...state.profiles,
					[ `${ accountID }::${ propertyID }` ]: [
						...( state.profiles[ `${ accountID }::${ propertyID }` ] || [] ),
						profile,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROFILE_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES: {
			const { profiles } = payload;

			return {
				...state,
				profiles: {
					...state.profiles,
					...groupBy( profiles, ( { accountId, webPropertyId } ) => `${ accountId }::${ webPropertyId }` ), // Capitalization rule exception: `accountId` and `webPropertyId` are properties of an API returned value.
				},
			};
		}

		case RECEIVE_PROFILES_COMPLETED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProfiles( accountID, propertyID ) {
		if ( ! isValidAccountID( accountID ) || ! isValidPropertyID( propertyID ) ) {
			return;
		}

		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProfiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

			// If there are already profiles loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProfiles ) {
				return;
			}

			const profiles = yield actions.fetchProfiles( accountID, propertyID );

			yield actions.receiveProfiles( profiles );

			return actions.receiveProfilesCompleted( accountID, propertyID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveProfilesFailed( { accountID, propertyID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics profiles this user account+property has available.
	 *
	 * Returns an array of all profiles.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to fetch profiles for.
	 * @param {string} propertyID The Analytics Property ID to fetch profiles for.
	 * @return {?Array.<Object>} An array of Analytics profiles; `undefined` if not loaded.
	 */
	getProfiles( state, accountID, propertyID ) {
		const { profiles } = state;

		return profiles[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Check if a profile is being created for an account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile creation.
	 * @param {string} propertyID The Analytics Property ID to check for profile creation.
	 * @return {boolean} `true` if creating a profile, `false` if not.
	 */
	isDoingCreateProfile( state, accountID, propertyID ) {
		const { isFetchingCreateProfile } = state;

		return !! isFetchingCreateProfile[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Checks if profiles are being fetched for the given account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile fetching.
	 * @param {string} propertyID The Analytics Property ID to check for profile fetching.
	 * @return {boolean} `true` if fetching a profiles, `false` if not.
	 */
	isDoingGetProfiles( state, accountID, propertyID ) {
		const { isFetchingProfiles } = state;

		return !! isFetchingProfiles[ `${ accountID }::${ propertyID }` ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Set profile if not set in getProfiles.<commit_after>/**
 * modules/analytics data store: profiles.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { isValidAccountID, isValidPropertyID } from '../util';
import { PROFILE_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROFILE = 'FETCH_CREATE_PROFILE';
const FETCH_CREATE_PROFILE_STARTED = 'FETCH_CREATE_PROFILE_STARTED';
const FETCH_PROFILES = 'FETCH_PROFILES';
const FETCH_PROFILES_STARTED = 'FETCH_PROFILES_STARTED';
const RECEIVE_CREATE_PROFILE = 'RECEIVE_CREATE_PROFILE';
const RECEIVE_CREATE_PROFILE_FAILED = 'RECEIVE_CREATE_PROFILE_FAILED';
const RECEIVE_PROFILES = 'RECEIVE_PROFILES';
const RECEIVE_PROFILES_COMPLETED = 'RECEIVE_PROFILES_COMPLETED';
const RECEIVE_PROFILES_FAILED = 'RECEIVE_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProfile: {},
	isFetchingProfiles: {},
	profiles: {},
};

export const actions = {
	/**
	 * Creates a new Analytics profile.
	 *
	 * Creates a new Analytics profile for an existing Google Analytics
	 * account + property combination.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string} accountID  Google Analytics account ID.
	 * @param {string} propertyID Google Analytics property ID.
	 * @return {Function} Generator function action.
	 */
	*createProfile( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		try {
			const profile = yield actions.fetchCreateProfile( accountID, propertyID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setProfileID( profile.id );

			return actions.receiveCreateProfile( { accountID, propertyID, profile } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreateProfileFailed( { accountID, error, propertyID } );
		}
	},

	*fetchCreateProfile( accountID, propertyID ) {
		yield {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE_STARTED,
		};

		return {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE,
		};
	},

	*fetchProfiles( accountID, propertyID ) {
		yield {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES_STARTED,
		};

		return {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics profile ID.
	 * @param {Object} args.profile    Google Analytics profile object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfile( { accountID, propertyID, profile } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( profile, 'profile is required.' );

		return {
			payload: { accountID, propertyID, profile },
			type: RECEIVE_CREATE_PROFILE,
		};
	},

	/**
	 * Logs an error with profile creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics property ID.
	 * @param {Object} args.error      Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfileFailed( { accountID, propertyID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_CREATE_PROFILE_FAILED,
		};
	},

	receiveProfiles( profiles ) {
		invariant( Array.isArray( profiles ), 'profiles must be an array.' );

		return {
			payload: { profiles },
			type: RECEIVE_PROFILES,
		};
	},

	receiveProfilesCompleted( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, propertyID },
			type: RECEIVE_PROFILES_COMPLETED,
		};
	},

	receiveProfilesFailed( { accountID, error, propertyID } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_PROFILES_FAILED,
		};
	},

	/**
	 * Sets a profile based on given account and property IDs.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {?Object} args Optional supporting arguments.
	 * @param {?string} args.accountID Account ID.
	 * @param {?string} args.property Property ID.
	 */
	*setProfileForProperty( { accountID, propertyID } = {} ) {
		const registry = yield Data.commonActions.getRegistry();

		if ( isValidAccountID( accountID ) && isValidPropertyID( propertyID ) ) {
			const profiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID ) || [];
			const matchedProfile = profiles.find( ( { webPropertyId } ) => webPropertyId === propertyID ) || { id: PROFILE_CREATE };

			registry.dispatch( STORE_NAME ).setProfileID( matchedProfile.id );
		} else {
			registry.dispatch( STORE_NAME ).setProfileID( PROFILE_CREATE );
		}
	},
};

export const controls = {
	[ FETCH_CREATE_PROFILE ]: ( { payload: { accountID, propertyID } } ) => {
		return API.set( 'modules', 'analytics', 'create-profile', {
			accountID,
			propertyID,
		} );
	},
	[ FETCH_PROFILES ]: ( { payload: { accountID, propertyID } } ) => {
		return API.get( 'modules', 'analytics', 'profiles', {
			accountID,
			propertyID,
		}, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROFILE_STARTED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case FETCH_PROFILES_STARTED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROFILE: {
			const { accountID, propertyID, profile } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
				profiles: {
					...state.profiles,
					[ `${ accountID }::${ propertyID }` ]: [
						...( state.profiles[ `${ accountID }::${ propertyID }` ] || [] ),
						profile,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROFILE_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES: {
			const { profiles } = payload;

			return {
				...state,
				profiles: {
					...state.profiles,
					...groupBy( profiles, ( { accountId, webPropertyId } ) => `${ accountId }::${ webPropertyId }` ), // Capitalization rule exception: `accountId` and `webPropertyId` are properties of an API returned value.
				},
			};
		}

		case RECEIVE_PROFILES_COMPLETED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProfiles( accountID, propertyID ) {
		if ( ! isValidAccountID( accountID ) || ! isValidPropertyID( propertyID ) ) {
			return;
		}

		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProfiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

			// If there are already profiles loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProfiles ) {
				return;
			}

			const profiles = yield actions.fetchProfiles( accountID, propertyID );

			yield actions.receiveProfiles( profiles );

			const profileID = registry.select( STORE_NAME ).getProfileID();
			if ( ! profileID ) {
				yield actions.setProfileForProperty( { accountID, propertyID } );
			}

			return actions.receiveProfilesCompleted( accountID, propertyID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveProfilesFailed( { accountID, propertyID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics profiles this user account+property has available.
	 *
	 * Returns an array of all profiles.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to fetch profiles for.
	 * @param {string} propertyID The Analytics Property ID to fetch profiles for.
	 * @return {?Array.<Object>} An array of Analytics profiles; `undefined` if not loaded.
	 */
	getProfiles( state, accountID, propertyID ) {
		const { profiles } = state;

		return profiles[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Check if a profile is being created for an account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile creation.
	 * @param {string} propertyID The Analytics Property ID to check for profile creation.
	 * @return {boolean} `true` if creating a profile, `false` if not.
	 */
	isDoingCreateProfile( state, accountID, propertyID ) {
		const { isFetchingCreateProfile } = state;

		return !! isFetchingCreateProfile[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Checks if profiles are being fetched for the given account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile fetching.
	 * @param {string} propertyID The Analytics Property ID to check for profile fetching.
	 * @return {boolean} `true` if fetching a profiles, `false` if not.
	 */
	isDoingGetProfiles( state, accountID, propertyID ) {
		const { isFetchingProfiles } = state;

		return !! isFetchingProfiles[ `${ accountID }::${ propertyID }` ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>import * as forms from './forms'
import fetchMock from 'fetch-mock'

describe('entity-browser', () => {
  describe('util', () => {
    describe('api', () => {
      describe('forms', () => {
        beforeEach(() => {
          fetchMock.reset()
          fetchMock.restore()
        })

        describe('getFieldsOfDetailForm', () => {
          it('get array of fields', () => {
            const formDefinition = {
              name: 'fromX',
              children: [
                {
                  type: 'ch.tocco.nice2.model.form.components.layout.HorizontalBox',
                  name: 'box 1',
                  children: [
                    {
                      type: 'ch.tocco.nice2.model.form.components.layout.HorizontalBox1',
                      name: 'box 2',
                      children: [
                        {
                          name: 'firstname',
                          type: 'ch.tocco.nice2.model.form.components.simple.TextArea'
                        },
                        {
                          name: 'lastname',
                          type: 'ch.tocco.nice2.model.form.components.simple.TextArea'
                        }
                      ]
                    }
                  ]
                },
                {
                  name: 'xyz',
                  type: 'ch.tocco.nice2.model.form.components.simple.SomeType'
                }
              ]
            }

            const fields = forms.getFieldsOfDetailForm(formDefinition)
            expect(fields).to.eql(['firstname', 'lastname', 'xyz'])
          })
        })

        describe('searchFormTransformer', () => {
          it('should return an array of fields', () => {
            const fetchResult = require('../../dev/test-data/user_search.json')

            const result = forms.searchFormTransformer(fetchResult)
            const expectedResult = [
              {
                name: 'txtFulltext',
                type: 'ch.tocco.nice2.model.form.components.simple.TextField',
                displayType: 'EDITABLE',
                label: 'Person',
                useLabel: 'YES'
              }
            ]
            expect(result).to.eql(expectedResult)
          })
        })

        describe('columnDefinitionTransformer', () => {
          it('should return an array of label and list of fields ', () => {
            const fetchResult = {
              form: {
                children: [{
                  name: 'table',
                  children: [
                    {
                      displayType: 'EDITABLE',
                      label: 'label1',
                      children: [{name: 'name1', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }, {
                      displayType: 'HIDDEN',
                      label: 'label2',
                      children: [{name: 'name2', type: 'type', displayType: 'HIDDEN', label: 'label'}]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label3',
                      children: [{name: 'custom:name3', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label4',
                      children: [{
                        name: 'name4',
                        type: 'ch.tocco.nice2.model.form.components.action.Action',
                        displayType: 'EDITABLE',
                        label: 'label'
                      }]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label5',
                      children: [{name: 'name5', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }
                  ]
                }]
              }
            }
            const result = forms.columnDefinitionTransformer(fetchResult)

            const expectedColumnDefinition = [
              {label: 'label1', value: ['name1']},
              {label: 'label3', value: []},
              {label: 'label4', value: []},
              {label: 'label5', value: ['name5']}
            ]
            expect(result).to.eql(expectedColumnDefinition)
          })
        })

        describe('fetchForm', () => {
          it('should call fetch ', () => {
            fetchMock.get('*', {})
            return forms.fetchForm('User_search').then(res => {
              expect(fetchMock.calls().matched).to.have.length(1)
              const lastCallUrl = fetchMock.lastCall()[0]
              expect(lastCallUrl).to.eql('/nice2/rest/forms/User_search')
            })
          })
        })
      })
    })
  })
})
<commit_msg>test(entity-browser): write better tests for _paths getter<commit_after>import * as forms from './forms'
import fetchMock from 'fetch-mock'

describe('entity-browser', () => {
  describe('util', () => {
    describe('api', () => {
      describe('forms', () => {
        beforeEach(() => {
          fetchMock.reset()
          fetchMock.restore()
        })

        describe('getFieldsOfDetailForm', () => {
          it('get array of fields and ignore not simple types and iterators', () => {
            const formDefinition = {
              name: 'fromX',
              children: [
                {
                  type: 'ch.tocco.nice2.model.form.components.layout.HorizontalBox',
                  name: 'box 1',
                  children: [
                    {
                      type: 'ch.tocco.nice2.model.form.components.layout.HorizontalBox1',
                      name: 'box 2',
                      children: [
                        {
                          name: 'firstname',
                          type: 'ch.tocco.nice2.model.form.components.simple.TextArea'
                        },
                        {
                          name: 'lastname',
                          type: 'ch.tocco.nice2.model.form.components.simple.TextArea'
                        },
                        {
                          name: 'relAffiliation',
                          type: 'ch.tocco.nice2.model.form.components.table.Table'
                        }
                      ]
                    }
                  ]
                },
                {
                  name: 'xyz',
                  type: 'ch.tocco.nice2.model.form.components.simple.SomeType'
                },
                {
                  name: 'relTask_note',
                  type: 'ch.tocco.nice2.model.form.components.navigation.IteratorComponent',
                  children: [
                    {
                      name: 'iteratorField',
                      type: 'ch.tocco.nice2.model.form.components.simple.TextArea'
                    }]
                }
              ]
            }

            const fields = forms.getFieldsOfDetailForm(formDefinition)
            expect(fields).to.eql(['firstname', 'lastname', 'xyz'])
          })
        })

        describe('searchFormTransformer', () => {
          it('should return an array of fields', () => {
            const fetchResult = require('../../dev/test-data/user_search.json')

            const result = forms.searchFormTransformer(fetchResult)
            const expectedResult = [
              {
                name: 'txtFulltext',
                type: 'ch.tocco.nice2.model.form.components.simple.TextField',
                displayType: 'EDITABLE',
                label: 'Person',
                useLabel: 'YES'
              }
            ]
            expect(result).to.eql(expectedResult)
          })
        })

        describe('columnDefinitionTransformer', () => {
          it('should return an array of label and list of fields ', () => {
            const fetchResult = {
              form: {
                children: [{
                  name: 'table',
                  children: [
                    {
                      displayType: 'EDITABLE',
                      label: 'label1',
                      children: [{name: 'name1', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }, {
                      displayType: 'HIDDEN',
                      label: 'label2',
                      children: [{name: 'name2', type: 'type', displayType: 'HIDDEN', label: 'label'}]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label3',
                      children: [{name: 'custom:name3', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label4',
                      children: [{
                        name: 'name4',
                        type: 'ch.tocco.nice2.model.form.components.action.Action',
                        displayType: 'EDITABLE',
                        label: 'label'
                      }]
                    }, {
                      displayType: 'EDITABLE',
                      label: 'label5',
                      children: [{name: 'name5', type: 'type', displayType: 'EDITABLE', label: 'label'}]
                    }
                  ]
                }]
              }
            }
            const result = forms.columnDefinitionTransformer(fetchResult)

            const expectedColumnDefinition = [
              {label: 'label1', value: ['name1']},
              {label: 'label3', value: []},
              {label: 'label4', value: []},
              {label: 'label5', value: ['name5']}
            ]
            expect(result).to.eql(expectedColumnDefinition)
          })
        })

        describe('fetchForm', () => {
          it('should call fetch ', () => {
            fetchMock.get('*', {})
            return forms.fetchForm('User_search').then(res => {
              expect(fetchMock.calls().matched).to.have.length(1)
              const lastCallUrl = fetchMock.lastCall()[0]
              expect(lastCallUrl).to.eql('/nice2/rest/forms/User_search')
            })
          })
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>const _ = require('lodash');
const moment = require('moment');

const Stocks = require('../../../stocks.js');
const Securities = require('../../securities/identification/index.js');

const searchOptions = {
  tickers: ['AAPL', 'GM'],
  range: { start: new Date('2016-01-02T23:00:00.000Z') }
};

// Stocks.getAllHistoricalsByTickers(searchOptions)
//   .then(console.log)
//   .catch(console.log)

// Securities.getISINtoTickersByResourceFile()
//   .then(console.log)
//   .catch(console.log)

const getValuation = () => {
  return getTransaction()
    .then((transactions) => {
      return getHistoricalsBy(transactions)
        .then((historicals) => {
          return { transactions, historicals };
        });
    })
    .then(createPortfolioHistoricals)
};

const getTransaction = () => {
  return Promise.resolve(require(`${__dirname}/../../../../lib/account/importers/degiro/resources/transactions.json`))
    .then((transactions) => {
      return _
        .chain(transactions)
        .map((transaction) => {
          transaction.date = moment(transaction.date, 'DD-MM-YYYY').toDate();
          return transaction;
        })
        .sortBy('date')
        .value();
    });
};

const getHistoricalsBy = (transactions) => {
  const isins = _.uniq(_.map(transactions, 'ISIN'));

  return Securities.getISINtoTickersByResourceFile()
    .then((isinsToTickers) => {
      return _
        .chain(isins)
        .map(isin => isinsToTickers[isin])
        .filter(_.isString)
        .value();
    })
    .then((tickers) => {
      if(tickers.length > 0) {
        const start = _.first(transactions).date;
        return Stocks.getAllHistoricalsByTickers({ tickers, range: { start } })
      }
    })
};

const createPortfolioHistoricals = ({ transactions, historicals }) => {
  const { start, end } = getRange({ transactions, historicals });
  return createSeries({ start, end, historicals });
};

const getRange = ({ transactions, historicals }) => {
  const start = _.get(_.first(transactions), 'date');

  const end = _
    .chain(historicals)
    .map('historicals')
    .map((historicals) => {
      return _.maxBy(historicals, 'date');
    })

    .map('date')
    .max()
    .value();

  return { start, end };
};

const createSeries = ({ start, end, historicals }) => {
  const series = [];
  const endDate = moment(end);
  let step = moment(start).clone();

  const stockIndices = {};

  do {
    // console.log(step);

    const portfolioQuote = {
      date: step.clone().toDate(),
      entries: {},
    };

    _.forEach(historicals, ({ ticker, historicals }, index) => {
      const stockIndex = stockIndices[index] || 0;
      let quote = historicals[stockIndex];

      // if(ticker === 'AAPL') { console.log(historicals.length); }
      // if(!(quote && step.isSame(moment(quote.date).startOf("day")))) {
      //   console.log(step, moment(quote.date).startOf("day"), stockIndices);
      // }
      // console.log(step, moment(quote.date).startOf("day"), stockIndices);

      if(quote && step.isSame(moment(quote.date).startOf("day"))) {
        // console.log('quote && step.isSame');
        // console.log(step, quote.date);
        stockIndices[index] = stockIndex + 1;
      } else {
        if(stockIndex > 0) {
          quote = historicals[stockIndex - 1];
        }
      }

      if(quote) {
        portfolioQuote.entries[ticker] = quote;
      }
    });

    step.add(1, 'day');

    series.push(portfolioQuote);
  } while(moment(step).isSameOrBefore(endDate));

  return series;// .then(_.last)
  // .then(content => JSON.stringify(content, null, 2))
  // .then(con
};

getValuation()
  .then(_.last)
  .then(content => JSON.stringify(content, null, 2))
  .then(console.log)
  .catch(console.log);
<commit_msg>get portfolio netVAlue<commit_after>const _ = require('lodash');
const moment = require('moment');

const Stocks = require('../../../stocks.js');
const Securities = require('../../securities/identification/index.js');

const searchOptions = {
  tickers: ['AAPL', 'GM'],
  range: { start: new Date('2016-01-02T23:00:00.000Z') }
};

// Stocks.getAllHistoricalsByTickers(searchOptions)
//   .then(console.log)
//   .catch(console.log)

// Securities.getISINtoTickersByResourceFile()
//   .then(console.log)
//   .catch(console.log)

const getValuation = () => {
  return getTransaction()
    .then((transactions) => {
      return Promise.all([
        getPortfolioHistoricals(transactions),
        getPortfolioSharesCompositionByDate(transactions),
      ])
      .then(([historicals, shareCompositionByDate]) => {
        return { shareCompositionByDate, historicals };
      })
    })
    .then(setNetValueByClose)
    .then(series => _.map(series, quote => _.pick(quote, ['date', 'netValue'])))
    .then(series => _.slice(series, series.length - 100))
};

const setNetValueByClose = ({ shareCompositionByDate, historicals }) => {
  let previousSharedComposition;

  return _.map(historicals, (historical) => {
    const shares = shareCompositionByDate[moment(historical.date).startOf('day').format('YYYY-MM-DD')] || previousSharedComposition;
    // historical.entries;

    historical.netValue = 0;

    _.forOwn(historical.entries, (quote, ticker) => {
      quote.amount = shares[ticker] || 0;
      quote.netValue = quote.amount * quote.close;
      historical.netValue += quote.netValue;
    });

    previousSharedComposition = _.cloneDeep(shares);

    return historical;
  });
};

const getPortfolioSharesCompositionByDate = (transactions) => {
  // return _.slice(transactions, 10, 20);

  return Securities.getISINtoTickersByResourceFile()
    .then((isinsToTickers) => {
      return _
        .chain(transactions)
        .groupBy(transaction => moment(transaction.date).format('YYYY-MM-DD'))
        .reduce((compositions, transactions, date) => {
          const composition = _.reduce(transactions, (composition, { ISIN, amount }) => {
            const ticker = isinsToTickers[ISIN];

            if(ticker) {
              composition[ticker] = composition[ticker] || 0;
              composition[ticker] += amount;
            } else {
              console.error(`missing ticker for ISIN: ${ ISIN }`);
            }

            return composition;
          }, {});

          const previous = _.last(compositions);

          if(previous) {
            const current = _.cloneDeep(previous);

            const tickers = _.uniq([
              ..._.keys(current.shares),
              ..._.keys(composition),
            ]);

            _.forOwn(tickers, (ticker) => {
              current.shares[ticker] = (current.shares[ticker] || 0) + (composition[ticker] || 0);
            });

            compositions.push({ date, shares: current.shares });
          } else {
            compositions.push({ date, shares: composition });
          }

          return compositions;
        }, [])
        .reduce((graph, { date, shares }) => {
          return _.set(graph, date, shares);
        }, {})
        .value();
    });
};

const getPortfolioHistoricals = (transactions) => {
  return getHistoricalsBy(transactions)
    .then((historicals) => {
      return { transactions, historicals };
    })
    .then(createPortfolioHistoricals);
};

const getTransaction = () => {
  return Promise.resolve(require(`${__dirname}/../../../../lib/account/importers/degiro/resources/transactions.json`))
    .then((transactions) => {
      return _
        .chain(transactions)
        .map((transaction) => {
          transaction.date = moment(transaction.date, 'DD-MM-YYYY').toDate();
          return transaction;
        })
        .sortBy('date')
        .value();
    });
};

const getHistoricalsBy = (transactions) => {
  const isins = _.uniq(_.map(transactions, 'ISIN'));

  return Securities.getISINtoTickersByResourceFile()
    .then((isinsToTickers) => {
      return _
        .chain(isins)
        .map(isin => isinsToTickers[isin])
        .filter(_.isString)
        .value();
    })
    .then((tickers) => {
      if(tickers.length > 0) {
        const start = _.first(transactions).date;
        return Stocks.getAllHistoricalsByTickers({ tickers, range: { start } })
      }
    })
};

const createPortfolioHistoricals = ({ transactions, historicals }) => {
  const { start, end } = getRange({ transactions, historicals });
  return createSeries({ start, end, historicals });
};

const getRange = ({ transactions, historicals }) => {
  const start = _.get(_.first(transactions), 'date');

  const end = _
    .chain(historicals)
    .map('historicals')
    .map((historicals) => {
      return _.maxBy(historicals, 'date');
    })

    .map('date')
    .max()
    .value();

  return { start, end };
};

const createSeries = ({ start, end, historicals }) => {
  const series = [];
  const endDate = moment(end);
  let step = moment(start).clone();

  const stockIndices = {};

  do {
    // console.log(step);

    const portfolioQuote = {
      date: step.clone().toDate(),
      entries: {},
    };

    _.forEach(historicals, ({ ticker, historicals }, index) => {
      const stockIndex = stockIndices[index] || 0;
      let quote = historicals[stockIndex];

      // if(ticker === 'AAPL') { console.log(historicals.length); }
      // if(!(quote && step.isSame(moment(quote.date).startOf("day")))) {
      //   console.log(step, moment(quote.date).startOf("day"), stockIndices);
      // }
      // console.log(step, moment(quote.date).startOf("day"), stockIndices);

      if(quote && step.isSame(moment(quote.date).startOf("day"))) {
        // console.log('quote && step.isSame');
        // console.log(step, quote.date);
        stockIndices[index] = stockIndex + 1;
      } else {
        if(stockIndex > 0) {
          quote = historicals[stockIndex - 1];
        }
      }

      if(quote) {
        portfolioQuote.entries[ticker] = quote;
      }
    });

    step.add(1, 'day');

    series.push(portfolioQuote);
  } while(moment(step).isSameOrBefore(endDate));

  return series;
};

getValuation()
  // .then(_.last)
  // .then(content => JSON.stringify(content, null, 2))
  .then(console.log)
  .catch(console.log);
<|endoftext|>


<commit_before>'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
import Event from '../sectionevent/sectionevent.model';
var async = require('async');
var scheduler = require('../../schedule');

var SectionSchema = new Schema({
  course: {type : Schema.Types.ObjectId, ref: 'Course'},
  instructors: [{type : Schema.Types.ObjectId, ref: 'User'}],
  students: [{type : Schema.Types.ObjectId, ref: 'User'}],
  pendingStudents: [{type : Schema.Types.ObjectId, ref: 'User'}],
  sectionNumbers: [Number],
  enrollmentPolicy: {type: String, enum: ['open', 'closed', 'approvalRequired']},
});

/**
 * Pre-save hook
 */
SectionSchema
  .pre('save', function(next) {
    // Handle new/update times
    Promise.all([
        this.getRelatedUsers(),
        this.getSectionEventsAsync()
    ]).then(([users, events]) => {
        Promise.all(users.map(user => {
          // console.log(user, events);
            return user.updateNotifications(events);
        })).then(()=>{
          next();
        });
    });
  });

/**
 * Methods
 */
SectionSchema.methods = {

  getRelatedUsers(){
      return this.populate('students').execPopulate().then(fullSection => fullSection.students);
  },

  getSectionEventsAsync(opts){
    return Event.find({section: this._id}).execAsync().then((secs) => {
      return Promise.all(secs.map(sec => sec.getFullEvent()));
    });
  },

    getEventsAsync(opts){
      var events = [];
      var query = Event.find({section : this._id})
      .populate('info');

      query.populate({
         path: 'section',
         populate: {
           path: 'course',
           model: 'Course'
         }
      });

      return query.lean().execAsync()
      .then((sectionEventsArray)=>{
        var events = {};
        var eventsList = [];
        sectionEventsArray.forEach((sectionEvent)=>{
          if(events[sectionEvent.info._id]){
            events[sectionEvent.info._id].sectionEvents.push(sectionEvent);
          }
          else{
            events[sectionEvent.info._id] = sectionEvent.info;
            events[sectionEvent.info._id].sectionEvents = [sectionEvent];
            eventsList.push(events[sectionEvent.info._id]);
          }
          delete sectionEvent.info;
        });
        return eventsList;
      });
    }
};

module.exports = mongoose.model('Section', SectionSchema);
<commit_msg>removed ghost code<commit_after>'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
import Event from '../sectionevent/sectionevent.model';
var async = require('async');
var scheduler = require('../../schedule');

var SectionSchema = new Schema({
  course: {type : Schema.Types.ObjectId, ref: 'Course'},
  instructors: [{type : Schema.Types.ObjectId, ref: 'User'}],
  students: [{type : Schema.Types.ObjectId, ref: 'User'}],
  pendingStudents: [{type : Schema.Types.ObjectId, ref: 'User'}],
  sectionNumbers: [Number],
  enrollmentPolicy: {type: String, enum: ['open', 'closed', 'approvalRequired']},
});

/**
 * Pre-save hook
 */
SectionSchema
  .pre('save', function(next) {
    // Handle new/update times
    Promise.all([
        this.getRelatedUsers(),
        this.getSectionEventsAsync()
    ]).then(([users, events]) => {
        Promise.all(users.map(user => {
            return user.updateNotifications(events);
        })).then(()=>{
          next();
        });
    });
  });

/**
 * Methods
 */
SectionSchema.methods = {

  getRelatedUsers(){
      return this.populate('students').execPopulate().then(fullSection => fullSection.students);
  },

  getSectionEventsAsync(opts){
    return Event.find({section: this._id}).execAsync().then((secs) => {
      return Promise.all(secs.map(sec => sec.getFullEvent()));
    });
  },

    getEventsAsync(opts){
      var events = [];
      var query = Event.find({section : this._id})
      .populate('info');

      query.populate({
         path: 'section',
         populate: {
           path: 'course',
           model: 'Course'
         }
      });

      return query.lean().execAsync()
      .then((sectionEventsArray)=>{
        var events = {};
        var eventsList = [];
        sectionEventsArray.forEach((sectionEvent)=>{
          if(events[sectionEvent.info._id]){
            events[sectionEvent.info._id].sectionEvents.push(sectionEvent);
          }
          else{
            events[sectionEvent.info._id] = sectionEvent.info;
            events[sectionEvent.info._id].sectionEvents = [sectionEvent];
            eventsList.push(events[sectionEvent.info._id]);
          }
          delete sectionEvent.info;
        });
        return eventsList;
      });
    }
};

module.exports = mongoose.model('Section', SectionSchema);
<|endoftext|>


<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module('axelor.ui');

/**
 * The Form widget.
 *
 */
ui.formWidget('Form', {

	priority: 100,

	css: "dynamic-form",

	scope: false,

	compile: function(element, attrs) {

		element.hide();
		element.find('[x-field],[data-field]').each(function(){

			var elem = $(this),
				name = elem.attr('x-field') || elem.attr('data-field');

			if (name && elem.attr('ui-button') === undefined) {
				if (!elem.attr('ng-model')) {
					elem.attr('ng-model', 'record.' + name);
				}
				if (!elem.attr('ng-required')) {
					// always attache a required validator to make
					// dynamic `required` attribute change effective
					elem.attr('ng-required', false);
				}
			}
		});

		return ui.formCompile.apply(this, arguments);
	},

	link: function(scope, element, attrs, controller) {

		element.on('submit', function(e) {
			e.preventDefault();
		});

		scope.$watch('record', function(rec, old) {
			if (element.is(':visible')) {
				return;
			}
			scope.ajaxStop(function() {
				element.show();
				axelor.$adjustSize();
			});
		});
	}
});

/**
 * This directive is used filter $watch on scopes of inactive tabs.
 *
 */
ui.directive('uiTabGate', function() {

	return {

		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					scope.$watchChecker(function(current) {
						if (current.tabSelected === undefined) {
							return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
						}
						return current.tabSelected;
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes of hidden forms.
 *
 */
ui.directive('uiFormGate', function() {

	return {
		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					var parent = null;
					scope.$watchChecker(function(current) {
						if (scope.tabSelected === false) {
							return false;
						}
						if (parent === null) {
							parent = element.parents('[ui-show]:first');
						}
						// hack for hidden nested editors (#2173)
						if (scope.$$forceWatch) {
							return true;
						}
						return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes based on some condition.
 *
 */
ui.directive('uiWatchIf', ['$parse', function($parse) {

	return {
		compile: function compile(tElement, tAttrs) {
			return {
				pre: function preLink(scope, element, attrs) {
					var value = false,
						expression = $parse(attrs.uiWatchIf);

					scope.$watchChecker(function (current) {
						if (current === scope) {
							return value = expression(scope);
						}
						return value;
					});
				}
			};
		}
	};
}]);

function toBoolean(value) {
	if (value && value.length !== 0) {
		var v = angular.lowercase("" + value);
		value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
	} else {
		value = false;
	}
	return value;
}

/**
 * This directive is used to speedup uiFormGate.
 */
ui.directive('uiShow', function() {

	return {
		scope: true, // create new scope to always watch the expression
		link: function link(scope, element, attrs) {
			scope.$$shouldWatch = true;
			scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
				element.css('display', toBoolean(value) ? '' : 'none').toggleClass('ui-hide', !toBoolean(value));
			});
		}
	};
});

ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {

	function isValid(scope, name) {
		if (!name) return scope.isValid();
		var ctrl = scope.form;
		if (ctrl) {
			ctrl = ctrl[name];
		}
		if (ctrl) {
			return ctrl.$valid;
		}
	}

	function withContext(scope, record) {
		var values = _.extend({}, scope._context, scope._jsonContext, record);
		return _.extend(values, {
			$user: axelor.config['user.login'],
			$group: axelor.config['user.group']
		});
	}

	function handleCondition(scope, field, attr, condition, negative) {

		if (!condition || _.isBoolean(condition)) {
			return;
		}

		scope.$on("on:record-change", function(e, rec, force) {
			if (field && field.jsonField) {
				handle(scope.record);
			} else if (rec === scope.record || force) {
				handle(rec);
			}
		});
		scope.$on("on:grid-selection-change", function(e, context) {
			handle(context);
		});

		scope.$watch("isReadonly()", watcher);
		scope.$watch("isRequired()", watcher);
		scope.$watch("isValid()", watcher);

		var expr = $parse(condition);

		function watcher(current, old) {
			var rec = scope.record;
			if (rec === undefined && current === old) return;
			if (rec === undefined && scope.getContext) {
				rec = scope.getContext();
			}
			handle(rec);
		}

		function handle(rec) {
			var value;
			try {
				value = !!axelor.$eval(scope, expr, withContext(scope, rec));
			} catch (e) {
				console.error('FAILED:', condition, e);
			}
			scope.attr(attr, negative ? !value : value);
		}
	}

	function handleHilites(scope, field) {
		if (!field || _.isEmpty(field.hilites)) {
			return;
		}

		var hilites = field.hilites || [];
		var exprs = _.map(_.pluck(hilites, 'condition'), $parse);

		function handle(rec) {
			for (var i = 0; i < hilites.length; i++) {
				var hilite = hilites[i];
				var expr = exprs[i];
				var value = false;
				try {
					value = axelor.$eval(scope, expr, withContext(scope, rec));
				} catch (e) {
					console.error('FAILED:', hilite, e);
				}
				if (value) {
					return scope.attr('highlight', {
						hilite: hilite,
						passed: value
					});
				}
			}
			return scope.attr('highlight', {});
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleBind(scope, field) {

		if (!field.bind || !field.name) {
			return;
		}

		var expr = $interpolate(field.bind);
		var last = null;

		function handle(rec) {
			var value;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
				if (value.length === 0) {
					value = null;
				}
			} catch (e) {
				console.error('FAILED:', field.bind, e);
			}

			if (scope.setValue && scope.record && last !== value) {
				scope.setValue(last = value);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			if (field && field.jsonField) {
				handle(scope.record);
			} else if (rec && rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleValueExpr(scope, field) {

		if (!field.valueExpr || !field.name) {
			return;
		}

		var expr = $parse(field.valueExpr);

		function handle(rec) {
			var value;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
				if (value && value.length === 0) {
					value = null;
				}
			} catch (e) {
				console.error('FAILED:', field.valueExpr, e);
			}

			if (scope.setValue && scope.record) {
				scope.setValue(value, false);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			scope.$timeout(function () {
				if (field && field.jsonField) {
					handle(scope.record);
				} else if (rec && rec === scope.record) {
					handle(rec);
				}
			});
		});
	}

	function handleFor(scope, field, attr, conditional, negative) {
		if (field[conditional]) {
			handleCondition(scope, field, attr, field[conditional], negative);
		}
	}

	function handleForField(scope) {
		var field = scope.field;
		if (!field) return;
		handleFor(scope, field, "valid", "validIf");
		handleFor(scope, field, "hidden", "hideIf");
		handleFor(scope, field, "hidden", "showIf", true);
		handleFor(scope, field, "readonly", "readonlyIf");
		handleFor(scope, field, "required", "requiredIf");
		handleFor(scope, field, "collapse", "collapseIf");
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canView", "canView");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canRemove", "canRemove");
		handleFor(scope, field, "canSelect", "canSelect");
		handleHilites(scope, field);
		handleBind(scope, field);
		handleValueExpr(scope, field);
	}

	function handleForView(scope) {
		var field = scope.schema;
		if (!field) return;
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canSave", "canSave");
		handleFor(scope, field, "canCopy", "canCopy");
		handleFor(scope, field, "canDelete", "canDelete");
		handleFor(scope, field, "canAttach", "canAttach");
	}

	return function(scope, element, attrs) {
		scope.$evalAsync(function() {
			if (element.is('[ui-form]')) {
				return handleForView(scope);
			}
			handleForField(scope);
		});
	};
}]);

})();
<commit_msg>Remove unnecessary watch in conditional expr handler<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module('axelor.ui');

/**
 * The Form widget.
 *
 */
ui.formWidget('Form', {

	priority: 100,

	css: "dynamic-form",

	scope: false,

	compile: function(element, attrs) {

		element.hide();
		element.find('[x-field],[data-field]').each(function(){

			var elem = $(this),
				name = elem.attr('x-field') || elem.attr('data-field');

			if (name && elem.attr('ui-button') === undefined) {
				if (!elem.attr('ng-model')) {
					elem.attr('ng-model', 'record.' + name);
				}
				if (!elem.attr('ng-required')) {
					// always attache a required validator to make
					// dynamic `required` attribute change effective
					elem.attr('ng-required', false);
				}
			}
		});

		return ui.formCompile.apply(this, arguments);
	},

	link: function(scope, element, attrs, controller) {

		element.on('submit', function(e) {
			e.preventDefault();
		});

		scope.$watch('record', function(rec, old) {
			if (element.is(':visible')) {
				return;
			}
			scope.ajaxStop(function() {
				element.show();
				axelor.$adjustSize();
			});
		});
	}
});

/**
 * This directive is used filter $watch on scopes of inactive tabs.
 *
 */
ui.directive('uiTabGate', function() {

	return {

		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					scope.$watchChecker(function(current) {
						if (current.tabSelected === undefined) {
							return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
						}
						return current.tabSelected;
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes of hidden forms.
 *
 */
ui.directive('uiFormGate', function() {

	return {
		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					var parent = null;
					scope.$watchChecker(function(current) {
						if (scope.tabSelected === false) {
							return false;
						}
						if (parent === null) {
							parent = element.parents('[ui-show]:first');
						}
						// hack for hidden nested editors (#2173)
						if (scope.$$forceWatch) {
							return true;
						}
						return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes based on some condition.
 *
 */
ui.directive('uiWatchIf', ['$parse', function($parse) {

	return {
		compile: function compile(tElement, tAttrs) {
			return {
				pre: function preLink(scope, element, attrs) {
					var value = false,
						expression = $parse(attrs.uiWatchIf);

					scope.$watchChecker(function (current) {
						if (current === scope) {
							return value = expression(scope);
						}
						return value;
					});
				}
			};
		}
	};
}]);

function toBoolean(value) {
	if (value && value.length !== 0) {
		var v = angular.lowercase("" + value);
		value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
	} else {
		value = false;
	}
	return value;
}

/**
 * This directive is used to speedup uiFormGate.
 */
ui.directive('uiShow', function() {

	return {
		scope: true, // create new scope to always watch the expression
		link: function link(scope, element, attrs) {
			scope.$$shouldWatch = true;
			scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
				element.css('display', toBoolean(value) ? '' : 'none').toggleClass('ui-hide', !toBoolean(value));
			});
		}
	};
});

ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {

	function isValid(scope, name) {
		if (!name) return scope.isValid();
		var ctrl = scope.form;
		if (ctrl) {
			ctrl = ctrl[name];
		}
		if (ctrl) {
			return ctrl.$valid;
		}
	}

	function withContext(scope, record) {
		var values = _.extend({}, scope._context, scope._jsonContext, record);
		return _.extend(values, {
			$user: axelor.config['user.login'],
			$group: axelor.config['user.group']
		});
	}

	function handleCondition(scope, field, attr, condition, negative) {

		if (!condition || _.isBoolean(condition)) {
			return;
		}

		scope.$on("on:record-change", function(e, rec, force) {
			if (field && field.jsonField) {
				handle(scope.record);
			} else if (rec === scope.record || force) {
				handle(rec);
			}
		});
		scope.$on("on:grid-selection-change", function(e, context) {
			handle(context);
		});

		var expr = $parse(condition);

		function watcher(current, old) {
			var rec = scope.record;
			if (rec === undefined && current === old) return;
			if (rec === undefined && scope.getContext) {
				rec = scope.getContext();
			}
			handle(rec);
		}

		function handle(rec) {
			var value;
			try {
				value = !!axelor.$eval(scope, expr, withContext(scope, rec));
			} catch (e) {
				console.error('FAILED:', condition, e);
			}
			scope.attr(attr, negative ? !value : value);
		}
	}

	function handleHilites(scope, field) {
		if (!field || _.isEmpty(field.hilites)) {
			return;
		}

		var hilites = field.hilites || [];
		var exprs = _.map(_.pluck(hilites, 'condition'), $parse);

		function handle(rec) {
			for (var i = 0; i < hilites.length; i++) {
				var hilite = hilites[i];
				var expr = exprs[i];
				var value = false;
				try {
					value = axelor.$eval(scope, expr, withContext(scope, rec));
				} catch (e) {
					console.error('FAILED:', hilite, e);
				}
				if (value) {
					return scope.attr('highlight', {
						hilite: hilite,
						passed: value
					});
				}
			}
			return scope.attr('highlight', {});
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleBind(scope, field) {

		if (!field.bind || !field.name) {
			return;
		}

		var expr = $interpolate(field.bind);
		var last = null;

		function handle(rec) {
			var value;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
				if (value.length === 0) {
					value = null;
				}
			} catch (e) {
				console.error('FAILED:', field.bind, e);
			}

			if (scope.setValue && scope.record && last !== value) {
				scope.setValue(last = value);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			if (field && field.jsonField) {
				handle(scope.record);
			} else if (rec && rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleValueExpr(scope, field) {

		if (!field.valueExpr || !field.name) {
			return;
		}

		var expr = $parse(field.valueExpr);

		function handle(rec) {
			var value;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
				if (value && value.length === 0) {
					value = null;
				}
			} catch (e) {
				console.error('FAILED:', field.valueExpr, e);
			}

			if (scope.setValue && scope.record) {
				scope.setValue(value, false);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			scope.$timeout(function () {
				if (field && field.jsonField) {
					handle(scope.record);
				} else if (rec && rec === scope.record) {
					handle(rec);
				}
			});
		});
	}

	function handleFor(scope, field, attr, conditional, negative) {
		if (field[conditional]) {
			handleCondition(scope, field, attr, field[conditional], negative);
		}
	}

	function handleForField(scope) {
		var field = scope.field;
		if (!field) return;
		handleFor(scope, field, "valid", "validIf");
		handleFor(scope, field, "hidden", "hideIf");
		handleFor(scope, field, "hidden", "showIf", true);
		handleFor(scope, field, "readonly", "readonlyIf");
		handleFor(scope, field, "required", "requiredIf");
		handleFor(scope, field, "collapse", "collapseIf");
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canView", "canView");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canRemove", "canRemove");
		handleFor(scope, field, "canSelect", "canSelect");
		handleHilites(scope, field);
		handleBind(scope, field);
		handleValueExpr(scope, field);
	}

	function handleForView(scope) {
		var field = scope.schema;
		if (!field) return;
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canSave", "canSave");
		handleFor(scope, field, "canCopy", "canCopy");
		handleFor(scope, field, "canDelete", "canDelete");
		handleFor(scope, field, "canAttach", "canAttach");
	}

	return function(scope, element, attrs) {
		scope.$evalAsync(function() {
			if (element.is('[ui-form]')) {
				return handleForView(scope);
			}
			handleForField(scope);
		});
	};
}]);

})();
<|endoftext|>


<commit_before>'use strict';

const _              = require('lodash');
const Bluebird       = require('bluebird');
const mongoose       = require('mongoose');
const mongooseDelete = require('mongoose-delete');
const ShortId        = require('mongoose-shortid-nodeps');
const Settings       = require('../../settings');
const Content        = require('./content');
const validations    = require('./validations');

const schema = new mongoose.Schema({
  code: {
    type  : ShortId,
    index : true // Added to make the code unique
  },
  name: String,
  active: { type: Boolean, default: true },
  content: [ Content ],
  authorizedEmails: [{
    email        : { type: String, lowercase: true },
    authorizedAt : Date
  }]
});

schema.plugin(validations);
schema.plugin(mongooseDelete, { overrideMethods: 'all' });

function transform(doc, ret) {
  return _.pick(ret, Settings.ContentCode.paths);
}

schema.set('toJSON',   { transform });
schema.set('toObject', { transform });

// Promisify mongoose-delete methods
Bluebird.promisifyAll(schema.methods);

module.exports = schema;
<commit_msg>Removed unused (for now) property 'authorizedEmails'<commit_after>'use strict';

const _              = require('lodash');
const Bluebird       = require('bluebird');
const mongoose       = require('mongoose');
const mongooseDelete = require('mongoose-delete');
const ShortId        = require('mongoose-shortid-nodeps');
const Settings       = require('../../settings');
const Content        = require('./content');
const validations    = require('./validations');

const schema = new mongoose.Schema({
  code: {
    type  : ShortId,
    index : true // Added to make the code unique
  },
  name: String,
  active: { type: Boolean, default: true },
  content: [ Content ]
});

schema.plugin(validations);
schema.plugin(mongooseDelete, { overrideMethods: 'all' });

function transform(doc, ret) {
  return _.pick(ret, Settings.ContentCode.paths);
}

schema.set('toJSON',   { transform });
schema.set('toObject', { transform });

// Promisify mongoose-delete methods
Bluebird.promisifyAll(schema.methods);

module.exports = schema;
<|endoftext|>


<commit_before>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import Snakeskin from '../core';
import { ws } from '../helpers/string';

Snakeskin.addDirective(
	'call',

	{
		block: true,
		deferInit: true,
		group: ['call', 'microTemplate', 'output'],
		notEmpty: true,
		shorthands: {'+=': 'call ', '/+': 'end call'},
		trim: true
	},

	function (command) {
		const
			short = command.slice(-1) === '/';

		if (short) {
			this.startInlineDir(null, {short: true});
			this.append(this.wrap(this.out(`${command.slice(0, -1)};`, {unsafe: true})));
			return;
		}

		this.startDir(null, {
			chunks: 1,
			command
		});

		this.append(ws`
			${this.declVars(
				ws`
					__CALL_CACHE__ = __RESULT__,
					__CALL_TMP__ = [],
					__CALL_POS__ = 0
				`,

				{sys: true}
			)}

			__RESULT__ = ${this.getResultDecl()};
		`);
	},

	function () {
		this.text = true;

		const
			p = this.structure.params;

		if (p.strongSpace) {
			this.strongSpace.pop();
		}

		if (p.short) {
			return;
		}

		const
			tmp = this.out('__CALL_TMP__', {unsafe: true});

		this.append(ws`
			if (__LENGTH__(__RESULT__)) {
				${tmp}.push(Unsafe(${this.getReturnResultDecl()}));
			}
		`);

		let
			i = p.chunks,
			j = 0;

		let
			wrapParams = '';

		while (i--) {
			if (wrapParams) {
				wrapParams += ',';
			}

			wrapParams += `${tmp}[${j++}]`;
		}

		let str;
		const command = p.command.replace(/\((.*?)\)$/, (str, $0) => {
			$0 = $0.trim();
			return $0 ? `(${$0},${wrapParams})` : `(${wrapParams})`;
		});

		const
			name = this.getFnName(command);

		if (name === '&') {
			const
				block = this.hasBlock(this.getGroup('block'), true);

			if (block) {
				str = block.params.fn + this.out(command.replace(name, ''), {unsafe: true});

			} else {
				return this.error(`invalid "${this.name}" declaration`);
			}

		} else {
			str = this.out(command, {unsafe: true});
		}

		this.append(ws`
			__RESULT__ = ${this.out('__CALL_CACHE__', {unsafe: true})};
			${this.wrap(str)}
		`);
	}

);
<commit_msg>:wrench:<commit_after>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import Snakeskin from '../core';
import { ws } from '../helpers/string';

Snakeskin.addDirective(
	'call',

	{
		block: true,
		deferInit: true,
		group: ['call', 'microTemplate', 'output'],
		notEmpty: true,
		shorthands: {'+=': 'call ', '/+': 'end call'},
		trim: true
	},

	function (command) {
		const
			short = command.slice(-1) === '/';

		if (short) {
			this.startInlineDir(null, {short: true});
			this.append(this.wrap(this.out(command.slice(0, -1), {unsafe: true})));
			return;
		}

		this.startDir(null, {
			chunks: 1,
			command
		});

		this.append(ws`
			${this.declVars(
				ws`
					__CALL_CACHE__ = __RESULT__,
					__CALL_TMP__ = [],
					__CALL_POS__ = 0
				`,

				{sys: true}
			)}

			__RESULT__ = ${this.getResultDecl()};
		`);
	},

	function () {
		this.text = true;

		const
			p = this.structure.params;

		if (p.strongSpace) {
			this.strongSpace.pop();
		}

		if (p.short) {
			return;
		}

		const
			tmp = this.out('__CALL_TMP__', {unsafe: true});

		this.append(ws`
			if (__LENGTH__(__RESULT__)) {
				${tmp}.push(Unsafe(${this.getReturnResultDecl()}));
			}
		`);

		let
			i = p.chunks,
			j = 0;

		let
			wrapParams = '';

		while (i--) {
			if (wrapParams) {
				wrapParams += ',';
			}

			wrapParams += `${tmp}[${j++}]`;
		}

		let str;
		const command = p.command.replace(/\((.*?)\)$/, (str, $0) => {
			$0 = $0.trim();
			return $0 ? `(${$0},${wrapParams})` : `(${wrapParams})`;
		});

		const
			name = this.getFnName(command);

		if (name === '&') {
			const
				block = this.hasBlock(this.getGroup('block'), true);

			if (block) {
				str = block.params.fn + this.out(command.replace(name, ''), {unsafe: true});

			} else {
				return this.error(`invalid "${this.name}" declaration`);
			}

		} else {
			str = this.out(command, {unsafe: true});
		}

		this.append(ws`
			__RESULT__ = ${this.out('__CALL_CACHE__', {unsafe: true})};
			${this.wrap(str)}
		`);
	}

);
<|endoftext|>


<commit_before>
"use strict";

export default () => {
    return {
        restrict: 'EA',
        scope: {module: '='},
        controller: ['$scope', $scope => {}],
        controllerAs: 'list',
        bindToController: true
    };
};

<commit_msg>allow this directly on the directive<commit_after>
"use strict";

export default () => {
    return {
        restrict: 'EA',
        scope: {component: '=moList'},
        controller: ['$scope', $scope => {}],
        controllerAs: 'list',
        bindToController: true
    };
};

<|endoftext|>