<commit_before>import React from 'react';

import UI from '../../package/index';
import {COLORS} from '../../package/utils/colors';

import View from '../../package/View';
import Drawer from '../../package/Drawer';
import Toolbar from '../../package/Toolbar';
import Button from '../../package/Button';
import Progress from '../../package/Progress';
import NotificationsActiveIcon from '../../package/icons/NotificationsActiveIcon';
import FormatPaintIcon from '../../package/icons/FormatPaintIcon';
import InvertColorsIcon from '../../package/icons/InvertColorsIcon';
import MenuIcon from '../../package/icons/MenuIcon';
import ZoomInIcon from '../../package/icons/ZoomInIcon';
import ZoomOutIcon from '../../package/icons/ZoomOutIcon';
import AddCircleIcon from '../../package/icons/AddCircleIcon';
import ArrowBackIcon from '../../package/icons/ArrowBackIcon';
import FullscreenIcon from '../../package/icons/FullscreenIcon';
import ColorLensIcon from '../../package/icons/ColorLensIcon';
import ViewAgendaIcon from '../../package/icons/ViewAgendaIcon';

import Buttons from './Buttons';
import Palette from './Palette';
import Lists from './Lists';
import Icons from './Icons';
import ProgressBars from './ProgressBars';
import Cards from './Cards';

export default class App extends React.Component {

  constructor(...args){
    super(...args);
    // Initial state
    this.state = {
      page: localStorage.getItem('page') || 'lists',
      scale: 1.2,
    };
    // Trigger update on browser resize
    UI.viewport.on('change', () => {
      this.forceUpdate();
    });
  }

  toggleMenu(){
    if( UI.viewport.width.huge ){
      this.toggleSidebar();
    }else{
      if( this.state.sidebarHidden ){
        this.toggleSidebar();
      }
      this.toggleDrawer();
    }
  }

  toggleFullscreen(){
    UI.viewport.toggleFullscreen();
  }

  clickBody(){
    this.closeDrawer();
  }

  toggleDiscoMode(){
    if ( this.state.discoInterval ) {
      console.log( 'Party over! Oops, out of time :(' ); // Prince lyric FYI
      clearInterval( this.state.discoInterval );
      this.setState({discoInterval:false});
    }
    else {
      this.setState({discoInterval: setInterval(() => {
        let notAccent = ['brown', 'grey', 'blue-grey'];
        let primaryColors = Object.keys(COLORS);
        let accentColors = Object.keys(COLORS).filter(c => notAccent.indexOf(c) === -1 );
        UI.theme.primaryPalette = primaryColors[Math.floor(Math.random()*primaryColors.length)];
        UI.theme.accentPalette = accentColors[Math.floor(Math.random()*accentColors.length)];
        this.forceUpdate();
      },1000)});
    }
  }

  openPage(page){
    console.log('Opening page:', page);
    this.setState({page:page});
    this.toggleMenu();
    localStorage.setItem('page', page);
  }

  open(page){
    return this.openPage.bind(this,page);
  }

  toggleThemeMode(){
    UI.theme.mode = UI.theme.mode=='light' ? 'dark' : 'light';
    this.forceUpdate();
    console.log('Changed theme to:', UI.theme.mode);
  }

  closeDrawer(){
    if( this.state.sidebarActive ){
      this.setState({sidebarActive: false});
    }
  }

  toggleDrawer(){
    this.setState({sidebarActive: !this.state.sidebarActive});
  }

  toggleSidebar(){
    this.setState({sidebarHidden: !this.state.sidebarHidden});
  }

  scaleUp(){
    this.setState({scale: this.state.scale + 0.1});
  }

  scaleDown(){
    this.setState({scale: this.state.scale - 0.1});
  }

  getPage(){
    switch ( this.state.page ){
      case 'buttons'  :   return <Buttons />;
      case 'palette'  :   return <Palette />;
      case 'lists'    :   return <Lists />;
      case 'icons'    :   return <Icons />;
      case 'progress'    :   return <ProgressBars />;
      case 'cards'    :   return <Cards />;
    }
  }

  render(){
    return (
      <View row scale={this.state.scale}>
        <Drawer docked="huge" raised={3} hide={this.state.sidebarHidden} active={this.state.sidebarActive}>
          <Toolbar accent>
            <View>Demo-crazy</View>
            <Button transparent icon={<ArrowBackIcon/>} align="left" onClick={this.toggleMenu.bind(this)}/>
          </Toolbar>
          <View scroll>
            <Button icon={<ViewAgendaIcon/>} align="left" label="Buttons" id="menu-buttons" onClick={this.open('buttons')} />
            <Button icon={<ColorLensIcon/>} align="left" label="Palette" onClick={this.open('palette')} />
            <Button icon={<FullscreenIcon/>} align="left" label="Lists" onClick={this.open('lists')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Icons" onClick={this.open('icons')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Progress" onClick={this.open('progress')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Cards" onClick={this.open('cards')} />
            <Button icon={<NotificationsActiveIcon/>} align="left" label="Bells & Whistles" />
            <View/>
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers A-C" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers D-G" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers H-M" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers N-Z" />
          </View>
        </Drawer>
        <View onClickCapture={this.clickBody.bind(this)}>
          <Toolbar primary={'600'}>
            <Button transparent onClick={this.toggleMenu.bind(this)} icon={<MenuIcon/>} />
            <View>Title</View>
            <Button transparent onClick={this.scaleUp.bind(this)} icon={<ZoomInIcon/>} />
            <Button transparent onClick={this.scaleDown.bind(this)} icon={<ZoomOutIcon/>} />
            <Button transparent onClick={this.toggleDiscoMode.bind(this)} icon={<FormatPaintIcon/>} />
            <Button transparent onClick={this.toggleThemeMode.bind(this)} icon={<InvertColorsIcon/>}  />
            <Button transparent onClick={this.toggleFullscreen.bind(this)} icon={<FullscreenIcon/>} />
          </Toolbar>
          {this.getPage()}
        </View>
      </View>
    );
  }
}

UI.render(<App/>, document.getElementById('app'));
<commit_msg>whitespace<commit_after>import React from 'react';

import UI from '../../package/index';
import {COLORS} from '../../package/utils/colors';

import View from '../../package/View';
import Drawer from '../../package/Drawer';
import Toolbar from '../../package/Toolbar';
import Button from '../../package/Button';
import Progress from '../../package/Progress';
import NotificationsActiveIcon from '../../package/icons/NotificationsActiveIcon';
import FormatPaintIcon from '../../package/icons/FormatPaintIcon';
import InvertColorsIcon from '../../package/icons/InvertColorsIcon';
import MenuIcon from '../../package/icons/MenuIcon';
import ZoomInIcon from '../../package/icons/ZoomInIcon';
import ZoomOutIcon from '../../package/icons/ZoomOutIcon';
import AddCircleIcon from '../../package/icons/AddCircleIcon';
import ArrowBackIcon from '../../package/icons/ArrowBackIcon';
import FullscreenIcon from '../../package/icons/FullscreenIcon';
import ColorLensIcon from '../../package/icons/ColorLensIcon';
import ViewAgendaIcon from '../../package/icons/ViewAgendaIcon';

import Buttons from './Buttons';
import Palette from './Palette';
import Lists from './Lists';
import Icons from './Icons';
import ProgressBars from './ProgressBars';
import Cards from './Cards';

export default class App extends React.Component {

  constructor(...args){
    super(...args);
    // Initial state
    this.state = {
      page: localStorage.getItem('page') || 'lists',
      scale: 1.2,
    };
    // Trigger update on browser resize
    UI.viewport.on('change', () => {
      this.forceUpdate();
    });
  }

  toggleMenu(){
    if( UI.viewport.width.huge ){
      this.toggleSidebar();
    }else{
      if( this.state.sidebarHidden ){
        this.toggleSidebar();
      }
      this.toggleDrawer();
    }
  }

  toggleFullscreen(){
    UI.viewport.toggleFullscreen();
  }

  clickBody(){
    this.closeDrawer();
  }

  toggleDiscoMode(){
    if ( this.state.discoInterval ) {
      console.log( 'Party over! Oops, out of time :(' ); // Prince lyric FYI
      clearInterval( this.state.discoInterval );
      this.setState({discoInterval:false});
    }
    else {
      this.setState({discoInterval: setInterval(() => {
        let notAccent = ['brown', 'grey', 'blue-grey'];
        let primaryColors = Object.keys(COLORS);
        let accentColors = Object.keys(COLORS).filter(c => notAccent.indexOf(c) === -1 );
        UI.theme.primaryPalette = primaryColors[Math.floor(Math.random()*primaryColors.length)];
        UI.theme.accentPalette = accentColors[Math.floor(Math.random()*accentColors.length)];
        this.forceUpdate();
      },1000)});
    }
  }

  openPage(page){
    console.log('Opening page:', page);
    this.setState({page:page});
    this.toggleMenu();
    localStorage.setItem('page', page);
  }

  open(page){
    return this.openPage.bind(this,page);
  }

  toggleThemeMode(){
    UI.theme.mode = UI.theme.mode=='light' ? 'dark' : 'light';
    this.forceUpdate();
    console.log('Changed theme to:', UI.theme.mode);
  }

  closeDrawer(){
    if( this.state.sidebarActive ){
      this.setState({sidebarActive: false});
    }
  }

  toggleDrawer(){
    this.setState({sidebarActive: !this.state.sidebarActive});
  }

  toggleSidebar(){
    this.setState({sidebarHidden: !this.state.sidebarHidden});
  }

  scaleUp(){
    this.setState({scale: this.state.scale + 0.1});
  }

  scaleDown(){
    this.setState({scale: this.state.scale - 0.1});
  }

  getPage(){
    switch ( this.state.page ){
      case 'buttons'  :   return <Buttons />;
      case 'palette'  :   return <Palette />;
      case 'lists'    :   return <Lists />;
      case 'icons'    :   return <Icons />;
      case 'progress' :   return <ProgressBars />;
      case 'cards'    :   return <Cards />;
    }
  }

  render(){
    return (
      <View row scale={this.state.scale}>
        <Drawer docked="huge" raised={3} hide={this.state.sidebarHidden} active={this.state.sidebarActive}>
          <Toolbar accent>
            <View>Demo-crazy</View>
            <Button transparent icon={<ArrowBackIcon/>} align="left" onClick={this.toggleMenu.bind(this)}/>
          </Toolbar>
          <View scroll>
            <Button icon={<ViewAgendaIcon/>} align="left" label="Buttons" id="menu-buttons" onClick={this.open('buttons')} />
            <Button icon={<ColorLensIcon/>} align="left" label="Palette" onClick={this.open('palette')} />
            <Button icon={<FullscreenIcon/>} align="left" label="Lists" onClick={this.open('lists')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Icons" onClick={this.open('icons')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Progress" onClick={this.open('progress')} />
            <Button icon={<AddCircleIcon/>} align="left" label="Cards" onClick={this.open('cards')} />
            <Button icon={<NotificationsActiveIcon/>} align="left" label="Bells & Whistles" />
            <View/>
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers A-C" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers D-G" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers H-M" />
            <Button icon={<FormatPaintIcon/>} align="right" label="Paint Rollers N-Z" />
          </View>
        </Drawer>
        <View onClickCapture={this.clickBody.bind(this)}>
          <Toolbar primary={'600'}>
            <Button transparent onClick={this.toggleMenu.bind(this)} icon={<MenuIcon/>} />
            <View>Title</View>
            <Button transparent onClick={this.scaleUp.bind(this)} icon={<ZoomInIcon/>} />
            <Button transparent onClick={this.scaleDown.bind(this)} icon={<ZoomOutIcon/>} />
            <Button transparent onClick={this.toggleDiscoMode.bind(this)} icon={<FormatPaintIcon/>} />
            <Button transparent onClick={this.toggleThemeMode.bind(this)} icon={<InvertColorsIcon/>}  />
            <Button transparent onClick={this.toggleFullscreen.bind(this)} icon={<FullscreenIcon/>} />
          </Toolbar>
          {this.getPage()}
        </View>
      </View>
    );
  }
}

UI.render(<App/>, document.getElementById('app'));
<|endoftext|>


<commit_before>'use strict';

var directives = require('./directives');
var dom = require('./dom');
var shared = require('./shared');
var templates = require('./templates');
var utils = require('./utils');

function Elements() {
  // Methods
  this.create = create;
  this.element = element;
  this.render = render;

  return this;

  /**
   * @name create
   * @description Creates a new element (with or without id & class).
   * @param {string} tag
   * @param {object || string || boolean} props = false
   * @param {string} content
   * @return {object} element with properties.
   * @public
   */
  function create(tag, props, content) {
    var element = dom.getElementNameAndType(tag);
    var el = dom.newElement(element.name);
    var value;
    var property;
    var type;

    // Get properties for class or id and default attributes
    var getProps = function(element, props) {
      var newProps = shared.isSpecialTag(tag, props);

      if (element.id || element.class) {
        props = !props ? {} : props;
      }

      if (element.id) {
        props.id = element.id;
      }

      if (element.class) {
        props.class = element.class;
      }

      return newProps ? utils.merge(props, newProps) : props;
    };

    // Builds the object
    var buildElement = function() {
      props = getProps(element, props);

      if (content) {
        el.innerHTML = content;
      }

      if (utils.isObject(props)) {
        utils.forEach(props, function(key) {
          value = props[key] || '';
          property = shared.getProperty(key);
          el[property] = value;
        });
      } else if (props) {
        type = dom.getElementType(props, true);
        value = type !== 'tag' ? props.substring(1) : props;
        property = shared.getProperty(type);
        el[property] = value;
      }

      return el;
    };

    return buildElement();
  }

  /**
   * Get an element object.
   *
   * @param {string} elementName
   * @return {object} element
   * @public
   */
  function element(elementName) {
    return dom.getElement(elementName);
  }

  /**
   * Render elements.
   *
   * @param {string} target
   * @param {array} elements
   * @public
   */
  function render(target) {
    var elements = shared.getArguments(arguments);
    var el = element(target);
    var directiveProps;
    var directiveClass;
    var properties = {};
    var newElements;
    var vm;
    var directive = shared.isDirective(elements[0]);

    // Rendering a directive
    if (directive) {
      if (utils.isObject(elements[1])) {
        properties = elements[1];
        vm = elements[2] || 'vm' + directive;
      }

      // Setting the target of the directive
      properties.$target = target;

      // Getting the directive properties & merging with actual properties
      directiveProps = directives.getDirectiveProps(elements[0]);
      directiveProps.props = utils.merge(directiveProps.props, properties);

      // Getting the directiveClass object (coming from the controller)
      directiveClass = directives.getDirective(directiveProps.props.$directiveName);

      // Getting the compiled HTML as new DOM element
      newElements = templates.getCompiledHTML(directiveClass.render(), directiveProps, directiveClass, vm);

      // Removing used directive (free memory)
      directives.removeDirective(directiveProps.props.$directiveName);

      if (utils.isArray(newElements)) {
        utils.forEach(newElements, function(element) {
          el.appendChild(element);
        });
      }
    } else {
      // Rendering DOM elements
      utils.forEach(elements, function(element) {
        if (utils.isObject(element)) {
          el.appendChild(element);
        }
      });
    }
  }
}

// Exporting object
module.exports = new Elements();
<commit_msg>Update elements.js<commit_after>'use strict';

var directives = require('./directives');
var dom = require('./dom');
var shared = require('./shared');
var templates = require('./templates');
var utils = require('./utils');

function Elements() {
  // Methods
  this.create = create;
  this.element = element;
  this.render = render;

  return this;

  /**
   * @name create
   * @desc Creates a new element (with or without id & class).
   * @param {string} tag
   * @param {object || string || boolean} props = false
   * @param {string} content
   * @return {object} element with properties.
   * @public
   */
  function create(tag, props, content) {
    var element = dom.getElementNameAndType(tag);
    var el = dom.newElement(element.name);
    var value;
    var property;
    var type;

    // Get properties for class or id and default attributes
    var getProps = function(element, props) {
      var newProps = shared.isSpecialTag(tag, props);

      if (element.id || element.class) {
        props = !props ? {} : props;
      }

      if (element.id) {
        props.id = element.id;
      }

      if (element.class) {
        props.class = element.class;
      }

      return newProps ? utils.merge(props, newProps) : props;
    };

    // Builds the object
    var buildElement = function() {
      props = getProps(element, props);

      if (content) {
        el.innerHTML = content;
      }

      if (utils.isObject(props)) {
        utils.forEach(props, function(key) {
          value = props[key] || '';
          property = shared.getProperty(key);
          el[property] = value;
        });
      } else if (props) {
        type = dom.getElementType(props, true);
        value = type !== 'tag' ? props.substring(1) : props;
        property = shared.getProperty(type);
        el[property] = value;
      }

      return el;
    };

    return buildElement();
  }

  /**
   * Get an element object.
   *
   * @param {string} elementName
   * @return {object} element
   * @public
   */
  function element(elementName) {
    return dom.getElement(elementName);
  }

  /**
   * Render elements.
   *
   * @param {string} target
   * @param {array} elements
   * @public
   */
  function render(target) {
    var elements = shared.getArguments(arguments);
    var el = element(target);
    var directiveProps;
    var directiveClass;
    var properties = {};
    var newElements;
    var vm;
    var directive = shared.isDirective(elements[0]);

    // Rendering a directive
    if (directive) {
      if (utils.isObject(elements[1])) {
        properties = elements[1];
        vm = elements[2] || 'vm' + directive;
      }

      // Setting the target of the directive
      properties.$target = target;

      // Getting the directive properties & merging with actual properties
      directiveProps = directives.getDirectiveProps(elements[0]);
      directiveProps.props = utils.merge(directiveProps.props, properties);

      // Getting the directiveClass object (coming from the controller)
      directiveClass = directives.getDirective(directiveProps.props.$directiveName);

      // Getting the compiled HTML as new DOM element
      newElements = templates.getCompiledHTML(directiveClass.render(), directiveProps, directiveClass, vm);

      // Removing used directive (free memory)
      directives.removeDirective(directiveProps.props.$directiveName);

      if (utils.isArray(newElements)) {
        utils.forEach(newElements, function(element) {
          el.appendChild(element);
        });
      }
    } else {
      // Rendering DOM elements
      utils.forEach(elements, function(element) {
        if (utils.isObject(element)) {
          el.appendChild(element);
        }
      });
    }
  }
}

// Exporting object
module.exports = new Elements();
<|endoftext|>


<commit_before>// Rebuild all forum posts
//
'use strict';

const Promise  = require('bluebird');
const ObjectId = require('mongoose').Types.ObjectId;

const POSTS_PER_CHUNK = 500;

module.exports = function (N) {
  N.wire.on('init:jobs', function register_forum_posts_rebuild() {
    N.queue.registerWorker({
      name: 'forum_posts_rebuild',

      // static id to make sure it will never be executed twice at the same time
      taskID() {
        return 'forum_posts_rebuild';
      },

      chunksPerInstance: 1,

      * map() {
        let runid = Date.now();

        //
        // Select first and last posts from Posts collection,
        // and split range between them into chunks
        //

        // find first post id
        let first_post = yield N.models.forum.Post
                                             .findOne()
                                             .select('_id')
                                             .sort({ _id: 1 })
                                             .lean(true);

        // find last post id
        let last_post  = yield N.models.forum.Post
                                             .findOne()
                                             .select('_id')
                                             .sort({ _id: -1 })
                                             .lean(true);

        if (!first_post || !last_post) {
          return [];
        }

        const MSEC_MONTHLY = 30 * 24 * 60 * 60 * 1000;

        // find an amount of posts created last month
        let last_month_id = new ObjectId((last_post._id.getTimestamp() - MSEC_MONTHLY) / 1000);
        let monthly_post_count = yield N.models.forum.Post
                                                     .where('_id').gte(last_month_id)
                                                     .count();

        // we want to process around 1000 posts per chunk,
        // so calculate the post rate based on last month
        let delta  = POSTS_PER_CHUNK / monthly_post_count * MSEC_MONTHLY;

        let chunks = [];
        let from   = first_post._id.getTimestamp().valueOf() - 1;
        let to     = last_post._id.getTimestamp().valueOf() + 1;
        let fromid = null;
        let toid   = new ObjectId(from / 1000);

        for (let ts = from; ts <= to; ts += delta) {
          fromid = toid;
          toid = new ObjectId((ts + delta) / 1000);

          chunks.push({
            from:  fromid.toString(),
            to:    toid.toString(),
            runid
          });
        }

        return chunks;
      },

      * process() {
        let posts = yield N.models.forum.Post
                                        .where('_id').gte(this.data.from)
                                        .where('_id').lte(this.data.to)
                                        .select('_id')
                                        .lean(true);

        N.logger.info('Rebuilding posts range ' +
          this.data.from + '-' + this.data.to + ' (found ' + posts.length + ')');

        yield Promise.map(posts, post => N.wire.emit('internal:forum.post_rebuild', post._id), { concurrency: 50 });

        //
        // Send stat update to client
        //

        let data = yield this.task.worker.status(this.task.id);

        if (data) {
          let task_info = {
            current: data.chunks.done + data.chunks.errored,
            total:   data.chunks.done + data.chunks.errored +
                     data.chunks.active + data.chunks.pending,
            runid:   this.data.runid
          };

          N.live.debounce('admin.core.rebuild.forum_posts', task_info);
        }

        return this.data.runid;
      },

      reduce(chunksResult) {
        var task_info = {
          current: 1,
          total:   1,
          runid:   chunksResult[0] || 0
        };

        N.live.emit('admin.core.rebuild.forum_posts', task_info);
      }
    });
  });
};
<commit_msg>Add logging for post rebuild<commit_after>// Rebuild all forum posts
//
'use strict';

const Promise  = require('bluebird');
const ObjectId = require('mongoose').Types.ObjectId;

const POSTS_PER_CHUNK = 100;

module.exports = function (N) {
  N.wire.on('init:jobs', function register_forum_posts_rebuild() {
    N.queue.registerWorker({
      name: 'forum_posts_rebuild',

      // static id to make sure it will never be executed twice at the same time
      taskID() {
        return 'forum_posts_rebuild';
      },

      chunksPerInstance: 1,

      timeout: 60000,

      * map() {
        let runid = Date.now();

        //
        // Select first and last posts from Posts collection,
        // and split range between them into chunks
        //

        // find first post id
        let first_post = yield N.models.forum.Post
                                             .findOne()
                                             .select('_id')
                                             .sort({ _id: 1 })
                                             .lean(true);

        // find last post id
        let last_post  = yield N.models.forum.Post
                                             .findOne()
                                             .select('_id')
                                             .sort({ _id: -1 })
                                             .lean(true);

        if (!first_post || !last_post) {
          return [];
        }

        const MSEC_MONTHLY = 30 * 24 * 60 * 60 * 1000;

        // find an amount of posts created last month
        let last_month_id = new ObjectId((last_post._id.getTimestamp() - MSEC_MONTHLY) / 1000);
        let monthly_post_count = yield N.models.forum.Post
                                                     .where('_id').gte(last_month_id)
                                                     .count();

        // we want to process around 1000 posts per chunk,
        // so calculate the post rate based on last month
        let delta  = POSTS_PER_CHUNK / monthly_post_count * MSEC_MONTHLY;

        let chunks = [];
        let from   = first_post._id.getTimestamp().valueOf() - 1;
        let to     = last_post._id.getTimestamp().valueOf() + 1;
        let fromid = null;
        let toid   = new ObjectId(from / 1000);

        for (let ts = from; ts <= to; ts += delta) {
          fromid = toid;
          toid = new ObjectId((ts + delta) / 1000);

          chunks.push({
            from:  fromid.toString(),
            to:    toid.toString(),
            runid
          });
        }

        N.logger.info(`Start rebuilding forum posts (${chunks.length} chunks)`);

        return chunks;
      },

      * process() {
        let posts = yield N.models.forum.Post
                                        .where('_id').gte(this.data.from)
                                        .where('_id').lte(this.data.to)
                                        .select('_id')
                                        .lean(true);

        let start_time = Date.now();

        N.logger.info(`Rebuilding forum posts ${this.data.from}-${this.data.to} - ${posts.length} found`);

        yield Promise.map(
          posts,
          post => N.wire.emit('internal:forum.post_rebuild', post._id),
          { concurrency: 50 }
        );

        N.logger.info(`Rebuilding forum posts ${this.data.from}-${this.data.to} - finished (${
          ((Date.now() - start_time) / 1000).toFixed(1)
        }s)`);

        //
        // Send stat update to client
        //

        let data = yield this.task.worker.status(this.task.id);

        if (data) {
          let task_info = {
            current: data.chunks.done + data.chunks.errored,
            total:   data.chunks.done + data.chunks.errored +
                     data.chunks.active + data.chunks.pending,
            runid:   this.data.runid
          };

          N.live.debounce('admin.core.rebuild.forum_posts', task_info);
        }

        return this.data.runid;
      },

      reduce(chunksResult) {
        var task_info = {
          current: 1,
          total:   1,
          runid:   chunksResult[0] || 0
        };

        N.live.emit('admin.core.rebuild.forum_posts', task_info);

        N.logger.info('Finish rebuilding forum posts');
      }
    });
  });
};
<|endoftext|>


<commit_before>pc.extend(pc, function () {
    var _schema = [ 'enabled' ];

    /**
     * @name pc.ElementComponentSystem
     * @description Create a new ElementComponentSystem
     * @class Attach 2D text to an entity
     * @param {pc.Application} app The application
     * @extends pc.ComponentSystem
     */

    var ElementComponentSystem = function ElementComponentSystem(app) {
        this.id = 'element';
        this.app = app;
        app.systems.add(this.id, this);

        this.ComponentType = pc.ElementComponent;
        this.DataType = pc.ElementComponentData;

        this.schema = _schema;

        this._defaultTexture = new pc.Texture(app.graphicsDevice, {width:4, height:4, format:pc.PIXELFORMAT_R8_G8_B8});

        this.defaultImageMaterial = new pc.StandardMaterial();
        this.defaultImageMaterial.emissive = new pc.Color(0.5,0.5,0.5,1); // use non-white to compile shader correctly
        this.defaultImageMaterial.emissiveMap = this._defaultTexture;
        this.defaultImageMaterial.emissiveMapTint = true;
        this.defaultImageMaterial.opacityMap = this._defaultTexture;
        this.defaultImageMaterial.opacityMapChannel = "a";
        this.defaultImageMaterial.opacityTint = true;
        this.defaultImageMaterial.opacity = 0; // use non-1 opacity to compile shader correctly
        this.defaultImageMaterial.useLighting = false;
        this.defaultImageMaterial.useGammaTonemap = false;
        this.defaultImageMaterial.useFog = false;
        this.defaultImageMaterial.useSkybox = false;
        this.defaultImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultImageMaterial.depthWrite = false;
        this.defaultImageMaterial.update();

        this.defaultScreenSpaceImageMaterial = new pc.StandardMaterial();
        this.defaultScreenSpaceImageMaterial.emissive = new pc.Color(0.5,0.5,0.5,1); // use non-white to compile shader correctly
        this.defaultScreenSpaceImageMaterial.emissiveMap = this._defaultTexture;
        this.defaultScreenSpaceImageMaterial.emissiveMapTint = true;
        this.defaultScreenSpaceImageMaterial.opacityMap = this._defaultTexture;
        this.defaultScreenSpaceImageMaterial.opacityMapChannel = "a";
        this.defaultScreenSpaceImageMaterial.opacityTint = true;
        this.defaultScreenSpaceImageMaterial.opacity = 0; // use non-1 opacity to compile shader correctly
        this.defaultScreenSpaceImageMaterial.useLighting = false;
        this.defaultScreenSpaceImageMaterial.useGammaTonemap = false;
        this.defaultScreenSpaceImageMaterial.useFog = false;
        this.defaultScreenSpaceImageMaterial.useSkybox = false;
        this.defaultScreenSpaceImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultScreenSpaceImageMaterial.depthTest = false;
        this.defaultScreenSpaceImageMaterial.depthWrite = false;
        this.defaultScreenSpaceImageMaterial.update();

        this.defaultTextMaterial = new pc.StandardMaterial();
        this.defaultTextMaterial.msdfMap = this._defaultTexture;
        this.defaultTextMaterial.useLighting = false;
        this.defaultTextMaterial.useGammaTonemap = false;
        this.defaultTextMaterial.useFog = false;
        this.defaultTextMaterial.useSkybox = false;
        this.defaultTextMaterial.emissive = new pc.Color(1,1,1,1);
        this.defaultTextMaterial.opacity = 0.5;
        this.defaultTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultTextMaterial.depthWrite = false;
        this.defaultTextMaterial.update();

        this.defaultScreenSpaceTextMaterial = new pc.StandardMaterial();
        this.defaultScreenSpaceTextMaterial.msdfMap = this._defaultTexture;
        this.defaultScreenSpaceTextMaterial.useLighting = false;
        this.defaultScreenSpaceTextMaterial.useGammaTonemap = false;
        this.defaultScreenSpaceTextMaterial.useFog = false;
        this.defaultScreenSpaceTextMaterial.useSkybox = false;
        this.defaultScreenSpaceTextMaterial.emissive = new pc.Color(1,1,1,1);
        this.defaultScreenSpaceTextMaterial.opacity = 0.5;
        this.defaultScreenSpaceTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultScreenSpaceTextMaterial.depthWrite = false;
        this.defaultScreenSpaceTextMaterial.depthTest = false;
        this.defaultScreenSpaceTextMaterial.update();

        this.on('beforeremove', this.onRemoveComponent, this);
    };
    ElementComponentSystem = pc.inherits(ElementComponentSystem, pc.ComponentSystem);

    pc.Component._buildAccessors(pc.ElementComponent.prototype, _schema);

    pc.extend(ElementComponentSystem.prototype, {
        initializeComponentData: function (component, data, properties) {
            if (data.anchor !== undefined) {
                if (data.anchor instanceof pc.Vec4) {
                    component.anchor.copy(data.anchor);
                } else {
                    component.anchor.set(data.anchor[0], data.anchor[1], data.anchor[2], data.anchor[3]);
                }
            }
            if (data.pivot !== undefined) {
                if (data.pivot instanceof pc.Vec2) {
                    component.pivot.copy(data.pivot);
                } else {
                    component.pivot.set(data.pivot[0], data.pivot[1]);
                }
            }

            if (data.margin !== undefined) {
                if (data.margin instanceof pc.Vec4) {
                    component.margin.copy(data.margin);
                } else {
                    component._margin.set(data.margin[0], data.margin[1], data.margin[2], data.margin[3]);
                }
                // force update
                component.margin = component._margin;
            }

            var _marginChange = false;
            if (data.left !== undefined) {
                component._margin.x = data.left;
                _marginChange = true;
            }
            if (data.bottom !== undefined) {
                component._margin.y = data.bottom;
                _marginChange = true;
            }
            if (data.right !== undefined) {
                component._margin.z = data.right;
                _marginChange = true;
            }
            if (data.top !== undefined) {
                component._margin.w = data.top;
                _marginChange = true;
            }
            if (_marginChange) {
                // force update
                component.margin = component._margin;
            }

            if (data.width !== undefined) component.width = data.width;
            if (data.height !== undefined) component.height = data.height;

            component.type = data.type;
            if (component.type === pc.ELEMENTTYPE_IMAGE) {
                if (data.rect !== undefined) {
                    if (data.rect instanceof pc.Vec4) {
                        component.rect.copy(data.rect);
                    } else {
                        component.rect.set(data.rect[0], data.rect[1], data.rect[2], data.rect[3])
                    }
                }
                if (data.materialAsset !== undefined) component.materialAsset = data.materialAsset;
                if (data.material) component.material = data.material;
                if (data.color !== undefined) {
                    if (data.color instanceof pc.Color) {
                        component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
                    } else {
                        component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
                    }
                } else if (data.opacity !== undefined) {
                    component.opacity = data.opacity;
                }
                if (data.textureAsset !== undefined) component.textureAsset = data.textureAsset;
                if (data.texture) component.texture = data.texture;
            } else if(component.type === pc.ELEMENTTYPE_TEXT) {
                if (data.text !== undefined) component.text = data.text;
                if (data.color !== undefined) {
                    if (data.color instanceof pc.Color) {
                        component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
                    } else {
                        component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
                    }
                } else if (data.opacity !== undefined) {
                    component.opacity = data.opacity;
                }
                if (data.spacing !== undefined) component.spacing = data.spacing;
                if (data.fontSize !== undefined) {
                    component.fontSize = data.fontSize;
                    if (!data.lineHeight) component.lineHeight = data.fontSize;
                }
                if (data.lineHeight !== undefined) component.lineHeight = data.lineHeight;
                if (data.fontAsset !== undefined) component.fontAsset = data.fontAsset;
                if (data.font !== undefined) component.font = data.font;
            } else {
                // group
            }

            // find screen
            // do this here not in constructor so that component is added to the entity
            var screen = component._findScreen();
            if (screen) {
                component._updateScreen(screen);
            }

            ElementComponentSystem._super.initializeComponentData.call(this, component, data, properties);
        },

        onRemoveComponent: function (entity, component) {
            component.onRemove();
        },

        cloneComponent: function (entity, clone) {
            var source = entity.element;

            return this.addComponent(clone, {
                enabled: source.enabled,
                width: source.width,
                height: source.height,
                anchor: source.anchor.clone(),
                pivot: source.pivot.clone(),
                margin: source.margin.clone(),
                type: source.type,
                rect: source.rect && source.rect.clone() || source.rect,
                materialAsset: source.materialAsset,
                material: source.material,
                color: source.color.clone(),
                opacity: source.opacity,
                textureAsset: source.textureAsset,
                texture: source.texture,
                text: source.text,
                spacing: source.spacing,
                lineHeight: source.lineHeight,
                fontSize: source.fontSize,
                fontAsset: source.fontAsset,
                font: source.font
            });
        }
    });

    return {
        ElementComponentSystem: ElementComponentSystem
    }
}());
<commit_msg>Fix cloning group element components<commit_after>pc.extend(pc, function () {
    var _schema = [ 'enabled' ];

    /**
     * @name pc.ElementComponentSystem
     * @description Create a new ElementComponentSystem
     * @class Attach 2D text to an entity
     * @param {pc.Application} app The application
     * @extends pc.ComponentSystem
     */

    var ElementComponentSystem = function ElementComponentSystem(app) {
        this.id = 'element';
        this.app = app;
        app.systems.add(this.id, this);

        this.ComponentType = pc.ElementComponent;
        this.DataType = pc.ElementComponentData;

        this.schema = _schema;

        this._defaultTexture = new pc.Texture(app.graphicsDevice, {width:4, height:4, format:pc.PIXELFORMAT_R8_G8_B8});

        this.defaultImageMaterial = new pc.StandardMaterial();
        this.defaultImageMaterial.emissive = new pc.Color(0.5,0.5,0.5,1); // use non-white to compile shader correctly
        this.defaultImageMaterial.emissiveMap = this._defaultTexture;
        this.defaultImageMaterial.emissiveMapTint = true;
        this.defaultImageMaterial.opacityMap = this._defaultTexture;
        this.defaultImageMaterial.opacityMapChannel = "a";
        this.defaultImageMaterial.opacityTint = true;
        this.defaultImageMaterial.opacity = 0; // use non-1 opacity to compile shader correctly
        this.defaultImageMaterial.useLighting = false;
        this.defaultImageMaterial.useGammaTonemap = false;
        this.defaultImageMaterial.useFog = false;
        this.defaultImageMaterial.useSkybox = false;
        this.defaultImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultImageMaterial.depthWrite = false;
        this.defaultImageMaterial.update();

        this.defaultScreenSpaceImageMaterial = new pc.StandardMaterial();
        this.defaultScreenSpaceImageMaterial.emissive = new pc.Color(0.5,0.5,0.5,1); // use non-white to compile shader correctly
        this.defaultScreenSpaceImageMaterial.emissiveMap = this._defaultTexture;
        this.defaultScreenSpaceImageMaterial.emissiveMapTint = true;
        this.defaultScreenSpaceImageMaterial.opacityMap = this._defaultTexture;
        this.defaultScreenSpaceImageMaterial.opacityMapChannel = "a";
        this.defaultScreenSpaceImageMaterial.opacityTint = true;
        this.defaultScreenSpaceImageMaterial.opacity = 0; // use non-1 opacity to compile shader correctly
        this.defaultScreenSpaceImageMaterial.useLighting = false;
        this.defaultScreenSpaceImageMaterial.useGammaTonemap = false;
        this.defaultScreenSpaceImageMaterial.useFog = false;
        this.defaultScreenSpaceImageMaterial.useSkybox = false;
        this.defaultScreenSpaceImageMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultScreenSpaceImageMaterial.depthTest = false;
        this.defaultScreenSpaceImageMaterial.depthWrite = false;
        this.defaultScreenSpaceImageMaterial.update();

        this.defaultTextMaterial = new pc.StandardMaterial();
        this.defaultTextMaterial.msdfMap = this._defaultTexture;
        this.defaultTextMaterial.useLighting = false;
        this.defaultTextMaterial.useGammaTonemap = false;
        this.defaultTextMaterial.useFog = false;
        this.defaultTextMaterial.useSkybox = false;
        this.defaultTextMaterial.emissive = new pc.Color(1,1,1,1);
        this.defaultTextMaterial.opacity = 0.5;
        this.defaultTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultTextMaterial.depthWrite = false;
        this.defaultTextMaterial.update();

        this.defaultScreenSpaceTextMaterial = new pc.StandardMaterial();
        this.defaultScreenSpaceTextMaterial.msdfMap = this._defaultTexture;
        this.defaultScreenSpaceTextMaterial.useLighting = false;
        this.defaultScreenSpaceTextMaterial.useGammaTonemap = false;
        this.defaultScreenSpaceTextMaterial.useFog = false;
        this.defaultScreenSpaceTextMaterial.useSkybox = false;
        this.defaultScreenSpaceTextMaterial.emissive = new pc.Color(1,1,1,1);
        this.defaultScreenSpaceTextMaterial.opacity = 0.5;
        this.defaultScreenSpaceTextMaterial.blendType = pc.BLEND_PREMULTIPLIED;
        this.defaultScreenSpaceTextMaterial.depthWrite = false;
        this.defaultScreenSpaceTextMaterial.depthTest = false;
        this.defaultScreenSpaceTextMaterial.update();

        this.on('beforeremove', this.onRemoveComponent, this);
    };
    ElementComponentSystem = pc.inherits(ElementComponentSystem, pc.ComponentSystem);

    pc.Component._buildAccessors(pc.ElementComponent.prototype, _schema);

    pc.extend(ElementComponentSystem.prototype, {
        initializeComponentData: function (component, data, properties) {
            if (data.anchor !== undefined) {
                if (data.anchor instanceof pc.Vec4) {
                    component.anchor.copy(data.anchor);
                } else {
                    component.anchor.set(data.anchor[0], data.anchor[1], data.anchor[2], data.anchor[3]);
                }
            }
            if (data.pivot !== undefined) {
                if (data.pivot instanceof pc.Vec2) {
                    component.pivot.copy(data.pivot);
                } else {
                    component.pivot.set(data.pivot[0], data.pivot[1]);
                }
            }

            if (data.margin !== undefined) {
                if (data.margin instanceof pc.Vec4) {
                    component.margin.copy(data.margin);
                } else {
                    component._margin.set(data.margin[0], data.margin[1], data.margin[2], data.margin[3]);
                }
                // force update
                component.margin = component._margin;
            }

            var _marginChange = false;
            if (data.left !== undefined) {
                component._margin.x = data.left;
                _marginChange = true;
            }
            if (data.bottom !== undefined) {
                component._margin.y = data.bottom;
                _marginChange = true;
            }
            if (data.right !== undefined) {
                component._margin.z = data.right;
                _marginChange = true;
            }
            if (data.top !== undefined) {
                component._margin.w = data.top;
                _marginChange = true;
            }
            if (_marginChange) {
                // force update
                component.margin = component._margin;
            }

            if (data.width !== undefined) component.width = data.width;
            if (data.height !== undefined) component.height = data.height;

            component.type = data.type;
            if (component.type === pc.ELEMENTTYPE_IMAGE) {
                if (data.rect !== undefined) {
                    if (data.rect instanceof pc.Vec4) {
                        component.rect.copy(data.rect);
                    } else {
                        component.rect.set(data.rect[0], data.rect[1], data.rect[2], data.rect[3])
                    }
                }
                if (data.materialAsset !== undefined) component.materialAsset = data.materialAsset;
                if (data.material) component.material = data.material;
                if (data.color !== undefined) {
                    if (data.color instanceof pc.Color) {
                        component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
                    } else {
                        component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
                    }
                } else if (data.opacity !== undefined) {
                    component.opacity = data.opacity;
                }
                if (data.textureAsset !== undefined) component.textureAsset = data.textureAsset;
                if (data.texture) component.texture = data.texture;
            } else if(component.type === pc.ELEMENTTYPE_TEXT) {
                if (data.text !== undefined) component.text = data.text;
                if (data.color !== undefined) {
                    if (data.color instanceof pc.Color) {
                        component.color.set(data.color.data[0], data.color.data[1], data.color.data[2], data.opacity !== undefined ? data.opacity : 1);
                    } else {
                        component.color.set(data.color[0], data.color[1], data.color[2], data.opacity !== undefined ? data.opacity : 1);
                    }
                } else if (data.opacity !== undefined) {
                    component.opacity = data.opacity;
                }
                if (data.spacing !== undefined) component.spacing = data.spacing;
                if (data.fontSize !== undefined) {
                    component.fontSize = data.fontSize;
                    if (!data.lineHeight) component.lineHeight = data.fontSize;
                }
                if (data.lineHeight !== undefined) component.lineHeight = data.lineHeight;
                if (data.fontAsset !== undefined) component.fontAsset = data.fontAsset;
                if (data.font !== undefined) component.font = data.font;
            } else {
                // group
            }

            // find screen
            // do this here not in constructor so that component is added to the entity
            var screen = component._findScreen();
            if (screen) {
                component._updateScreen(screen);
            }

            ElementComponentSystem._super.initializeComponentData.call(this, component, data, properties);
        },

        onRemoveComponent: function (entity, component) {
            component.onRemove();
        },

        cloneComponent: function (entity, clone) {
            var source = entity.element;

            return this.addComponent(clone, {
                enabled: source.enabled,
                width: source.width,
                height: source.height,
                anchor: source.anchor.clone(),
                pivot: source.pivot.clone(),
                margin: source.margin.clone(),
                type: source.type,
                rect: source.rect && source.rect.clone() || source.rect,
                materialAsset: source.materialAsset,
                material: source.material,
                color: source.color && source.color.clone() || source.color,
                opacity: source.opacity,
                textureAsset: source.textureAsset,
                texture: source.texture,
                text: source.text,
                spacing: source.spacing,
                lineHeight: source.lineHeight,
                fontSize: source.fontSize,
                fontAsset: source.fontAsset,
                font: source.font
            });
        }
    });

    return {
        ElementComponentSystem: ElementComponentSystem
    }
}());
<|endoftext|>


<commit_before>(function($) {
  $.fn.conditionsBuilder = function(options) {
    if(options == "data") {
      var builder = $(this).eq(0).data("conditionsBuilder");
      return builder.collectData();
    } else {
      return $(this).each(function() {
        var builder = new ConditionsBuilder(this, options);
        $(this).data("conditionsBuilder", builder);
      });
    }
  };

  function ConditionsBuilder(element, options) {
    this.element = $(element);
    this.options = options || {};
    this.init();
  }

  ConditionsBuilder.prototype = {
    init: function() {
      this.fields = this.options.fields;
      this.data = this.options.data || {"all": []};
      var rules = this.buildRules(this.data);
      this.element.html(rules);
    },

    collectData: function() {
      return this.collectDataFromNode(this.element.find("> .conditional"));
    },

    collectDataFromNode: function(element) {
      var klass = null;
      var _this = this;
      if(element.is(".conditional")) {
        klass = element.find("> .all-any-wrapper > .all-any").val();
      }

      if(klass) {
        var out = {};
        out[klass] = [];
        element.find("> .conditional, > .rule").each(function() {
          out[klass].push(_this.collectDataFromNode($(this)));
        });
        return out;
      }
      else {
        return {
          name: element.find(".field").val(),
          operator: element.find(".operator").val(),
          value: element.find(".value").val()
        };
      }
    },

    buildRules: function(ruleData) {
      return this.buildConditional(ruleData) || this.buildRule(ruleData);
    },

    buildConditional: function(ruleData) {
      var kind;
      if(ruleData.all) { kind = "all"; }
      else if(ruleData.any) { kind = "any"; }
      if(!kind) { return; }

      var div = $("
", {"class": "conditional " + kind});
      var selectWrapper = $("
", {"class": "all-any-wrapper"});
      var select = $("<select>", {"class": "all-any"});
      select.append($("<option>", {"value": "all", "text": "All", "selected": kind == "all"}));
      select.append($("<option>", {"value": "any", "text": "Any", "selected": kind == "any"}));
      selectWrapper.append(select);
      selectWrapper.append($("
", {text: "of the following rules:"}));
      div.append(selectWrapper);

      var addRuleLink = $("", {"href": "#", "class": "add-rule", "text": "Add Rule"});
      var _this = this;
      addRuleLink.click(function(e) {
        e.preventDefault();
        var f = _this.fields[0];
        var newField = {name: f.value, operator: f.operators[0], value: null};
        div.append(_this.buildRule(newField));
      });
      div.append(addRuleLink);

      var addConditionLink = $("", {"href": "#", "class": "add-condition", "text": "Add Sub-Condition"});
      addConditionLink.click(function(e) {
        e.preventDefault();
        var f = _this.fields[0];
        var newField = {"all": [{name: f.value, operator: f.operators[0], value: null}]};
        div.append(_this.buildConditional(newField));
      });
      div.append(addConditionLink);

      var removeLink = $("", {"class": "remove", "href": "#", "text": "Remove This Sub-Condition"});
      removeLink.click(function(e) {
        e.preventDefault();
        div.remove();
      });
      div.append(removeLink);

      var rules = ruleData[kind];
      for(var i=0; i<rules.length; i++) {
        div.append(this.buildRules(rules[i]));
      }
      return div;
    },

    buildRule: function(ruleData) {
      var ruleDiv = $("
", {"class": "rule"});
      var fieldSelect = getFieldSelect(this.fields, ruleData);
      var operatorSelect = getOperatorSelect();

      fieldSelect.change(onFieldSelectChanged.call(this, operatorSelect, ruleData));

      ruleDiv.append(fieldSelect);
      ruleDiv.append(operatorSelect);
      ruleDiv.append(removeLink());

      fieldSelect.change();
      ruleDiv.find("> .value").val(ruleData.value);
      return ruleDiv;
    },

    operatorsFor: function(fieldName) {
      for(var i=0; i < this.fields.length; i++) {
        var field = this.fields[i];
        if(field.name == fieldName) {
          return field.operators;
        }
      }
    }
  };

  function getFieldSelect(fields, ruleData) {
    var select = $("<select>", {"class": "field"});
    for(var i=0; i < fields.length; i++) {
      var field = fields[i];
      var option = $("<option>", {
        text: field.label,
        value: field.name,
        selected: ruleData.name == field.name
      });
      option.data("options", field.options);
      select.append(option);
    }
    return select;
  }

  function getOperatorSelect() {
    var select = $("<select>", {"class": "operator"});
    select.change(onOperatorSelectChange);
    return select;
  }

  function removeLink() {
    var removeLink = $("", {"class": "remove", "href": "#", "text": "Remove"});
    removeLink.click(onRemoveLinkClicked);
    return removeLink;
  }

  function onRemoveLinkClicked(e) {
    e.preventDefault();
    $(this).parents(".rule").remove();
  }

  function onFieldSelectChanged(operatorSelect, ruleData) {
    var builder = this;
    return function(e) {
      var operators = builder.operatorsFor($(e.target).val());
      operatorSelect.empty();
      for(var i=0; i < operators.length; i++) {
        var operator = operators[i];
        var option = $("<option>", {
          text: operator.label || operator.name,
          value: operator.name,
          selected: ruleData.operator == operator.name
        });
        option.data("fieldType", operator.fieldType);
        operatorSelect.append(option);
      }
      operatorSelect.change();
    }
  }

  function onOperatorSelectChange(e) {
    var $this = $(this);
    var option = $this.find("> :selected");
    var container = $this.parents(".rule");
    var fieldSelect = container.find(".field");
    var currentValue = container.find(".value");
    var val = currentValue.val();

    // Clear errorMessages when switching between operator types
    $this.nextAll().each( function(index) {
        if ( $(this).attr('class') == 'errorMessage' ) {
            $(this).remove();
        }});
    switch(option.data("fieldType")) {
      case "none":
        $this.after($("<input>", {"type": "hidden", "class": "value"}));
        break;
      case "text":
        $this.after($(""));
        $this.after($("<input>", {"type": "text", "class": "value textInput"}));
        break;
      case "number":
        $this.after($(""));
        $this.after($("<input>", {"type": "text", "class": "value numberInput"}));
        break;
      case "select":
        var select = $("<select>", {"class": "value"});
        var options = fieldSelect.find("> :selected").data("options");
        for(var i=0; i < options.length; i++) {
          var opt = options[i];
          select.append($("<option>", {"text": opt.label || opt.name, "value": opt.name}));
        }
        $this.after(select);
        break;
    }
    currentValue.remove();
  }

})($);
<commit_msg>change rule to condition<commit_after>(function($) {
  $.fn.conditionsBuilder = function(options) {
    if(options == "data") {
      var builder = $(this).eq(0).data("conditionsBuilder");
      return builder.collectData();
    } else {
      return $(this).each(function() {
        var builder = new ConditionsBuilder(this, options);
        $(this).data("conditionsBuilder", builder);
      });
    }
  };

  function ConditionsBuilder(element, options) {
    this.element = $(element);
    this.options = options || {};
    this.init();
  }

  ConditionsBuilder.prototype = {
    init: function() {
      this.fields = this.options.fields;
      this.data = this.options.data || {"all": []};
      var rules = this.buildRules(this.data);
      this.element.html(rules);
    },

    collectData: function() {
      return this.collectDataFromNode(this.element.find("> .conditional"));
    },

    collectDataFromNode: function(element) {
      var klass = null;
      var _this = this;
      if(element.is(".conditional")) {
        klass = element.find("> .all-any-wrapper > .all-any").val();
      }

      if(klass) {
        var out = {};
        out[klass] = [];
        element.find("> .conditional, > .rule").each(function() {
          out[klass].push(_this.collectDataFromNode($(this)));
        });
        return out;
      }
      else {
        return {
          name: element.find(".field").val(),
          operator: element.find(".operator").val(),
          value: element.find(".value").val()
        };
      }
    },

    buildRules: function(ruleData) {
      return this.buildConditional(ruleData) || this.buildRule(ruleData);
    },

    buildConditional: function(ruleData) {
      var kind;
      if(ruleData.all) { kind = "all"; }
      else if(ruleData.any) { kind = "any"; }
      if(!kind) { return; }

      var div = $("
", {"class": "conditional " + kind});
      var selectWrapper = $("
", {"class": "all-any-wrapper"});
      var select = $("<select>", {"class": "all-any"});
      select.append($("<option>", {"value": "all", "text": "All", "selected": kind == "all"}));
      select.append($("<option>", {"value": "any", "text": "Any", "selected": kind == "any"}));
      selectWrapper.append(select);
      selectWrapper.append($("
", {text: "of the following conditions:"}));
      div.append(selectWrapper);

      var addRuleLink = $("", {"href": "#", "class": "add-rule", "text": "Add Condition"});
      var _this = this;
      addRuleLink.click(function(e) {
        e.preventDefault();
        var f = _this.fields[0];
        var newField = {name: f.value, operator: f.operators[0], value: null};
        div.append(_this.buildRule(newField));
      });
      div.append(addRuleLink);

      var addConditionLink = $("", {"href": "#", "class": "add-condition", "text": "Add Sub-Condition"});
      addConditionLink.click(function(e) {
        e.preventDefault();
        var f = _this.fields[0];
        var newField = {"all": [{name: f.value, operator: f.operators[0], value: null}]};
        div.append(_this.buildConditional(newField));
      });
      div.append(addConditionLink);

      var removeLink = $("", {"class": "remove", "href": "#", "text": "Remove This Sub-Condition"});
      removeLink.click(function(e) {
        e.preventDefault();
        div.remove();
      });
      div.append(removeLink);

      var rules = ruleData[kind];
      for(var i=0; i<rules.length; i++) {
        div.append(this.buildRules(rules[i]));
      }
      return div;
    },

    buildRule: function(ruleData) {
      var ruleDiv = $("
", {"class": "rule"});
      var fieldSelect = getFieldSelect(this.fields, ruleData);
      var operatorSelect = getOperatorSelect();

      fieldSelect.change(onFieldSelectChanged.call(this, operatorSelect, ruleData));

      ruleDiv.append(fieldSelect);
      ruleDiv.append(operatorSelect);
      ruleDiv.append(removeLink());

      fieldSelect.change();
      ruleDiv.find("> .value").val(ruleData.value);
      return ruleDiv;
    },

    operatorsFor: function(fieldName) {
      for(var i=0; i < this.fields.length; i++) {
        var field = this.fields[i];
        if(field.name == fieldName) {
          return field.operators;
        }
      }
    }
  };

  function getFieldSelect(fields, ruleData) {
    var select = $("<select>", {"class": "field"});
    for(var i=0; i < fields.length; i++) {
      var field = fields[i];
      var option = $("<option>", {
        text: field.label,
        value: field.name,
        selected: ruleData.name == field.name
      });
      option.data("options", field.options);
      select.append(option);
    }
    return select;
  }

  function getOperatorSelect() {
    var select = $("<select>", {"class": "operator"});
    select.change(onOperatorSelectChange);
    return select;
  }

  function removeLink() {
    var removeLink = $("", {"class": "remove", "href": "#", "text": "Remove"});
    removeLink.click(onRemoveLinkClicked);
    return removeLink;
  }

  function onRemoveLinkClicked(e) {
    e.preventDefault();
    $(this).parents(".rule").remove();
  }

  function onFieldSelectChanged(operatorSelect, ruleData) {
    var builder = this;
    return function(e) {
      var operators = builder.operatorsFor($(e.target).val());
      operatorSelect.empty();
      for(var i=0; i < operators.length; i++) {
        var operator = operators[i];
        var option = $("<option>", {
          text: operator.label || operator.name,
          value: operator.name,
          selected: ruleData.operator == operator.name
        });
        option.data("fieldType", operator.fieldType);
        operatorSelect.append(option);
      }
      operatorSelect.change();
    }
  }

  function onOperatorSelectChange(e) {
    var $this = $(this);
    var option = $this.find("> :selected");
    var container = $this.parents(".rule");
    var fieldSelect = container.find(".field");
    var currentValue = container.find(".value");
    var val = currentValue.val();

    // Clear errorMessages when switching between operator types
    $this.nextAll().each( function(index) {
        if ( $(this).attr('class') == 'errorMessage' ) {
            $(this).remove();
        }});
    switch(option.data("fieldType")) {
      case "none":
        $this.after($("<input>", {"type": "hidden", "class": "value"}));
        break;
      case "text":
        $this.after($(""));
        $this.after($("<input>", {"type": "text", "class": "value textInput"}));
        break;
      case "number":
        $this.after($(""));
        $this.after($("<input>", {"type": "text", "class": "value numberInput"}));
        break;
      case "select":
        var select = $("<select>", {"class": "value"});
        var options = fieldSelect.find("> :selected").data("options");
        for(var i=0; i < options.length; i++) {
          var opt = options[i];
          select.append($("<option>", {"text": opt.label || opt.name, "value": opt.name}));
        }
        $this.after(select);
        break;
    }
    currentValue.remove();
  }

})($);
<|endoftext|>


<commit_before>var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:dataview:histogram');

var dot = require('dot');
dot.templateSettings.strip = false;

var columnTypeQueryTpl = dot.template(
    'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");

var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;

var basicsQueryTpl = dot.template([
    'basics AS (',
    '  SELECT',
    '    max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
    '    avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
    '  FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var overrideBasicsQueryTpl = dot.template([
    'basics AS (',
    '  SELECT',
    '    max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
    '    avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
    '  FROM ({{=it._query}}) _cdb_basics',
    ')'
].join('\n'));

var iqrQueryTpl = dot.template([
    'iqrange AS (',
    '  SELECT max(quartile_max) - min(quartile_max) AS iqr',
    '  FROM (',
    '    SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
    '      SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
    '    ) AS quartile',
    '    FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
    '    WHERE quartile = 1 or quartile = 3',
    '    GROUP BY quartile',
    '  ) _cdb_iqr',
    ')'
].join('\n'));

var binsQueryTpl = dot.template([
    'bins AS (',
    '  SELECT CASE WHEN total_rows = 0 OR iqr = 0',
    '      THEN 1',
    '      ELSE GREATEST(',
    '        LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
    '        LEAST(',
    '          CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
    '          {{=it._maxBins}}',
    '        )',
    '      )',
    '    END AS bins_number',
    '  FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
    '  LIMIT 1',
    ')'
].join('\n'));

var overrideBinsQueryTpl = dot.template([
    'bins AS (',
    '  SELECT {{=it._bins}} AS bins_number',
    ')'
].join('\n'));

var nullsQueryTpl = dot.template([
    'nulls AS (',
    '  SELECT',
    '    count(*) AS nulls_count',
    '  FROM ({{=it._query}}) _cdb_histogram_nulls',
    '  WHERE {{=it._column}} IS NULL',
    ')'
].join('\n'));

var histogramQueryTpl = dot.template([
    'SELECT',
    '    (max_val - min_val) / cast(bins_number as float) AS bin_width,',
    '    bins_number,',
    '    nulls_count,',
    '    avg_val,',
    '    CASE WHEN min_val = max_val',
    '      THEN 0',
    '      ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
    '    END AS bin,',
    '    min({{=it._column}})::numeric AS min,',
    '    max({{=it._column}})::numeric AS max,',
    '    avg({{=it._column}})::numeric AS avg,',
    '    count(*) AS freq',
    'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
    'WHERE {{=it._column}} IS NOT NULL',
    'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
    'ORDER BY bin'
].join('\n'));

var dateBasicsQueryTpl = dot.template([
    'basics AS (',
    '    SELECT',
    '        max(date_part(\'epoch\', {{=it._column}})) AS max_val,',
    '        min(date_part(\'epoch\', {{=it._column}})) AS min_val,',
    '        avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
    '        min(date_trunc(',
    '           \'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._timezone}}\'',
    '        )) AS start_date,',
    '        max({{=it._column}} AT TIME ZONE \'{{=it._timezone}}\') AS end_date,',
    '        count(1) AS total_rows',
    '    FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var dateOverrideBasicsQueryTpl = dot.template([
    'basics AS (',
    '    SELECT',
    '        max({{=it._end}}) AS max_val,',
    '        min({{=it._start}}) AS min_val,',
    '        avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
    '        min(date_trunc(',
    '           \'{{=it._aggregation}}\',',
    '           TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._timezone}}\'',
    '        )) AS start_date,',
    '        max(TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._timezone}}\') AS end_date,',
    '        count(1) AS total_rows',
    '    FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var dateBinsQueryTpl = dot.template([
    'bins AS (',
    '    SELECT',
    '        bins_array,',
    '        ARRAY_LENGTH(bins_array, 1) AS bins_number',
    '    FROM (',
    '       SELECT',
    '           ARRAY(',
    '               {{?it._aggregation==="quarter"}}',
    '               SELECT GENERATE_SERIES(start_date, end_date, \'3 month\'::interval)',
    '               {{??}}',
    '               SELECT GENERATE_SERIES(start_date, end_date, \'1 {{=it._aggregation}}\'::interval)',
    '               {{?}}',
    '           ) AS bins_array',
    '       FROM basics',
    '    ) _cdb_bins_array',
    ')'
].join('\n'));

var dateHistogramQueryTpl = dot.template([
    'SELECT',
    '    (max_val - min_val) / cast(bins_number as float) AS bin_width,',
    '    bins_number,',
    '    nulls_count,',
    '    CASE WHEN min_val = max_val',
    '      THEN 0',
    '      ELSE GREATEST(1, LEAST(',
    '        WIDTH_BUCKET({{=it._column}} AT TIME ZONE \'{{=it._timezone}}\', bins_array),',
    '        bins_number',
    '      )) - 1',
    '    END AS bin,',
    '    min(date_part(',
    '       \'epoch\', ',
    '       date_trunc(\'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._timezone}}\'',
    '    ) AT TIME ZONE \'{{=it._timezone}}\'))::numeric AS timestamp,',
    '    min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,',
    '    max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,',
    '    avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,',
    '    count(*) AS freq',
    'FROM ({{=it._query}}) _cdb_histogram, basics, bins, nulls',
    'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL',
    'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
    'ORDER BY bin'
].join('\n'));

var TYPE = 'histogram';

/**
 {
     type: 'histogram',
     options: {
         column: 'name',
         bins: 10, // OPTIONAL
         aggregation: 'day' // OPTIONAL
     }
 }
 */
function Histogram(query, options, queries) {
    if (!_.isString(options.column)) {
        throw new Error('Histogram expects `column` in widget options');
    }

    this.query = query;
    this.queries = queries;
    this.column = options.column;
    this.bins = options.bins;
    this.aggregation = options.aggregation;
    this.timezone = options.timezone;

    this._columnType = null;
}

Histogram.prototype = new BaseWidget();
Histogram.prototype.constructor = Histogram;

module.exports = Histogram;

var DATE_OIDS = {
    1082: true,
    1114: true,
    1184: true
};

Histogram.prototype.sql = function(psql, override, callback) {
    // jshint maxcomplexity: 7
    if (!callback) {
        callback = override;
        override = {};
    }

    var self = this;

    var _column = this.column;

    var columnTypeQuery = columnTypeQueryTpl({
        column: _column, query: this.queries.no_filters
    });

    if (this._columnType === null) {
        psql.query(columnTypeQuery, function(err, result) {
            // assume numeric, will fail later
            self._columnType = 'numeric';
            if (!err && !!result.rows[0]) {
                var pgType = result.rows[0].pg_typeof;
                if (DATE_OIDS.hasOwnProperty(pgType)) {
                    self._columnType = 'date';
                }
            }
            self.sql(psql, override, callback);
        }, true); // use read-only transaction
        return null;
    }

    if (this._columnType === 'date' && this.aggregation !== undefined) {
        return this._buildDateHistogramQuery(override, callback);
    }

    if (this._columnType === 'date') {
        _column = columnCastTpl({ column: this.column});
    }
    var _query = this.query;

    var basicsQuery, binsQuery;

    if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
        debug('overriding with %j', override);
        basicsQuery = overrideBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _start: getBinStart(override),
            _end: getBinEnd(override)
        });

        binsQuery = [
            overrideBinsQueryTpl({
                _bins: override.bins
            })
        ].join(',\n');
    } else {
        basicsQuery = basicsQueryTpl({
            _query: _query,
            _column: _column
        });

        if (override && _.has(override, 'bins')) {
            binsQuery = [
                overrideBinsQueryTpl({
                    _bins: override.bins
                })
            ].join(',\n');
        } else {
            binsQuery = [
                iqrQueryTpl({
                    _query: _query,
                    _column: _column
                }),
                binsQueryTpl({
                    _query: _query,
                    _minBins: BIN_MIN_NUMBER,
                    _maxBins: BIN_MAX_NUMBER
                })
            ].join(',\n');
        }
    }

    var histogramSql = [
        "WITH",
        [
            basicsQuery,
            binsQuery,
            nullsQueryTpl({
                _query: _query,
                _column: _column
            })
        ].join(',\n'),
        histogramQueryTpl({
            _query: _query,
            _column: _column
        })
    ].join('\n');

    debug(histogramSql);

    return callback(null, histogramSql);
};

Histogram.prototype._buildDateHistogramQuery = function (override, callback) {
    var _column = this.column;
    var _query = this.query;
    var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation;
    var _timezone = override && Number.isFinite(override.timezone) ? override.timezone : this.timezone;

    var dateBasicsQuery;

    if (override && _.has(override, 'start') && _.has(override, 'end')) {
        dateBasicsQuery = dateOverrideBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _aggregation: _aggregation,
            _start: getBinStart(override),
            _end: getBinEnd(override),
            _timezone: getTimezone(_timezone)
        });
    } else {
        dateBasicsQuery = dateBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _aggregation: _aggregation,
            _timezone: getTimezone(_timezone)
        });
    }

    var dateBinsQuery = [
        dateBinsQueryTpl({
            _aggregation: _aggregation
        })
    ].join(',\n');

    var nullsQuery = nullsQueryTpl({
        _query: _query,
        _column: _column
    });

    var dateHistogramQuery = dateHistogramQueryTpl({
        _query: _query,
        _column: _column,
        _aggregation: _aggregation,
        _timezone: getTimezone(_timezone)
    });

    var histogramSql = [
        "WITH",
        [
            dateBasicsQuery,
            dateBinsQuery,
            nullsQuery
        ].join(',\n'),
        dateHistogramQuery
    ].join('\n');

    debug(histogramSql);

    return callback(null, histogramSql);
};

Histogram.prototype.format = function(result, override) {
    override = override || {};
    var buckets = [];

    var binsCount = getBinsCount(override);
    var width = getWidth(override);
    var binsStart = getBinStart(override);
    var nulls = 0;
    var avg;

    if (result.rows.length) {
        var firstRow = result.rows[0];
        binsCount = firstRow.bins_number;
        width = firstRow.bin_width || width;
        avg = firstRow.avg_val;
        nulls = firstRow.nulls_count;
        binsStart = override.hasOwnProperty('start') ?
            getBinStart(override) :
            firstRow.hasOwnProperty('timestamp') ?
            firstRow.timestamp :
            firstRow.min;

        buckets = result.rows.map(function(row) {
            return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
        });
    }

    return {
        bin_width: width,
        bins_count: binsCount,
        bins_start: binsStart,
        nulls: nulls,
        avg: avg,
        bins: buckets
    };
};

function getBinStart(override) {
    if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
        return Math.min(override.start, override.end);
    }
    return override.start || 0;
}

function getBinEnd(override) {
    if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
        return Math.max(override.start, override.end);
    }
    return override.end || 0;
}

function getBinsCount(override) {
    return override.bins || 0;
}

function getWidth(override) {
    var width = 0;
    var binsCount = override.bins;

    if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
        width = (override.end - override.start) / binsCount;
    }

    return width;
}

function getTimezone(timezone) {
    if (!timezone) {
        return '0';
    }
    var timezoneInHours = Math.ceil(timezone / 3600);
    return '' + timezoneInHours;
}

Histogram.prototype.getType = function() {
    return TYPE;
};

Histogram.prototype.toString = function() {
    return JSON.stringify({
        _type: TYPE,
        _column: this.column,
        _query: this.query
    });
};
<commit_msg>Update commented use cases<commit_after>var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:dataview:histogram');

var dot = require('dot');
dot.templateSettings.strip = false;

var columnTypeQueryTpl = dot.template(
    'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");

var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;

var basicsQueryTpl = dot.template([
    'basics AS (',
    '  SELECT',
    '    max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
    '    avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
    '  FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var overrideBasicsQueryTpl = dot.template([
    'basics AS (',
    '  SELECT',
    '    max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
    '    avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
    '  FROM ({{=it._query}}) _cdb_basics',
    ')'
].join('\n'));

var iqrQueryTpl = dot.template([
    'iqrange AS (',
    '  SELECT max(quartile_max) - min(quartile_max) AS iqr',
    '  FROM (',
    '    SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
    '      SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
    '    ) AS quartile',
    '    FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
    '    WHERE quartile = 1 or quartile = 3',
    '    GROUP BY quartile',
    '  ) _cdb_iqr',
    ')'
].join('\n'));

var binsQueryTpl = dot.template([
    'bins AS (',
    '  SELECT CASE WHEN total_rows = 0 OR iqr = 0',
    '      THEN 1',
    '      ELSE GREATEST(',
    '        LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
    '        LEAST(',
    '          CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
    '          {{=it._maxBins}}',
    '        )',
    '      )',
    '    END AS bins_number',
    '  FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
    '  LIMIT 1',
    ')'
].join('\n'));

var overrideBinsQueryTpl = dot.template([
    'bins AS (',
    '  SELECT {{=it._bins}} AS bins_number',
    ')'
].join('\n'));

var nullsQueryTpl = dot.template([
    'nulls AS (',
    '  SELECT',
    '    count(*) AS nulls_count',
    '  FROM ({{=it._query}}) _cdb_histogram_nulls',
    '  WHERE {{=it._column}} IS NULL',
    ')'
].join('\n'));

var histogramQueryTpl = dot.template([
    'SELECT',
    '    (max_val - min_val) / cast(bins_number as float) AS bin_width,',
    '    bins_number,',
    '    nulls_count,',
    '    avg_val,',
    '    CASE WHEN min_val = max_val',
    '      THEN 0',
    '      ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
    '    END AS bin,',
    '    min({{=it._column}})::numeric AS min,',
    '    max({{=it._column}})::numeric AS max,',
    '    avg({{=it._column}})::numeric AS avg,',
    '    count(*) AS freq',
    'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
    'WHERE {{=it._column}} IS NOT NULL',
    'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
    'ORDER BY bin'
].join('\n'));

var dateBasicsQueryTpl = dot.template([
    'basics AS (',
    '    SELECT',
    '        max(date_part(\'epoch\', {{=it._column}})) AS max_val,',
    '        min(date_part(\'epoch\', {{=it._column}})) AS min_val,',
    '        avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
    '        min(date_trunc(',
    '           \'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._timezone}}\'',
    '        )) AS start_date,',
    '        max({{=it._column}} AT TIME ZONE \'{{=it._timezone}}\') AS end_date,',
    '        count(1) AS total_rows',
    '    FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var dateOverrideBasicsQueryTpl = dot.template([
    'basics AS (',
    '    SELECT',
    '        max({{=it._end}}) AS max_val,',
    '        min({{=it._start}}) AS min_val,',
    '        avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
    '        min(date_trunc(',
    '           \'{{=it._aggregation}}\',',
    '           TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._timezone}}\'',
    '        )) AS start_date,',
    '        max(TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._timezone}}\') AS end_date,',
    '        count(1) AS total_rows',
    '    FROM ({{=it._query}}) _cdb_basics',
    ')'
].join(' \n'));

var dateBinsQueryTpl = dot.template([
    'bins AS (',
    '    SELECT',
    '        bins_array,',
    '        ARRAY_LENGTH(bins_array, 1) AS bins_number',
    '    FROM (',
    '       SELECT',
    '           ARRAY(',
    '               {{?it._aggregation==="quarter"}}',
    '               SELECT GENERATE_SERIES(start_date, end_date, \'3 month\'::interval)',
    '               {{??}}',
    '               SELECT GENERATE_SERIES(start_date, end_date, \'1 {{=it._aggregation}}\'::interval)',
    '               {{?}}',
    '           ) AS bins_array',
    '       FROM basics',
    '    ) _cdb_bins_array',
    ')'
].join('\n'));

var dateHistogramQueryTpl = dot.template([
    'SELECT',
    '    (max_val - min_val) / cast(bins_number as float) AS bin_width,',
    '    bins_number,',
    '    nulls_count,',
    '    CASE WHEN min_val = max_val',
    '      THEN 0',
    '      ELSE GREATEST(1, LEAST(',
    '        WIDTH_BUCKET({{=it._column}} AT TIME ZONE \'{{=it._timezone}}\', bins_array),',
    '        bins_number',
    '      )) - 1',
    '    END AS bin,',
    '    min(date_part(',
    '       \'epoch\', ',
    '       date_trunc(\'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._timezone}}\'',
    '    ) AT TIME ZONE \'{{=it._timezone}}\'))::numeric AS timestamp,',
    '    min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,',
    '    max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,',
    '    avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,',
    '    count(*) AS freq',
    'FROM ({{=it._query}}) _cdb_histogram, basics, bins, nulls',
    'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL',
    'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
    'ORDER BY bin'
].join('\n'));

var TYPE = 'histogram';

/**
Numeric histogram:
{
    type: 'histogram',
    options: {
        column: 'name', // column data type: numeric
        bins: 10 // OPTIONAL
    }
}

Time series:
{
    type: 'histogram',
    options: {
        column: 'date', // column data type: date
        aggregation: 'day' // OPTIONAL (if undefined then it'll be built as numeric)
        timezone: -7200 // OPTIONAL (UTC offset in seconds)
    }
 }
 */
function Histogram(query, options, queries) {
    if (!_.isString(options.column)) {
        throw new Error('Histogram expects `column` in widget options');
    }

    this.query = query;
    this.queries = queries;
    this.column = options.column;
    this.bins = options.bins;
    this.aggregation = options.aggregation;
    this.timezone = options.timezone;

    this._columnType = null;
}

Histogram.prototype = new BaseWidget();
Histogram.prototype.constructor = Histogram;

module.exports = Histogram;

var DATE_OIDS = {
    1082: true,
    1114: true,
    1184: true
};

Histogram.prototype.sql = function(psql, override, callback) {
    // jshint maxcomplexity: 7
    if (!callback) {
        callback = override;
        override = {};
    }

    var self = this;

    var _column = this.column;

    var columnTypeQuery = columnTypeQueryTpl({
        column: _column, query: this.queries.no_filters
    });

    if (this._columnType === null) {
        psql.query(columnTypeQuery, function(err, result) {
            // assume numeric, will fail later
            self._columnType = 'numeric';
            if (!err && !!result.rows[0]) {
                var pgType = result.rows[0].pg_typeof;
                if (DATE_OIDS.hasOwnProperty(pgType)) {
                    self._columnType = 'date';
                }
            }
            self.sql(psql, override, callback);
        }, true); // use read-only transaction
        return null;
    }

    if (this._columnType === 'date' && this.aggregation !== undefined) {
        return this._buildDateHistogramQuery(override, callback);
    }

    if (this._columnType === 'date') {
        _column = columnCastTpl({ column: this.column});
    }
    var _query = this.query;

    var basicsQuery, binsQuery;

    if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
        debug('overriding with %j', override);
        basicsQuery = overrideBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _start: getBinStart(override),
            _end: getBinEnd(override)
        });

        binsQuery = [
            overrideBinsQueryTpl({
                _bins: override.bins
            })
        ].join(',\n');
    } else {
        basicsQuery = basicsQueryTpl({
            _query: _query,
            _column: _column
        });

        if (override && _.has(override, 'bins')) {
            binsQuery = [
                overrideBinsQueryTpl({
                    _bins: override.bins
                })
            ].join(',\n');
        } else {
            binsQuery = [
                iqrQueryTpl({
                    _query: _query,
                    _column: _column
                }),
                binsQueryTpl({
                    _query: _query,
                    _minBins: BIN_MIN_NUMBER,
                    _maxBins: BIN_MAX_NUMBER
                })
            ].join(',\n');
        }
    }

    var histogramSql = [
        "WITH",
        [
            basicsQuery,
            binsQuery,
            nullsQueryTpl({
                _query: _query,
                _column: _column
            })
        ].join(',\n'),
        histogramQueryTpl({
            _query: _query,
            _column: _column
        })
    ].join('\n');

    debug(histogramSql);

    return callback(null, histogramSql);
};

Histogram.prototype._buildDateHistogramQuery = function (override, callback) {
    var _column = this.column;
    var _query = this.query;
    var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation;
    var _timezone = override && Number.isFinite(override.timezone) ? override.timezone : this.timezone;

    var dateBasicsQuery;

    if (override && _.has(override, 'start') && _.has(override, 'end')) {
        dateBasicsQuery = dateOverrideBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _aggregation: _aggregation,
            _start: getBinStart(override),
            _end: getBinEnd(override),
            _timezone: getTimezone(_timezone)
        });
    } else {
        dateBasicsQuery = dateBasicsQueryTpl({
            _query: _query,
            _column: _column,
            _aggregation: _aggregation,
            _timezone: getTimezone(_timezone)
        });
    }

    var dateBinsQuery = [
        dateBinsQueryTpl({
            _aggregation: _aggregation
        })
    ].join(',\n');

    var nullsQuery = nullsQueryTpl({
        _query: _query,
        _column: _column
    });

    var dateHistogramQuery = dateHistogramQueryTpl({
        _query: _query,
        _column: _column,
        _aggregation: _aggregation,
        _timezone: getTimezone(_timezone)
    });

    var histogramSql = [
        "WITH",
        [
            dateBasicsQuery,
            dateBinsQuery,
            nullsQuery
        ].join(',\n'),
        dateHistogramQuery
    ].join('\n');

    debug(histogramSql);

    return callback(null, histogramSql);
};

Histogram.prototype.format = function(result, override) {
    override = override || {};
    var buckets = [];

    var binsCount = getBinsCount(override);
    var width = getWidth(override);
    var binsStart = getBinStart(override);
    var nulls = 0;
    var avg;

    if (result.rows.length) {
        var firstRow = result.rows[0];
        binsCount = firstRow.bins_number;
        width = firstRow.bin_width || width;
        avg = firstRow.avg_val;
        nulls = firstRow.nulls_count;
        binsStart = override.hasOwnProperty('start') ?
            getBinStart(override) :
            firstRow.hasOwnProperty('timestamp') ?
            firstRow.timestamp :
            firstRow.min;

        buckets = result.rows.map(function(row) {
            return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
        });
    }

    return {
        bin_width: width,
        bins_count: binsCount,
        bins_start: binsStart,
        nulls: nulls,
        avg: avg,
        bins: buckets
    };
};

function getBinStart(override) {
    if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
        return Math.min(override.start, override.end);
    }
    return override.start || 0;
}

function getBinEnd(override) {
    if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
        return Math.max(override.start, override.end);
    }
    return override.end || 0;
}

function getBinsCount(override) {
    return override.bins || 0;
}

function getWidth(override) {
    var width = 0;
    var binsCount = override.bins;

    if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
        width = (override.end - override.start) / binsCount;
    }

    return width;
}

function getTimezone(timezone) {
    if (!timezone) {
        return '0';
    }
    var timezoneInHours = Math.ceil(timezone / 3600);
    return '' + timezoneInHours;
}

Histogram.prototype.getType = function() {
    return TYPE;
};

Histogram.prototype.toString = function() {
    return JSON.stringify({
        _type: TYPE,
        _column: this.column,
        _query: this.query
    });
};
<|endoftext|>


<commit_before>/**
 * postcss.js - config for grunt-postcss
 */

module.exports = function ( grunt ) {

    var common = grunt.fcoo.common,
        paths  = grunt.fcoo.paths,
        lodash = require('lodash');

    //Get browserslist
    var browserslistJSON = common.readJSONFile('.browserslistrc',
        {
            //Default browserslist
            browserslist: [

                'last 1 Chrome versions',         //Google Chrome.

                'last 1 Firefox versions',        //Mozilla Firefox.
                'ie >= 9',                        //Explorer Internet Explorer.
                'last 1 Edge versions',           //Microsoft Edge.
                'last 1 iOS versions',            //iOS Safari.
                'last 1 Opera versions',          //Opera.
                'last 1 Safari versions',         //desktop Safari.
                'last 1 ExplorerMobile versions', //Internet Explorer Mobile.
                //Other

                'last 1 Android versions',        //Android WebView.
                'last 1 BlackBerry versions',     //Blackberry browser.
                'last 1 ChromeAndroid versions',  //Chrome for Android (in Other section, because mostly same as common Chrome).
                'last 1 FirefoxAndroid versions', //Firefox for Android.
                'last 1 OperaMobile versions'     //Opera Mobile.

                //'last 1 OperaMini versions',      //Opera Mini
            ]
        }
    );
    var browserslist = browserslistJSON.browserslist.join(',');

    //Variables and function used to create caniuse-log using caniuse.com DB
    grunt.fcoo.caniuseFeatures = [];
    function caniuse_onFeatureUsage ( usageInfo ) {
        var featureList = grunt.fcoo.caniuseFeatures[usageInfo.feature] || [];
        featureList.push(usageInfo);
        grunt.fcoo.caniuseFeatures[usageInfo.feature] = featureList;
    }

    return {
        //optimize: optimize using cssnano but no minimizing
        optimize: {
            options: {
                map: false,
                processors: [
                    //PostCSS plugin that generates pixel fallbacks for rem units.
                    require('pixrem')({
                        rootValue    : 16,           //Default=16     rootValue the root element font size. Can be px, rem, em, %, or unitless pixel value. Pixrem also tries to get the root font-size from CSS (html or :root) and overrides this option. Use html option to disable this behaviour.
                        replace      : false,        //Default=false  replace replaces rules containing rems instead of adding fallbacks.
                        atrules      : false,        //Default=false  atrules generates fallback in at-rules too (media-queries)
                        html         : true,         //Default=true   html overrides root font-size from CSS html {} or :root {}
                        browsers     : browserslist, //               browsers sets browser's range you want to target, based on browserslist
                        unitPrecision: 3             //Default=3      unitPrecision control the significant digits after the decimal point
                    }),
                    require('cssnano')({
                        autoprefixer   : { browsers: browserslist },
                        discardComments: false, //Leave comments
                        core           : false, //Leave NL
                        safe           : true,
                    })
                ]
            },
            src : paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css,
            dest: paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css
        },

        //minimize: Minimize into APPLICATIONNAME_TIMPSTAMP.min.css and create source map file
        minimize: {
            options: {
                map: {
                    inline: false,
                    prev  : false
                },
                processors: [
                    require('cssnano')({
                        discardComments: { removeAll: true },
                        core           : true,
                        safe           : true,
                    })
                ]
            },
            src : paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css,
            dest: paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_min_css
        },

        //caniuse - create a log-file with status from caniuse.com
        caniuse: {
            options: {
                map      : false,
                writeDest: false,
                processors: [
                    require('doiuse')({
                        browsers      : browserslist,
                        ignore        : ['rem'], //Is fixed in postcss:optimize by pixrem
                        onFeatureUsage: caniuse_onFeatureUsage
                    })
                ]
            }, //end of options
            src  : paths.dist + paths.APPLICATIONNAME_TIMPSTAMP_css
        } //end of caniuse

    }
}<commit_msg>Update cssnano config: Don't check for rem=>px browserlist read from .browserslistrc Removed caniuse<commit_after>/**
 * postcss.js - config for grunt-postcss
 */

module.exports = function ( grunt ) {

    var paths  = grunt.fcoo.paths;

    return {
        //optimize: optimize using cssnano but no minimizing
        optimize: {
            options: {
                map: false,
                processors: [
                    require('cssnano')({
                        discardComments: false, //Leave comments
                        core           : false, //Leave NL
                        safe           : true
                    })
                ]
            },
            src : paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css,
            dest: paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css
        },

        //minimize: Minimize into APPLICATIONNAME_TIMPSTAMP.min.css and create source map file
        minimize: {
            options: {
                map: {
                    inline: false,
                    prev  : false
                },
                processors: [
                    require('cssnano')({
                        discardComments: { removeAll: true },
                        core           : true,
                        safe           : true,
                    })
                ]
            },
            src : paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_css,
            dest: paths.temp_dist_APPLICATIONNAME_TIMPSTAMP_min_css
        },
    }
}<|endoftext|>


<commit_before>// Minify JS
module.exports = function(grunt) {
    grunt.config('uglify', {
        options: {
            sourceMap: true
        },
        deploy: {
            src: 'src/scripts/master.js',
            dest: 'dist/scripts/master.js'
        }
    });
};
<commit_msg>Use original concat sourcemap for production build<commit_after>// Minify JS
module.exports = function(grunt) {
    grunt.config('uglify', {
        options: {
            sourceMap: true,
            sourceMapIn: 'src/scripts/master.js.map'
        },
        deploy: {
            src: 'src/scripts/master.js',
            dest: 'dist/scripts/master.js'
        }
    });
};
<|endoftext|>