<commit_before>// load our config file
var config = require('./config');
var Twit = require('twit');
// stat the twit stuff
var T = new Twit({
  consumer_key: config.consumer_key,
  consumer_secret: config.consumer_secret,
  access_token: config.access_token,
  access_token_secret: config.access_token_secret
});

// filter the public stream by english tweets containing `#toronto, #ldnont, #BostonStrong`
var stream = T.stream('statuses/filter', {
  track: ['#virtuoso14', '#virtuoso2014', '#iabc', '#iabclondon', '#iabcldnont', '#virtuoso', '#virtuosoawards', '@iabclondon', '@EdgarAndJoes'], // '#virtuoso14', '#iabc', '#iabclondon', '#iabcldnont', '#virtuoso', '#virtuosoawards', '@iabclondon', '@EdgarAndJoes'
  language: 'en'
});

var hue = require("node-hue-api"),
  HueApi = hue.HueApi,
  lightState = hue.lightState;

// flash when tweets
var flash = lightState.create().white(154, 100).transition(0);
// dim when flash is over
var dim = lightState.create().white(154, 10).transition(0);

var Emitter = require('events').EventEmitter,
    emitter = new Emitter();
    emitter.setMaxListeners(2);

var colors = [
  {
    r: 149,
    g: 210,
    b: 184
  }, // teal
  {
    r: 246,
    g: 30,
    b: 47
  }, // red
  {
    r: 200,
    g: 255,
    b: 138
  }, // green
  {
    r: 255,
    g: 175,
    b: 79
  }, // orange
  {
    r: 165,
    g: 25,
    b: 41
  } // maroon
];

var names = ['teal', 'red', 'green', 'orange', 'maroon'];

emitter.on('starting', function (i) {
  console.log('starting', names[i]);
});

var currentLight = 2; // my one light that is setup

function loop(api) {
  var counter = 0;
  // number of seconds to wait
  var transition = 1;
  var looper = setInterval(function () {
    emitter.emit('starting', counter);
    var state = lightState.create().rgb(
      colors[counter].r,
      colors[counter].g,
      colors[counter].b
    ).brightness(50).transition(transition);
    api.setGroupLightState(0, state);
    counter += 1;
    if (counter === colors.length) {
      counter = 0;
    }
  }, transition * 5250);
  emitter.on('tweet', function handleTweet() {
    clearInterval(looper);
    // use a noop function to avoid errors
    emitter.removeListener('tweet', function(){});
  });
}

// find the hue base on this network
hue.locateBridges(function (err, result) {
  if (err) {
    throw err;
  }
  // connect using the located IP and the config username
  var api = new HueApi(result[0].ipaddress, config.username);
  // start the loop with the active api
  loop(api);

  stream.on('tweet', function (tweet) {
    console.log("> %s \r", tweet.text);
    emitter.emit('tweet');
    api.setGroupLightState(0, flash).then(loop.bind(this, api));
  });
});
<commit_msg>update timinig a little<commit_after>// load our config file
var config = require('./config');
var Twit = require('twit');
// stat the twit stuff
var T = new Twit({
  consumer_key: config.consumer_key,
  consumer_secret: config.consumer_secret,
  access_token: config.access_token,
  access_token_secret: config.access_token_secret
});

// filter the public stream by english tweets containing `#toronto, #ldnont, #BostonStrong`
var stream = T.stream('statuses/filter', {
  track: ['#virtuoso14', '#virtuoso2014', '#iabc', '#iabclondon', '#iabcldnont', '#virtuoso', '#virtuosoawards', '@iabclondon', '@EdgarAndJoes'], // '#virtuoso14', '#iabc', '#iabclondon', '#iabcldnont', '#virtuoso', '#virtuosoawards', '@iabclondon', '@EdgarAndJoes'
  language: 'en'
});

var hue = require("node-hue-api"),
  HueApi = hue.HueApi,
  lightState = hue.lightState;

// flash when tweets
var flash = lightState.create().white(154, 100).transition(0);
// dim when flash is over
var dim = lightState.create().white(154, 10).transition(0);

var Emitter = require('events').EventEmitter,
    emitter = new Emitter();
    emitter.setMaxListeners(2);

var colors = [
  {
    r: 149,
    g: 210,
    b: 184
  }, // teal
  {
    r: 246,
    g: 30,
    b: 47
  }, // red
  {
    r: 200,
    g: 255,
    b: 138
  }, // green
  {
    r: 255,
    g: 175,
    b: 79
  }, // orange
  {
    r: 165,
    g: 25,
    b: 41
  } // maroon
];

var names = ['teal', 'red', 'green', 'orange', 'maroon'];

emitter.on('starting', function (i) {
  console.log('starting', names[i]);
});

var currentLight = 2; // my one light that is setup

function loop(api) {
  var counter = 0;
  // number of seconds to wait
  var transition = 1;
  var looper = setInterval(function () {
    emitter.emit('starting', counter);
    var state = lightState.create().rgb(
      colors[counter].r,
      colors[counter].g,
      colors[counter].b
    ).brightness(100).transition(transition);
    api.setGroupLightState(0, state);
    counter += 1;
    if (counter === colors.length) {
      counter = 0;
    }
  }, transition * 5250);
  emitter.on('tweet', function handleTweet() {
    clearInterval(looper);
    // use a noop function to avoid errors
    emitter.removeListener('tweet', function(){});
  });
}

// find the hue base on this network
hue.locateBridges(function (err, result) {
  if (err) {
    throw err;
  }
  // connect using the located IP and the config username
  var api = new HueApi(result[0].ipaddress, config.username);
  // start the loop with the active api
  loop(api);

  stream.on('tweet', function (tweet) {
    console.log("> %s \r", tweet.text);
    emitter.emit('tweet');
    api.setGroupLightState(0, flash).then(loop.bind(this, api));
  });
});
<|endoftext|>


<commit_before>require(['async!https://maps.googleapis.com/maps/api/js?v=3.24&libraries=geometry&sensor=false'], function() {
        /*
      Initialize variables
     */
    var map = new google.maps.Map(document.getElementById('map'));
    var boundariesGeoJSON = 'https://gist.githubusercontent.com/anonymous/0b1f9831d6d4941206f7/raw/b1c4dd632b4466e596169639dcba3bebda693d4e/map.geojson'
    // Chicago City box points
    var NorthEast = new google.maps.LatLng(42.023135, -87.523661);
    var NorthWest = new google.maps.LatLng(42.023135, -87.940101);
    var SouthWest = new google.maps.LatLng(41.644286, -87.940101 );
    var SouthEast = new google.maps.LatLng(41.644286, -87.523661);
    /*
      Set initial map properties
     */
    function initializeMap() {
      var fit = new google.maps.LatLngBounds(SouthEast, NorthEast);
      map.fitBounds(fit);
      map.data.loadGeoJson(boundariesGeoJSON, null, function(feat){
        boundary = feat[0].getGeometry();
        citybounds = new google.maps.Polygon({paths:boundary.getAt(0).getArray()})
        map.data.setStyle(function(feature) {
            var color = 'red';
            var fill = 'gray';
            return /** @type {google.maps.Data.StyleOptions} */({
                fillColor: fill,
                strokeColor: color,
                strokeWeight: 2,
            });
        });
      });
    }
    initializeMap();

    /*
      Bind page actions
     */
    function bindActions() {
      google.maps.event.addDomListener(window, "resize", resizeMap);
    }
    bindActions();

    /*
      Event handler for making Google Map responsive
     */
    function resizeMap() {
       var center = map.getCenter();
       google.maps.event.trigger(map, "resize");
       map.setCenter(center);
    }
});<commit_msg>show grid<commit_after>require(['async!https://maps.googleapis.com/maps/api/js?v=3.24&libraries=geometry&sensor=false'], function() {
        /*
      Initialize variables
     */
    var map = new google.maps.Map(document.getElementById('map'));
    var boundariesGeoJSON = 'https://gist.githubusercontent.com/anonymous/0b1f9831d6d4941206f7/raw/b1c4dd632b4466e596169639dcba3bebda693d4e/map.geojson'
    // Chicago City box points
    var NorthEast = new google.maps.LatLng(42.023135, -87.523661);
    var NorthWest = new google.maps.LatLng(42.023135, -87.940101);
    var SouthWest = new google.maps.LatLng(41.644286, -87.940101 );
    var SouthEast = new google.maps.LatLng(41.644286, -87.523661);
    var grid_size = 1000;
    var rectArr = [];
    /*
      Set initial map properties
     */
    function initializeMap() {
      var fit = new google.maps.LatLngBounds(SouthEast, NorthEast);
      map.fitBounds(fit);
      map.data.loadGeoJson(boundariesGeoJSON, null, function(feat){
        boundary = feat[0].getGeometry();
        citybounds = new google.maps.Polygon({paths:boundary.getAt(0).getArray()})
        map.data.setStyle(function(feature) {
            var color = 'red';
            var fill = 'gray';
            return /** @type {google.maps.Data.StyleOptions} */({
                fillColor: fill,
                strokeColor: color,
                strokeWeight: 2,
            });
        });
        drawGrid();
      });
    }
    initializeMap();

    /*
      Bind page actions
     */
    function bindActions() {
      google.maps.event.addDomListener(window, "resize", resizeMap);
    }
    bindActions();

    /*
      Event handler for making Google Map responsive
     */
    function resizeMap() {
       var center = map.getCenter();
       google.maps.event.trigger(map, "resize");
       map.setCenter(center);
    }

    function drawGrid() {
        var rect_number = 0;
        var distance = grid_size;  // grid size distance, you can change this value from grid_size (in meters)
        var dist_coords_long = google.maps.geometry.spherical.computeOffset(NorthWest, distance, 90);
        dist_coords_long = dist_coords_long.lng() - NorthWest.lng();
        var dist_coords_lat = google.maps.geometry.spherical.computeOffset(NorthWest, distance, 180);
        dist_coords_lat = NorthWest.lat() - dist_coords_lat.lat();

        var NW = google.maps.geometry.spherical.computeOffset(NorthWest, distance, 270);

        // check if current square is has not reached the southwest border.
        while(NW.lat() >= SouthWest.lat()) {
            curr_NE = new google.maps.LatLng(NW.lat(), NW.lng() + dist_coords_long);
            curr_SW = new google.maps.LatLng(NW.lat() - dist_coords_lat, NW.lng());
            curr_NW = NW;
            NW = new google.maps.LatLng(NW.lat() - dist_coords_lat, NW.lng());
            // check if current square has not reached the northeast border
            while(curr_NW.lng() <= NorthEast.lng()) {
                // create new southwest and northeast coordnates for a new square
                NE = new google.maps.LatLng(curr_NE.lat(), curr_NE.lng() + dist_coords_long);
                SW = new google.maps.LatLng(curr_SW.lat(), curr_SW.lng() + dist_coords_long);
                // save current square coordinates
                curr_NW = NE;
                curr_NE = NE;
                curr_SW = SW;

                // create bounds from southwest and northeast coordinates
                var bounds = new google.maps.LatLngBounds(SW,NE)
                var bound_NW = new google.maps.LatLng(NE.lat(),SW.lng());
                var bound_SE = new google.maps.LatLng(SW.lat(), NE.lng());

                //check if any of the new bounds vertices is within the city bounds
                if (google.maps.geometry.poly.containsLocation(bound_NW, citybounds) ||
                    google.maps.geometry.poly.containsLocation(NE, citybounds) ||
                    google.maps.geometry.poly.containsLocation(SW, citybounds) ||
                    google.maps.geometry.poly.containsLocation(bound_SE, citybounds)){

                    var rectangle = new google.maps.Rectangle();
                    var rectOptions = {
                        strokeColor: "gray",
                        strokeOpacity: 0.8,
                        strokeWeight: 2,
                        fillOpacity: 0,
                        map: map,
                        bounds: bounds,
                        counts: 0,
                        number: rect_number,
                        merged: false,
                    };
                    rectangle.setOptions(rectOptions);
                    rectangle.getBounds().extend(bound_NW);
                    rectangle.getBounds().extend(bound_SE);
                    rectArr.push(rectangle);
                }
            }
        }
    }

});<|endoftext|>


<commit_before>/**
 * Idea Hub module Tour.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { EVENTS } from 'react-joyride';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

/*
 * Internal dependencies
 */
import { VIEW_CONTEXT_DASHBOARD } from '../googlesitekit/constants';
import { CORE_MODULES } from '../googlesitekit/modules/datastore/constants';
import {
	MODULES_IDEA_HUB,
	IDEA_HUB_GA_CATEGORY_WIDGET,
} from '../modules/idea-hub/datastore/constants';

const ideaHubModule = {
	slug: 'ideaHubModule',
	contexts: [ VIEW_CONTEXT_DASHBOARD ],
	// TODO: change this if launch version for the feature changes.
	version: '3.6.0',
	checkRequirements: async ( registry ) => {
		await registry.__experimentalResolveSelect( CORE_MODULES ).getModules();
		const isIdeaHubModuleActive = registry
			.select( CORE_MODULES )
			.isModuleActive( 'idea-hub' );
		const isIdeaHubModuleConnected = registry
			.select( CORE_MODULES )
			.isModuleConnected( 'idea-hub' );

		if ( ! isIdeaHubModuleActive || ! isIdeaHubModuleConnected ) {
			return false;
		}

		const newIdeas = await registry
			.__experimentalResolveSelect( MODULES_IDEA_HUB )
			.getNewIdeas();

		return !! newIdeas.length;
	},
	steps: [
		{
			target: '.googlesitekit-idea-hub__title',
			title: __(
				'Get inspiration for new topics to write about!',
				'google-site-kit'
			),
			content: __(
				'These ideas are based on unanswered searches related to the content of your site. They are organized by topics and will refresh every 2-3 days.',
				'google-site-kit'
			),
		},
		{
			target: '.googlesitekit-idea-hub__actions--create',
			title: __( 'Start a draft', 'google-site-kit' ),
			content: __(
				'Found an interesting idea you want to write about? Create a draft! You can always get back to it later on the Posts page.',
				'google-site-kit'
			),
		},
		{
			target: '.googlesitekit-idea-hub__idea--single',
			title: __( 'Save for later or dismiss', 'google-site-kit' ),
			content: __(
				'If you’re not ready to create a draft about an idea just yet, add it to your "Saved" list and revisit later. If you don’t like an idea, you can dismiss it from your list.',
				'google-site-kit'
			),
			placement: 'top',
		},
	],
	gaEventCategory: IDEA_HUB_GA_CATEGORY_WIDGET,
	callback: ( data ) => {
		/*
		 * The third step of the feature tour involves the 'save' (pin) and
		 * 'dismiss' buttons, both of which are hidden unless you hover over
		 * them. As an enhancement, we will un-hide these buttons so they are
		 * visible during the final step of the tour by adding a CSS class,
		 * then remove the class once that step is finished.
		 */

		const { type, index } = data;
		if (
			! [ EVENTS.STEP_BEFORE, EVENTS.STEP_AFTER ].includes( type ) ||
			index !== 2
		) {
			return;
		}

		const unhideElementClass = 'googlesitekit-idea-hub__actions--unhide';

		const pinElement = global.document.querySelector(
			'.googlesitekit-idea-hub__actions--pin'
		);
		const dismissElement = global.document.querySelector(
			'.googlesitekit-idea-hub__actions--delete'
		);

		if ( ! pinElement || ! dismissElement ) {
			return;
		}

		if ( type === EVENTS.STEP_BEFORE ) {
			// Before final step, add the CSS class to the save / dismiss elements
			// to un-hide them.
			pinElement.classList.add( unhideElementClass );
			dismissElement.classList.add( unhideElementClass );
		}

		if ( type === EVENTS.STEP_AFTER ) {
			// After final step, remove the CSS class to the save / dismiss elements
			// to return them to normal.
			pinElement.classList.remove( unhideElementClass );
			dismissElement.classList.remove( unhideElementClass );
		}
	},
};

export default ideaHubModule;
<commit_msg>Update feature tour target to be entire Idea Hub widget.<commit_after>/**
 * Idea Hub module Tour.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { EVENTS } from 'react-joyride';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

/*
 * Internal dependencies
 */
import { VIEW_CONTEXT_DASHBOARD } from '../googlesitekit/constants';
import { CORE_MODULES } from '../googlesitekit/modules/datastore/constants';
import {
	MODULES_IDEA_HUB,
	IDEA_HUB_GA_CATEGORY_WIDGET,
} from '../modules/idea-hub/datastore/constants';

const ideaHubModule = {
	slug: 'ideaHubModule',
	contexts: [ VIEW_CONTEXT_DASHBOARD ],
	// TODO: change this if launch version for the feature changes.
	version: '3.6.0',
	checkRequirements: async ( registry ) => {
		await registry.__experimentalResolveSelect( CORE_MODULES ).getModules();
		const isIdeaHubModuleActive = registry
			.select( CORE_MODULES )
			.isModuleActive( 'idea-hub' );
		const isIdeaHubModuleConnected = registry
			.select( CORE_MODULES )
			.isModuleConnected( 'idea-hub' );

		if ( ! isIdeaHubModuleActive || ! isIdeaHubModuleConnected ) {
			return false;
		}

		const newIdeas = await registry
			.__experimentalResolveSelect( MODULES_IDEA_HUB )
			.getNewIdeas();

		return !! newIdeas.length;
	},
	steps: [
		{
			target: '.googlesitekit-widget--ideaHubIdeas',
			title: __(
				'Get inspiration for new topics to write about!',
				'google-site-kit'
			),
			content: __(
				'These ideas are based on unanswered searches related to the content of your site. They are organized by topics and will refresh every 2-3 days.',
				'google-site-kit'
			),
		},
		{
			target: '.googlesitekit-idea-hub__actions--create',
			title: __( 'Start a draft', 'google-site-kit' ),
			content: __(
				'Found an interesting idea you want to write about? Create a draft! You can always get back to it later on the Posts page.',
				'google-site-kit'
			),
		},
		{
			target: '.googlesitekit-idea-hub__idea--single',
			title: __( 'Save for later or dismiss', 'google-site-kit' ),
			content: __(
				'If you’re not ready to create a draft about an idea just yet, add it to your "Saved" list and revisit later. If you don’t like an idea, you can dismiss it from your list.',
				'google-site-kit'
			),
			placement: 'top',
		},
	],
	gaEventCategory: IDEA_HUB_GA_CATEGORY_WIDGET,
	callback: ( data ) => {
		/*
		 * The third step of the feature tour involves the 'save' (pin) and
		 * 'dismiss' buttons, both of which are hidden unless you hover over
		 * them. As an enhancement, we will un-hide these buttons so they are
		 * visible during the final step of the tour by adding a CSS class,
		 * then remove the class once that step is finished.
		 */

		const { type, index } = data;
		if (
			! [ EVENTS.STEP_BEFORE, EVENTS.STEP_AFTER ].includes( type ) ||
			index !== 2
		) {
			return;
		}

		const unhideElementClass = 'googlesitekit-idea-hub__actions--unhide';

		const pinElement = global.document.querySelector(
			'.googlesitekit-idea-hub__actions--pin'
		);
		const dismissElement = global.document.querySelector(
			'.googlesitekit-idea-hub__actions--delete'
		);

		if ( ! pinElement || ! dismissElement ) {
			return;
		}

		if ( type === EVENTS.STEP_BEFORE ) {
			// Before final step, add the CSS class to the save / dismiss elements
			// to un-hide them.
			pinElement.classList.add( unhideElementClass );
			dismissElement.classList.add( unhideElementClass );
		}

		if ( type === EVENTS.STEP_AFTER ) {
			// After final step, remove the CSS class to the save / dismiss elements
			// to return them to normal.
			pinElement.classList.remove( unhideElementClass );
			dismissElement.classList.remove( unhideElementClass );
		}
	},
};

export default ideaHubModule;
<|endoftext|>


<commit_before>module.exports = function( grunt ) {

	"use strict";

	grunt.registerTask( "testswarm", function( commit, configFile, browserSets ) {
		var jobName,
			testswarm = require( "testswarm" ),
			runs = {},
			done = this.async(),
			pull = /PR-(\d+)/.exec( commit ),
			config = grunt.file.readJSON( configFile ).jquery,
			tests = grunt.config([ this.name, "tests" ]);

		if ( !browserSets ) {
			browserSets = [ "popular-no-old-ie", "ios" ];
		}

		if ( pull ) {
			jobName = "Pull #" + pull[ 1 ] + "";
		} else {
			jobName = "Commit " + commit.substr( 0, 10 ) + "";
		}

		tests.forEach(function( test ) {
			runs[ test ] = config.testUrl + commit + "/test/index.html?module=" + test;
		});

		testswarm.createClient( {
			url: config.swarmUrl,
			pollInterval: 10000,
			timeout: 1000 * 60 * 30
		} )
		.addReporter( testswarm.reporters.cli )
		.auth( {
			id: config.authUsername,
			token: config.authToken
		})
		.addjob(
			{
				name: jobName,
				runs: runs,
				runMax: config.runMax,
				browserSets: browserSets
			}, function( err, passed ) {
				if ( err ) {
					grunt.log.error( err );
				}
				done( passed );
			}
		);
	});
};
<commit_msg>Build: Allow to specify timeout in the testswarm task<commit_after>module.exports = function( grunt ) {

	"use strict";

	grunt.registerTask( "testswarm", function( commit, configFile, browserSets, timeout ) {
		var jobName,
			testswarm = require( "testswarm" ),
			runs = {},
			done = this.async(),
			pull = /PR-(\d+)/.exec( commit ),
			config = grunt.file.readJSON( configFile ).jquery,
			tests = grunt.config([ this.name, "tests" ]);

		if ( pull ) {
			jobName = "Pull #" + pull[ 1 ] + "";
		} else {
			jobName = "Commit " + commit.substr( 0, 10 ) + "";
		}

		tests.forEach(function( test ) {
			runs[ test ] = config.testUrl + commit + "/test/index.html?module=" + test;
		});

		testswarm.createClient( {
			url: config.swarmUrl,
			pollInterval: 10000,
			timeout: timeout || 1000 * 60 * 30
		} )
		.addReporter( testswarm.reporters.cli )
		.auth( {
			id: config.authUsername,
			token: config.authToken
		})
		.addjob(
			{
				name: jobName,
				runs: runs,
				runMax: config.runMax,
				browserSets: browserSets || [ "popular-no-old-ie", "ios" ]
			}, function( err, passed ) {
				if ( err ) {
					grunt.log.error( err );
				}
				done( passed );
			}
		);
	});
};
<|endoftext|>


<commit_before>/*
 * This script runs through a few checks and lets us know if something is off
 */

import models, { sequelize } from '../../server/models';
import emailLib from '../../server/lib/email';

const VERBOSE = true;
let result = '';
let start;

const done = (err) => {
  if (err) result = result.concat('err', err);
  result = result.concat("\n\nTotal time taken: ", new Date() - start, "ms")
  console.log('\ndone!\n');
  console.log(result);
  const subject = `Daily ledger health report - ${(new Date()).toLocaleDateString()}`;
  return emailLib.sendMessage(
    'ops@opencollective.com',
    subject,
    '', {
      bcc: ' ',
      text: result
    })
    .then(process.exit)
    .catch(console.error)
}

const checkHostsUserOrOrg = () => {

  result = result.concat('\n>>> Checking Hosts to be USER or ORG...\n')

  const hostErrors = [];

  // Check that a Host is a User or an ORG
  return sequelize.query(`
    WITH hosts as (SELECT distinct("HostCollectiveId") from "Collectives")

    SELECT * from "Collectives"
    WHERE id IN (SELECT * FROM hosts);
    `, { type: sequelize.QueryTypes.SELECT})
    .then(hostCollectives => {
      result = result.concat('\n\t>>> Hosts found: ', hostCollectives.length);
      return hostCollectives
    })
    .each(hostCollective => {
      if (hostCollective.type !== 'USER' && hostCollective.type !== 'ORGANIZATION') {
        hostErrors.push(hostCollective);
      }
    })
    .then(() => {
      result = result.concat('\n\t>>> Hosts found with incorrect type: ', hostErrors.length)
      if (VERBOSE)
        result = result.concat(hostErrors && JSON.stringify(hostErrors.map(h => Object.assign({slug: h.slug, type: h.type}))));
    });
}

// Ensure all Collectives are setup properly
const checkHostCollectives = () => {

  result = result.concat('\n>>> Checking Host Collectives...\n')

  // Check that a collective is not setup to host itself or be it's own parentCollectiveId
  return models.Collective.findAll({
    where: {
      HostCollectiveId: {
        $col: 'id'
      }
    }
  })
  .then(selfReferencingHosts => {
    result = result.concat('\n\t>>> Self-referencing Hosts found: ', selfReferencingHosts.length)
    if (VERBOSE)
      result = result.concat(selfReferencingHosts && JSON.stringify(selfReferencingHosts.map(h => Object.assign({slug: h.slug, id: h.id}))))
  })
}

// TODO: make sure every Host has a Stripe Account
const checkHostStripeAccount = () => {
  return sequelize.query(`
  WITH hosts AS
    (SELECT DISTINCT("HostCollectiveId") AS id FROM "Collectives" c
      WHERE "HostCollectiveId" IS NOT NULL)

  SELECT h.id FROM hosts h
  LEFT JOIN "ConnectedAccounts" ca ON (h.id = ca."CollectiveId")
  WHERE ca."CollectiveId" IS NULL
    `, { type: sequelize.QueryTypes.SELECT})
  .then(hostsWithoutStripe => {
    result = result.concat('\n\t>>> Hosts without Stripe: ', hostsWithoutStripe.length)
    if (VERBOSE)
      result = result.concat(hostsWithoutStripe && JSON.stringify(hostsWithoutStripe.map(h => h.id).join(', ')).concat('\n'));
  })
}

const checkUsersAndOrgs = () => {

  result = result.concat('\n>>> Checking USER and ORG Collectives\n')

  // Check that no User or ORG has a HostCollectiveId or ParentCollectiveId
  return models.Collective.findAll({
    where: {
      type: {
        $or: ['USER', 'ORGANIZATION']
      },
      HostCollectiveId: {
        $ne: null
      }
    }
  })
  .then(collectives => {
    result = result.concat('\n\t>>> USER or ORGs found with HostCollectiveId: ', collectives.length)
    if (VERBOSE)
      result = result.concat(JSON.stringify(collectives.map(c => Object.assign({slug: c.slug, HostCollectiveId: c.HostCollectiveId}))))
  })
  // TODO: Check that no non-USER Collective is directly linked to a USER
  .then(() => models.User.findAll({
    attributes: ['CollectiveId']
  }))
  .then(userCollectives => models.Collective.findAll({
    where: {
      id: {
        $in: userCollectives.map(u => u.CollectiveId)
      },
      type: {
        $ne: 'USER'
      }
    }
  }))
  .then(improperlyLinkedCollectives => {
    result = result.concat('\n\t>>> non-User collectives that are linked to a USER: ', improperlyLinkedCollectives.length);
    if (VERBOSE)
      result = result.concat(JSON.stringify(improperlyLinkedCollectives.map(c => Object.assign({id: c.id, slug: c.slug}))));
  })
}

const checkMembers = () => {
  result = result.concat('\n\n>>> Checking Members table\n');

  return models.Member.findAll({
    where: {
      MemberCollectiveId: {
        $col: 'CollectiveId'
      }
    }
  })
  .then(circularMembers => {
    result = result.concat('\n\t>>> Members with CollectiveId = MemberCollectiveId: ', circularMembers.length)
    if (VERBOSE)
      result = result.concat(JSON.stringify(circularMembers.map(cm => cm.id).join(', '))).concat('\n')
  })
}

// TODO: may need to disalbe this check if production sees impact
// Check orders
const checkOrders = () => {

  result = result.concat('\n>>> Check orders\n');

  // Check that FromCollectiveId on an Order matches all Transactions
  const brokenOrders = [];

  return sequelize.query(`
    SELECT * from "Orders"
    WHERE "deletedAt" is null AND "processedAt" is not null AND "CollectiveId" != 1
    `, { type: sequelize.QueryTypes.SELECT
    })
    .then(orders => {
      result = result.concat('\n\t>>> orders found: ', orders.length);
      return orders;
    })
    .each(order => {
      return sequelize.query(`
        SELECT distinct("FromCollectiveId") from "Transactions"
        WHERE "OrderId" = :orderId AND type LIKE 'CREDIT' AND "deletedAt" is null
        `, {
          type: sequelize.QueryTypes.SELECT,
          replacements: {
            orderId: order.id
          }
        })
        .then(FromCollectiveIds => {
          if (FromCollectiveIds.length > 1) {
            brokenOrders.push(order.id);
          }
          return Promise.resolve();
        })
    })
    .then(() => {
      result = result.concat('\n\t>>> orders found with mismatched FromCollectiveId: ', brokenOrders.length,
        '\n');
      if (VERBOSE)
        result = result.concat(JSON.stringify(brokenOrders))
    })
}

// Check expenses
const checkExpenses = () => {

  result = result.concat('\n\n>>> Check expenses\n');

  // Check that there are no expenses marked as "PAID" and without transaction entries
  return sequelize.query(`
    SELECT
      e.id AS id
    FROM "Expenses" e
    LEFT JOIN "Transactions" t ON t."ExpenseId" = e.id
    WHERE e.status ILIKE 'paid' AND t.id IS NULL AND  e."deletedAt" IS NULL
    ORDER BY "ExpenseId" DESC, e."updatedAt"
    `, { type: sequelize.QueryTypes.SELECT
    })
    .then(expenses => {
      result = result.concat('\n\t>>> Paid expenses found without transactions: ', expenses.length);
      if (VERBOSE)
        result = result.concat('\n');
        result = result.concat(expenses && JSON.stringify(expenses.map(e => Object.assign({id: e.id}))))
    })
}

// Check all transactions
const checkTransactions = () => {

  result = result.concat('\n\n>>> Checking Transactions...\n')

  // Check every transaction has a "FromCollectiveId"
  return models.Transaction.count({
    where: {
      FromCollectiveId: {
        $eq: null
      }
    }
  })
  .then(txsWithoutFromCollectiveId => {
    result = result.concat('\n\t>>> Transactions without `FromCollectiveId`:', txsWithoutFromCollectiveId);
  })

  // Check no transaction has same "FromCollectiveId" and "CollectiveId"
  .then(() => models.Transaction.findAll({
    where: {
      CollectiveId: {
        $col: 'FromCollectiveId'
      }
    }
  }))
  .then(circularTxs => {
    result = result.concat('\n\t>>> Transactions with same source and destination: ', circularTxs.length)
    if (VERBOSE)
      result = result.concat(JSON.stringify(circularTxs.map(t => Object.assign({id: t.id}))))
  })

  // check no transactions without TransactionGroup
  .then(() => models.Transaction.count({
    where: {
      TransactionGroup: {
        $eq: null
      }
    }
  }))
  .then(txnsWithoutTransactionGroup => {
    result = result.concat('\n\t>>> Transactions without `TransactionGroup`: ', txnsWithoutTransactionGroup)
  })

  // Check every Order has even number of entries
  .then(() => sequelize.query(`
    SELECT "OrderId" FROM "Transactions"
        WHERE "OrderId" IS NOT NULL and "deletedAt" is null
          GROUP BY "OrderId"
          HAVING COUNT(*) % 2 != 0
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddOrderIds => {
    result = result.concat('\n\t>>> Orders with odd (not multiple of 2) number of transactions: ', oddOrderIds.length)
  })

  // Check every Expense has a double Entry
  .then(() => sequelize.query(`
    SELECT "ExpenseId" FROM "Transactions"
        WHERE "ExpenseId" IS NOT NULL and "deletedAt" is null
          GROUP BY "ExpenseId"
          HAVING COUNT(*) != 2
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddExpenseIds => {
    result = result.concat('\n\t>>> Expenses with less than or more than 2 transactions: ', oddExpenseIds.length, '\n')
    if (VERBOSE)
      result = result.concat(JSON.stringify(oddExpenseIds))
  })

  // Check all TransactionGroups have two entries, one CREDIT and one DEBIT
  .then(() => sequelize.query(`
    SELECT "TransactionGroup" FROM "Transactions"
        WHERE "TransactionGroup" IS NOT NULL and "deletedAt" is null
          GROUP BY "TransactionGroup"
          HAVING COUNT(*) != 2
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddTxnGroups => {
    result = result.concat('\n\t>>> Transaction groups that are not pairs: ', oddTxnGroups.length)
    if (VERBOSE)
      result = result.concat(oddTxnGroups);
  })

  // Check no transactions without either an Expense or Order
  .then(() => models.Transaction.findAll({
    where: {
      OrderId: {
        $eq: null
      },
      ExpenseId: {
        $eq: null
      }
    }
  }))
  .then(txnsWithoutOrderOrExpenses => {
    result = result.concat('\n\t>>> Transactions without OrderId or ExpenseId: ', txnsWithoutOrderOrExpenses.length)
    // TODO: reenable when this count is lower than 600
    // if (VERBOSE)
    //  txnsWithoutOrderOrExpenses.map(t => Object.assign({id: t.id}));
  })

  // Check that various fees and amounts add up
  // TODO
}

const checkCollectiveBalance = () => {

  const brokenCollectives = [];
  result = result.concat('\n\n>>> Checking balance of each (non-USER, non-ORG) collective\n');
  return models.Collective.findAll({
    where: {
      $or: [{type: 'COLLECTIVE'}, {type: 'EVENT'}]
    }
  })
  .then(collectives => {
    result = result.concat('\n\t>>> Collectives found:', collectives.length);
    return collectives;
  })
  .each(collective => {
    return collective.getBalance()
      .then(balance => {
        if (balance < 0) {
          brokenCollectives.push(collective)
        }
        return Promise.resolve();
      })
  })
  .then(() => {
    result = result.concat('\n\t>>> Collectives with negative balance: ', brokenCollectives.length, '\n');
    if (VERBOSE)
      result = result.concat(JSON.stringify(brokenCollectives.map(c => Object.assign({id: c.id, slug: c.slug}))))
  })

}

const run = () => {
  console.log('\nStarting check_ledger_health script...')
  start = new Date();

  return checkHostsUserOrOrg()
  .then(() => checkHostCollectives())
  .then(() => checkHostStripeAccount())
  .then(() => checkUsersAndOrgs())
  .then(() => checkMembers())
  .then(() => checkOrders())
  .then(() => checkExpenses())
  .then(() => checkTransactions())
  .then(() => checkCollectiveBalance())
  .then(() => done())
  .catch(done)
}

run();<commit_msg>tweak cron job (#1027)<commit_after>/*
 * This script runs through a few checks and lets us know if something is off
 */

import models, { sequelize } from '../../server/models';
import emailLib from '../../server/lib/email';

const VERBOSE = true;
let result = '';
let start;

const done = (err) => {
  if (err) result = result.concat('err', err);
  result = result.concat("\n\nTotal time taken: ", new Date() - start, "ms")
  console.log(result);
  console.log('\ndone!\n');``
  const subject = `Daily ledger health report - ${(new Date()).toLocaleDateString()}`;
  return emailLib.sendMessage(
    'ops@opencollective.com',
    subject,
    '', {
      bcc: ' ',
      text: result
    })
    .then(process.exit)
    .catch(console.error)
}

const judgment = (value, goodFunc) => {
  if ((goodFunc && goodFunc(value)) || (!goodFunc && value === 0)) {
    return '✅'
  } else {
    return '❌'
  }
}

const header = (str) => {
  result = result.concat(`\n>>> ${str}\n`);
}

const subHeader = (str, value, goodFunc) => {
  result = result.concat(`\t${judgment(value, goodFunc)}  ${str}: ${value}\n`);
}

const verboseData = (values, mapFunction) => {
  const mapFunc = mapFunction || (o => o);
  if (VERBOSE && values.length > 0) {
    const slice = 5;
    const output = values.slice(0, slice).map(mapFunc);
    output.forEach(v => result = result.concat(`\t\t▫ ${JSON.stringify(v)}\n`))
    if (values.length > 10) {
      result = result.concat(`\t\t... and ${values.length - slice} more`);
    }
  }
}

const checkHostsUserOrOrg = () => {

  header('Checking Hosts must be USER or ORG');

  const hostErrors = [];

  // Check that a Host is a User or an ORG
  return sequelize.query(`
    WITH hosts as (SELECT distinct("HostCollectiveId") from "Collectives")

    SELECT id, type, slug from "Collectives"
    WHERE id IN (SELECT * FROM hosts);
    `, { type: sequelize.QueryTypes.SELECT})
    .then(hostCollectives => {
      subHeader('Hosts found', hostCollectives.length, h => h > 0);
      return hostCollectives
    })
    .each(hostCollective => {
      if (hostCollective.type !== 'USER' && hostCollective.type !== 'ORGANIZATION') {
        hostErrors.push(hostCollective);
      }
    })
    .then(() => {
      subHeader('Hosts found with incorrect type', hostErrors.length);
      verboseData(hostErrors, h => Object.assign({slug: h.slug, type: h.type}));
    });
}

// Ensure all Collectives are setup properly
const checkHostCollectives = () => {

  header('Checking Host Collectives')

  // Check that a collective is not setup to host itself or be it's own parentCollectiveId
  return models.Collective.findAll({
    where: {
      HostCollectiveId: {
        $col: 'id'
      }
    }
  })
  .then(selfReferencingHosts => {
    subHeader('Self-referencing Hosts found', selfReferencingHosts.length);
    verboseData(selfReferencingHosts, h => Object.assign({slug: h.slug, id: h.id}));
  })
}

const checkHostStripeAccount = () => {
  return sequelize.query(`
  WITH hosts AS
    (SELECT DISTINCT("HostCollectiveId") AS id FROM "Collectives" c
      WHERE "HostCollectiveId" IS NOT NULL)

  SELECT h.id FROM hosts h
  LEFT JOIN "ConnectedAccounts" ca ON (h.id = ca."CollectiveId")
  WHERE ca."CollectiveId" IS NULL
    `, { type: sequelize.QueryTypes.SELECT})
  .then(hostsWithoutStripe => {
    subHeader('Hosts without Stripe', hostsWithoutStripe.length);
    verboseData(hostsWithoutStripe, h => h.id)
  })
}

const checkUsersAndOrgs = () => {

  header('Checking USER and ORG Collectives');

  // Check that no User or ORG has a HostCollectiveId or ParentCollectiveId
  return models.Collective.findAll({
    where: {
      type: {
        $or: ['USER', 'ORGANIZATION']
      },
      HostCollectiveId: {
        $ne: null
      }
    }
  })
  .then(collectives => {
    subHeader('USER or ORGs found with HostCollectiveId', collectives.length);
    verboseData(collectives, c => Object.assign({slug: c.slug, HostCollectiveId: c.HostCollectiveId}));
  })
  // TODO: Check that no non-USER Collective is directly linked to a USER
  .then(() => models.User.findAll({
    attributes: ['CollectiveId']
  }))
  .then(userCollectives => models.Collective.findAll({
    where: {
      id: {
        $in: userCollectives.map(u => u.CollectiveId)
      },
      type: {
        $ne: 'USER'
      }
    }
  }))
  .then(improperlyLinkedCollectives => {
    subHeader('Non-User collectives that are linked to a USER', improperlyLinkedCollectives.length);
    verboseData(improperlyLinkedCollectives, c => Object.assign({id: c.id, slug: c.slug }));
  })
}

const checkMembers = () => {
  header('Checking Members table');

  return models.Member.findAll({
    where: {
      MemberCollectiveId: {
        $col: 'CollectiveId'
      }
    }
  })
  .then(circularMembers => {
    subHeader('Members with CollectiveId = MemberCollectiveId', circularMembers.length);
    verboseData(circularMembers, cm => cm.id);
  })
}

// Check orders
const checkOrders = () => {

  header('Check orders');

  // Check that FromCollectiveId on an Order matches all Transactions
  const brokenOrders = [];
  let orders, transactions;
  return sequelize.query(`
    SELECT id from "Orders"
    WHERE "deletedAt" is null AND "processedAt" is not null AND "CollectiveId" != 1
    `, { type: sequelize.QueryTypes.SELECT
    })
    .then(o => {
      orders = o;
      subHeader('orders found', orders.length, o => o > 0);
    })
    .then(() => sequelize.query(`
      SELECT distinct("FromCollectiveId"), "OrderId" from "Transactions"
      WHERE type LIKE 'CREDIT' AND "deletedAt" is null
      `, {
        type: sequelize.QueryTypes.SELECT
      }))
    .then(txns => {
      transactions = txns;
    })
    .then(() => orders)
    .each(order => {
      const fromCollectiveIds = transactions.filter(txn => txn.OrderId === order.id)
      if (fromCollectiveIds.length > 1) {
        brokenOrders.push(order)
      }
      return Promise.resolve();
    })
    .then(() => {
      subHeader('orders found with mismatched FromCollectiveId', brokenOrders.length);
      verboseData(brokenOrders, o => o.id);
    })
}

// Check expenses
const checkExpenses = () => {

  header('Check expenses');

  // Check that there are no expenses marked as "PAID" and without transaction entries
  return sequelize.query(`
    SELECT
      e.id AS id
    FROM "Expenses" e
    LEFT JOIN "Transactions" t ON t."ExpenseId" = e.id
    WHERE e.status ILIKE 'paid' AND t.id IS NULL AND  e."deletedAt" IS NULL
    ORDER BY "ExpenseId" DESC, e."updatedAt"
    `, { type: sequelize.QueryTypes.SELECT
    })
    .then(expenses => {
      subHeader('Paid expenses found without transactions', expenses.length);
      verboseData(expenses, e => Object.assign({id: e.id}));
    })
}

// Check all transactions
const checkTransactions = () => {

  header('Checking Transactions...')

  // Check every transaction has a "FromCollectiveId"
  return models.Transaction.count({
    where: {
      FromCollectiveId: {
        $eq: null
      }
    }
  })
  .then(txsWithoutFromCollectiveId => {
    subHeader('Transactions without `FromCollectiveId`', txsWithoutFromCollectiveId);
  })

  // Check no transaction has same "FromCollectiveId" and "CollectiveId"
  .then(() => models.Transaction.findAll({
    where: {
      CollectiveId: {
        $col: 'FromCollectiveId'
      }
    }
  }))
  .then(circularTxs => {
    subHeader('Transactions with same source and destination', circularTxs.length)
    verboseData(circularTxs, t => Object.assign({id: t.id}));
  })

  // check no transactions without TransactionGroup
  .then(() => models.Transaction.count({
    where: {
      TransactionGroup: {
        $eq: null
      }
    }
  }))
  .then(txnsWithoutTransactionGroup => {
    subHeader('Transactions without `TransactionGroup`', txnsWithoutTransactionGroup)
  })

  // Check every Order has even number of entries
  .then(() => sequelize.query(`
    SELECT "OrderId" FROM "Transactions"
        WHERE "OrderId" IS NOT NULL and "deletedAt" is null
          GROUP BY "OrderId"
          HAVING COUNT(*) % 2 != 0
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddOrderIds => {
    subHeader('Orders with odd (not multiple of 2) number of transactions', oddOrderIds.length)
  })

  // Check every Expense has a double Entry
  .then(() => sequelize.query(`
    SELECT "ExpenseId" FROM "Transactions"
        WHERE "ExpenseId" IS NOT NULL and "deletedAt" is null
          GROUP BY "ExpenseId"
          HAVING COUNT(*) != 2
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddExpenseIds => {
    subHeader('Expenses with less than or more than 2 transactions', oddExpenseIds.length)
    verboseData(oddExpenseIds)
  })

  // Check all TransactionGroups have two entries, one CREDIT and one DEBIT
  .then(() => sequelize.query(`
    SELECT "TransactionGroup" FROM "Transactions"
        WHERE "TransactionGroup" IS NOT NULL and "deletedAt" is null
          GROUP BY "TransactionGroup"
          HAVING COUNT(*) != 2
    `, {type: sequelize.QueryTypes.SELECT}))
  .then(oddTxnGroups => {
    subHeader('Transaction groups that are not pairs', oddTxnGroups.length)
    verboseData(oddTxnGroups)
  })

  // Check no transactions without either an Expense or Order
  .then(() => models.Transaction.findAll({
    where: {
      OrderId: {
        $eq: null
      },
      ExpenseId: {
        $eq: null
      }
    }
  }))
  .then(txnsWithoutOrderOrExpenses => {
    subHeader('Transactions without OrderId or ExpenseId', txnsWithoutOrderOrExpenses.length);
    // TODO: reenable when this count is lower than 600
    // if (VERBOSE)
    //  txnsWithoutOrderOrExpenses.map(t => Object.assign({id: t.id}));
  })

  // Check that various fees and amounts add up
  // TODO
}

const checkCollectiveBalance = () => {

  const brokenCollectives = [];
  header('Checking balance of each (non-USER, non-ORG) collective');
  return models.Collective.findAll({
    where: {
      $or: [{type: 'COLLECTIVE'}, {type: 'EVENT'}]
    }
  })
  .then(collectives => {
    subHeader('Collectives found', collectives.length, l => l > 0);
    return collectives;
  })
  .each(collective => {
    return collective.getBalance()
      .then(balance => {
        if (balance < 0) {
          brokenCollectives.push(collective)
        }
        return Promise.resolve();
      })
  })
  .then(() => {
    subHeader('Collectives with negative balance: ', brokenCollectives.length);
    verboseData(brokenCollectives, c => Object.assign({id: c.id, slug: c.slug}))
  })

}

const run = () => {
  console.log('\nStarting check_ledger_health script...')
  start = new Date();

  return checkHostsUserOrOrg()
  .then(() => checkHostCollectives())
  .then(() => checkHostStripeAccount())
  .then(() => checkUsersAndOrgs())
  .then(() => checkMembers())
  .then(() => checkOrders())
  .then(() => checkExpenses())
  .then(() => checkTransactions())
  .then(() => checkCollectiveBalance())
  .then(() => done())
  .catch(done)
}

run();<|endoftext|>


<commit_before>!function t(e,n,r){function o(i,l){if(!n[i]){if(!e[i]){var s="function"==typeof require&&require;if(!l&&s)return s(i,!0);if(a)return a(i,!0);throw new Error("Cannot find module '"+i+"'")}var u=n[i]={exports:{}};e[i][0].call(u.exports,function(t){var n=e[i][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[i].exports}for(var a="function"==typeof require&&require,i=0;i<r.length;i++)o(r[i]);return o}({1:[function(t,e,n){"undefined"==typeof tnt&&(e.exports=tnt={}),tnt.tooltip=t("./index.js")},{"./index.js":2}],2:[function(t,e,n){e.exports=tooltip=t("./src/tooltip.js")},{"./src/tooltip.js":5}],3:[function(t,e,n){e.exports=t("./src/api.js")},{"./src/api.js":4}],4:[function(t,e,n){var r=function(t){var e=function(){var t=[];return t.add_batch=function(e){t.unshift(e)},t.update=function(e,n){for(var r=0;r<t.length;r++)for(var o in t[r])if(o===e)return t[r][o]=n,!0;return!1},t.add=function(e,n){if(t.update(e,n));else{var r={};r[e]=n,t.add_batch(r)}},t.get=function(e){for(var n=0;n<t.length;n++)for(var r in t[n])if(r===e)return t[n][r]},t},n=e(),r=function(){};r.check=function(e,n,o){if(!(e instanceof Array))return"function"==typeof e?e.check(n,o):t[e].check(n,o),r;for(var a=0;a<e.length;a++)r.check(e[a],n,o)},r.transform=function(e,n){if(!(e instanceof Array))return"function"==typeof e?e.transform(n):t[e].transform(n),r;for(var o=0;o<e.length;o++)r.transform(e[o],n)};var o=function(e,r){var o=[],a=[],i=r.on_getter||function(){return n.get(e)},l=r.on_setter||function(t){for(var r=0;r<a.length;r++)t=a[r](t);for(var i=0;i<o.length;i++)if(!o[i].check(t)){var l=o[i].msg||"Value "+t+" doesn't seem to be valid for this method";throw l}n.add(e,t)},s=function(e){return arguments.length?(l(e),t):i()};s.check=function(t,e){return arguments.length?(o.push({check:t,msg:e}),this):o},s.transform=function(t){return arguments.length?(a.push(t),this):a},t[e]=s},a=function(t,e){if("object"==typeof t){n.add_batch(t);for(var r in t)o(r,e)}else n.add(t,e.default_value),o(t,e)};return r.getset=function(t,e){return a(t,{default_value:e}),r},r.get=function(t,e){var n=function(){throw"Method defined only as a getter (you are trying to use it as a setter"};return a(t,{default_value:e,on_setter:n}),r},r.set=function(t,e){var n=function(){throw"Method defined only as a setter (you are trying to use it as a getter"};return a(t,{default_value:e,on_getter:n}),r},r.method=function(e,n){if("object"==typeof e)for(var o in e)t[o]=e[o];else t[e]=n;return r},r};e.exports=n=r},{}],5:[function(t,e,n){var r=t("tnt.api"),o=function(){"use strict";function t(e,n){if(n=n.toLowerCase(),null===e.parentNode)return void console.log("No more parents");var r=e.parentNode.tagName;return void 0!==r&&r.toLowerCase()===n?e.parentNode:t(e.parentNode,n)}var e,n=d3.behavior.drag(),o={position:"right",allow_drag:!0,show_closer:!0,fill:function(){throw"fill is not defined in the base object"},width:180,id:1},a=function(r,i){n.origin(function(){return{x:parseInt(d3.select(this).style("left")),y:parseInt(d3.select(this).style("top"))}}).on("drag",function(){o.allow_drag&&d3.select(this).style("left",d3.event.x+"px").style("top",d3.event.y+"px")});var l=t(this,"div");if(void 0!==l){e=d3.select(l).append("div").attr("class","tnt_tooltip").classed("tnt_tooltip_active",!0).call(n),d3.select("#tnt_tooltip_"+o.id).remove(),null===d3.event&&i&&(d3.event=i);var s=d3.mouse(l);d3.event=null;var u=0;return"left"===o.position&&(u=o.width),e.attr("id","tnt_tooltip_"+o.id),e.style("left",s[0]-u+"px").style("top",s[1]+"px"),o.show_closer&&e.append("div").attr("class","tnt_tooltip_closer").on("click",function(){a.close()}),o.fill.call(e.node(),r),a}},i=r(a).getset(o);return i.check("position",function(t){return"left"===t||"right"===t},"Only 'left' or 'right' values are allowed for position"),i.method("close",function(){e&&e.remove()}),a};o.list=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").text(e.header);var o=r.selectAll(".tnt_zmenu_row").data(e.rows).enter().append("tr").attr("class","tnt_zmenu_row");o.append("td").style("text-align","center").html(function(t,n){return e.rows[n].value}).each(function(e){void 0!==e.link&&d3.select(this).classed("link",1).on("click",function(e){e.link(e.obj),t.close.call(this)})})}),t},o.table=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").attr("colspan",2).text(e.header);var o=r.selectAll(".tnt_zmenu_row").data(e.rows).enter().append("tr").attr("class","tnt_zmenu_row");o.append("th").attr("colspan",function(t,e){return""===t.value?2:1}).attr("class",function(t){return""===t.value?"tnt_zmenu_inner_header":"tnt_zmenu_cell"}).html(function(t,n){return e.rows[n].label}),o.append("td").html(function(t,n){return"function"!=typeof e.rows[n].value?e.rows[n].value:void e.rows[n].value.call(this,t)}).each(function(t){""===t.value&&d3.select(this).remove()}).each(function(e){void 0!==e.link&&d3.select(this).classed("link",1).on("click",function(e){e.link(e.obj),t.close.call(this)})})}),t},o.plain=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").text(e.header),e.body&&r.append("tr").attr("class","tnt_zmenu_row").append("td").style("text-align","center").html(e.body)}),t},e.exports=n=o},{"tnt.api":3}]},{},[1]);<commit_msg>updated built<commit_after>!function t(e,n,r){function o(i,l){if(!n[i]){if(!e[i]){var s="function"==typeof require&&require;if(!l&&s)return s(i,!0);if(a)return a(i,!0);throw new Error("Cannot find module '"+i+"'")}var c=n[i]={exports:{}};e[i][0].call(c.exports,function(t){var n=e[i][1][t];return o(n?n:t)},c,c.exports,t,e,n,r)}return n[i].exports}for(var a="function"==typeof require&&require,i=0;i<r.length;i++)o(r[i]);return o}({1:[function(t,e,n){"undefined"==typeof tnt&&(e.exports=tnt={}),tnt.tooltip=t("./index.js")},{"./index.js":2}],2:[function(t,e,n){e.exports=tooltip=t("./src/tooltip.js")},{"./src/tooltip.js":5}],3:[function(t,e,n){e.exports=t("./src/api.js")},{"./src/api.js":4}],4:[function(t,e,n){var r=function(t){var e=function(){var t=[];return t.add_batch=function(e){t.unshift(e)},t.update=function(e,n){for(var r=0;r<t.length;r++)for(var o in t[r])if(o===e)return t[r][o]=n,!0;return!1},t.add=function(e,n){if(t.update(e,n));else{var r={};r[e]=n,t.add_batch(r)}},t.get=function(e){for(var n=0;n<t.length;n++)for(var r in t[n])if(r===e)return t[n][r]},t},n=e(),r=function(){};r.check=function(e,n,o){if(!(e instanceof Array))return"function"==typeof e?e.check(n,o):t[e].check(n,o),r;for(var a=0;a<e.length;a++)r.check(e[a],n,o)},r.transform=function(e,n){if(!(e instanceof Array))return"function"==typeof e?e.transform(n):t[e].transform(n),r;for(var o=0;o<e.length;o++)r.transform(e[o],n)};var o=function(e,r){var o=[],a=[],i=r.on_getter||function(){return n.get(e)},l=r.on_setter||function(t){for(var r=0;r<a.length;r++)t=a[r](t);for(var i=0;i<o.length;i++)if(!o[i].check(t)){var l=o[i].msg||"Value "+t+" doesn't seem to be valid for this method";throw l}n.add(e,t)},s=function(e){return arguments.length?(l(e),t):i()};s.check=function(t,e){return arguments.length?(o.push({check:t,msg:e}),this):o},s.transform=function(t){return arguments.length?(a.push(t),this):a},t[e]=s},a=function(t,e){if("object"==typeof t){n.add_batch(t);for(var r in t)o(r,e)}else n.add(t,e.default_value),o(t,e)};return r.getset=function(t,e){return a(t,{default_value:e}),r},r.get=function(t,e){var n=function(){throw"Method defined only as a getter (you are trying to use it as a setter"};return a(t,{default_value:e,on_setter:n}),r},r.set=function(t,e){var n=function(){throw"Method defined only as a setter (you are trying to use it as a getter"};return a(t,{default_value:e,on_getter:n}),r},r.method=function(e,n){if("object"==typeof e)for(var o in e)t[o]=e[o];else t[e]=n;return r},r};e.exports=n=r},{}],5:[function(t,e,n){var r=t("tnt.api"),o=function(){"use strict";function t(e,n){if(n=n.toLowerCase(),null===e.parentNode)return void console.log("No more parents");var r=e.parentNode.tagName;return void 0!==r&&r.toLowerCase()===n?e.parentNode:t(e.parentNode,n)}var e,n=d3.behavior.drag(),o={container:void 0,position:"right",allow_drag:!0,show_closer:!0,fill:function(){throw"fill is not defined in the base object"},width:180,id:1},a=function(r,i){n.origin(function(){return{x:parseInt(d3.select(this).style("left")),y:parseInt(d3.select(this).style("top"))}}).on("drag",function(){o.allow_drag&&d3.select(this).style("left",d3.event.x+"px").style("top",d3.event.y+"px")});var l=o.container;if(l||(l=t(this,"div"),void 0!==l)){e=d3.select(l).append("div").attr("class","tnt_tooltip").classed("tnt_tooltip_active",!0).call(n),d3.select("#tnt_tooltip_"+o.id).remove(),null===d3.event&&i&&(d3.event=i);var s=d3.mouse(l);d3.event=null;var c=0;return"left"===o.position&&(c=o.width),e.attr("id","tnt_tooltip_"+o.id),e.style("left",s[0]-c+"px").style("top",s[1]+"px"),o.show_closer&&e.append("div").attr("class","tnt_tooltip_closer").on("click",function(){a.close()}),o.fill.call(e.node(),r),a}},i=r(a).getset(o);return i.check("position",function(t){return"left"===t||"right"===t},"Only 'left' or 'right' values are allowed for position"),i.method("close",function(){e&&e.remove()}),a};o.list=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").text(e.header);var o=r.selectAll(".tnt_zmenu_row").data(e.rows).enter().append("tr").attr("class","tnt_zmenu_row");o.append("td").style("text-align","center").html(function(t,n){return e.rows[n].value}).each(function(e){void 0!==e.link&&d3.select(this).classed("link",1).on("click",function(e){e.link(e.obj),t.close.call(this)})})}),t},o.table=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").attr("colspan",2).text(e.header);var o=r.selectAll(".tnt_zmenu_row").data(e.rows).enter().append("tr").attr("class","tnt_zmenu_row");o.append("th").attr("colspan",function(t,e){return""===t.value?2:1}).attr("class",function(t){return""===t.value?"tnt_zmenu_inner_header":"tnt_zmenu_cell"}).html(function(t,n){return e.rows[n].label}),o.append("td").html(function(t,n){return"function"!=typeof e.rows[n].value?e.rows[n].value:void e.rows[n].value.call(this,t)}).each(function(t){""===t.value&&d3.select(this).remove()}).each(function(e){void 0!==e.link&&d3.select(this).classed("link",1).on("click",function(e){e.link(e.obj),t.close.call(this)})})}),t},o.plain=function(){var t=o();return t.fill(function(e){var n=d3.select(this),r=n.append("table").attr("class","tnt_zmenu").attr("border","solid").style("width",t.width()+"px");e.header&&r.append("tr").attr("class","tnt_zmenu_header").append("th").text(e.header),e.body&&r.append("tr").attr("class","tnt_zmenu_row").append("td").style("text-align","center").html(e.body)}),t},e.exports=n=o},{"tnt.api":3}]},{},[1]);<|endoftext|>


<commit_before>// -*- coding: utf-8 -*-

/**
 * ------------------------------------------
 *            generic utilities
 * ------------------------------------------
 */

function to_list(value) {
    return _.isArray(value) && value || [value];
}

// FIXME: why does underscore.string's "include" not work?
function contains(string, pattern) {
    if (!string) return false;
    return string.indexOf(pattern) > -1;
}

/**
 * ------------------------------------------
 *            application objects
 * ------------------------------------------
 */
OpsChooserApp = Backbone.Marionette.Application.extend({
    // send to server and process response
    perform_search: function(options) {
        var query = $('#query').val();
        if (!_.isEmpty(query)) {
            if (options && options.range) {
                opsChooserApp.search.perform(this.documents, this.metadata, query, options.range);
            } else {
                opsChooserApp.search.perform(this.documents, this.metadata, query);
            }
        }
    }
});
opsChooserApp = new OpsChooserApp();

opsChooserApp.addRegions({
    listRegion: "#ops-collection-region",
    paginationRegion: "#ops-pagination-region",
});

/**
 * ------------------------------------------
 *               model objects
 * ------------------------------------------
 */
OpsPublishedDataSearch = Backbone.Model.extend({
    url: '/api/ops/published-data/search',
    perform: function(documents, metadata, query, range) {

        $('#spinner').show();
        var self = this;

        this.fetch({
            data: $.param({ query: query, range: range}),
            success: function (payload) {

                $('#spinner').hide();

                //console.log("payload raw:");
                //console.log(payload);
                console.log("payload data:");
                console.log(payload['attributes']);

                // get "node" containing record list from nested json response
                var search_result = payload['attributes']['ops:world-patent-data']['ops:biblio-search']['ops:search-result'];

                // unwrap response by creating a list of model objects from records
                var entries;
                if (search_result) {
                    var exchange_documents = to_list(search_result['exchange-documents']);
                    entries = _.map(exchange_documents, function(entry) { return new OpsExchangeDocument(entry['exchange-document']); });
                }

                // propagate data to model collection instance
                documents.reset(entries);

                // propagate metadata to model
                var total_result_count = payload['attributes']['ops:world-patent-data']['ops:biblio-search']['@total-result-count'];
                metadata.set({result_count: total_result_count});

                // run action bindings here after rendering data entries
                listview_bind_actions();

            },
        });

    },
});

OpsExchangeMetadata = Backbone.Model.extend({

    defaults: {
        result_count: null,
    }

});

OpsExchangeDocument = Backbone.Model.extend({

    defaults: {
        selected: false,

        // TODO: move these methods to "viewHelpers"
        // http://lostechies.com/derickbailey/2012/04/26/view-helpers-for-underscore-templates/
        // https://github.com/marionettejs/backbone.marionette/wiki/View-helpers-for-underscore-templates#using-this-with-backbonemarionette

        get_patent_number: function() {
            return this['@country'] + this['@doc-number'] + this['@kind'];
        },

        get_applicants: function() {
            var applicants_root_node = this['bibliographic-data']['parties']['applicants'];
            applicants_root_node = applicants_root_node || [];
            var applicants_node = applicants_root_node['applicant'];
            return this.parties_to_list(applicants_node, 'applicant-name');
        },

        get_inventors: function() {
            var inventors_root_node = this['bibliographic-data']['parties']['inventors'];
            inventors_root_node = inventors_root_node || [];
            var inventors_node = inventors_root_node['inventor'];
            return this.parties_to_list(inventors_node, 'inventor-name');
        },

        parties_to_list: function(container, value_attribute_name) {

            // deserialize list of parties (applicants/inventors) from exchange payload
            var sequence_max = "0";
            var groups = {};
            _.each(container, function(item) {
                var data_format = item['@data-format'];
                var sequence = item['@sequence'];
                var value = _.string.trim(item[value_attribute_name]['name']['$'], ', ');
                groups[data_format] = groups[data_format] || {};
                groups[data_format][sequence] = value;
                if (sequence > sequence_max)
                    sequence_max = sequence;
            });
            //console.log(groups);

            // TODO: somehow display in gui which one is the "epodoc" and which one is the "original" value
            var entries = [];
            _.each(_.range(1, parseInt(sequence_max) + 1), function(sequence) {
                sequence = sequence.toString();
                var epodoc_value = groups['epodoc'][sequence];
                var original_value = groups['original'][sequence];
                //entries.push(epodoc_value + ' / ' + original_value);
                entries.push(original_value);
            });

            return entries;

        },

        get_drawing_url: function() {
            // http://ops.epo.org/3.1/rest-services/published-data/images/EP/1000000/PA/firstpage.png?Range=1
            // http://ops.epo.org/3.1/rest-services/published-data/images/US/20130311929/A1/thumbnail.tiff?Range=1
            var url_tpl = _.template('/api/ops/<%= country %><%= docnumber %>.<%= kind %>/image/drawing');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        },

        get_fullimage_url: function() {
            // http://ops.epo.org/3.1/rest-services/published-data/images/EP/1000000/A1/fullimage.pdf?Range=1
            var url_tpl = _.template('/api/ops/<%= country %><%= docnumber %>.<%= kind %>/image/full');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        },

        get_espacenet_pdf_url: function() {
            // http://worldwide.espacenet.com/espacenetDocument.pdf?flavour=trueFull&FT=D&CC=US&NR=6269530B1&KC=B1
            var url_tpl = _.template('http://worldwide.espacenet.com/espacenetDocument.pdf?flavour=trueFull&FT=D&CC=<%= country %>&NR=<%= docnumber %><%= kind %>&KC=<%= kind %>');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        }

    },

    select: function() {
        this.set('selected', true);
    },
    unselect: function() {
        this.set('selected', false);
    },

});

OpsExchangeDocumentCollection = Backbone.Collection.extend({

    model: OpsExchangeDocument,

    initialize: function(collection) {
        var self = this;
    },

});

/**
 * ------------------------------------------
 *                view objects
 * ------------------------------------------
 */
OpsExchangeDocumentView = Backbone.Marionette.ItemView.extend({
    //template: "#ops-entry-template",
    template: _.template($('#ops-entry-template').html(), this.model, {variable: 'data'}),
    tagName: 'div',
    className: 'row-fluid',

    events: {
        'click .rank_up img': 'rankUp',
        'click .rank_down img': 'rankDown',
        'click a.disqualify': 'disqualify'
    },

    // actions to run after populating the view
    // e.g. to bind click handlers on individual records
    onDomRefresh: function() {

        var patent_number = this.model.attributes.get_patent_number();
        basket_update_ui_entry(patent_number);

    },

});

OpsExchangeDocumentCollectionView = Backbone.Marionette.CompositeView.extend({
    tagName: "div",
    id: "opsexchangedocumentcollection",
    className: "container",
    template: "#ops-collection-template",
    itemView: OpsExchangeDocumentView,

    appendHtml: function(collectionView, itemView) {
        $(collectionView.el).append(itemView.el);
    },

});

PaginationView = Backbone.Marionette.ItemView.extend({
    tagName: "div",
    id: "paginationview",
    template: "#ops-pagination-template",

    initialize: function() {
        console.log('PaginationView.initialize');
        this.listenTo(this.model, "change", this.render);
    },

    onDomRefresh: function() {
        console.log('PaginationView.onDomRefresh');
        $('div.pagination a').click(function() {
            var action = $(this).attr('action');
            var range = $(this).attr('range');
            opsChooserApp.perform_search({range: range});
            return false;
        });
    },

});

/*
------------------------------------------
    utility functions
------------------------------------------
 */

function basket_add(entry) {
    var payload = $('#basket').val();
    if (!contains(payload, entry))
        payload += entry + '\n';
    $('#basket').val(payload);
    basket_update_ui_entry(entry);
}

function basket_remove(entry) {
    var payload = $('#basket').val();
    if (contains(payload, entry))
        payload = payload.replace(entry + '\n', '');
    $('#basket').val(payload);
    basket_update_ui_entry(entry);
}

function basket_update_ui_entry(entry) {
    // backpropagate current basket entries into checkbox state
    //console.log(this.model);
    var payload = $('#basket').val();
    var checkbox_element = $('#' + 'chk-patent-number-' + entry);
    var add_button_element = $('#' + 'add-patent-number-' + entry);
    var remove_button_element = $('#' + 'remove-patent-number-' + entry);
    if (contains(payload, entry)) {
        checkbox_element && checkbox_element.prop('checked', true);
        add_button_element && add_button_element.hide();
        remove_button_element && remove_button_element.show();
    } else {
        checkbox_element && checkbox_element.prop('checked', false);
        add_button_element && add_button_element.show();
        remove_button_element && remove_button_element.hide();
    }
}

function pdf_display(element_id, url, page) {
    // embed pdf object
    var pdf_object = new PDFObject({
        url: url + '?page=' + page,
        id: 'myPDF',
        pdfOpenParams: {
            navpanes: 0,
            toolbar: 0,
            statusbar: 0,
            view: 'FitB',
            //zoom: '100',
        },
    });
    pdf_object.embed(element_id);
}

function pdf_set_headline(document_number, page) {
    var headline = document_number + ' Page ' + page;
    $(".modal-header #ops-pdf-modal-label").empty().append(headline);
}

function listview_bind_actions() {

    // handle checkbox clicks by add-/remove-operations on basket
    $(".chk-patent-number").click(function() {
        var patent_number = this.value;
        if (this.checked)
            basket_add(patent_number);
        if (!this.checked)
            basket_remove(patent_number);
    });

    // handle button clicks by add-/remove-operations on basket
    $(".add-patent-number").click(function() {
        var patent_number = $(this).data('patent-number');
        basket_add(patent_number);
    });
    $(".remove-patent-number").click(function() {
        var patent_number = $(this).data('patent-number');
        basket_remove(patent_number);
    });

    // use jquery.shorten on "abstract" text
    $(".abstract").shorten({showChars: 2000, moreText: 'mehr', lessText: 'weniger'});

    // popovers
    $('.add-patent-number').popover();
    $('.remove-patent-number').popover();
    $('.pdf-open').popover();
    $('.inid-tooltip').tooltip();

    // pdf action button
    $(".pdf-open").click(function() {

        var patent_number = $(this).data('patent-number');
        var pdf_url = $(this).data('pdf-url');
        var pdf_page = 1;

        pdf_set_headline(patent_number, pdf_page);
        pdf_display('pdf', pdf_url, pdf_page);

        // pdf paging actions
        $("#pdf-previous").unbind();
        $("#pdf-previous").click(function() {
            if (pdf_page == 1) return;
            pdf_page -= 1;
            pdf_set_headline(patent_number, pdf_page);
            pdf_display('pdf', pdf_url, pdf_page);
        });
        $("#pdf-next").unbind();
        $("#pdf-next").click(function() {
            pdf_page += 1;
            pdf_set_headline(patent_number, pdf_page);
            pdf_display('pdf', pdf_url, pdf_page);
        });

    });

}

/**
 * ------------------------------------------
 *           bootstrap application
 * ------------------------------------------
 */

opsChooserApp.addInitializer(function(options) {
    // create application domain model objects
    this.search = new OpsPublishedDataSearch();
    this.metadata = new OpsExchangeMetadata();
    this.documents = new OpsExchangeDocumentCollection();
});

opsChooserApp.addInitializer(function(options) {

    // bind model objects to view objects
    var collectionView = new OpsExchangeDocumentCollectionView({
        collection: this.documents
    });
    var paginationView = new PaginationView({
        model: this.metadata
    });

    // bind view objects to region objects
    opsChooserApp.listRegion.show(collectionView);
    opsChooserApp.paginationRegion.show(paginationView);
});

opsChooserApp.addInitializer(function(options) {
    // automatically run search after bootstrapping application
    this.perform_search();
});

$(document).ready(function() {

    console.log("OpsChooserApp starting");

    // process and propagate application ingress parameters
    //var url = $.url(window.location.href);
    //var query = url.param('query');
    //query = 'applicant=IBM';
    //query = 'publicationnumber=US2013255753A1';

    opsChooserApp.start();

    $('#query-button').click(function() {
        opsChooserApp.perform_search();
    });

    $('#query-button').popover();
    $('#basket-button').popover();

});
<commit_msg>ui: more german => english translation<commit_after>// -*- coding: utf-8 -*-

/**
 * ------------------------------------------
 *            generic utilities
 * ------------------------------------------
 */

function to_list(value) {
    return _.isArray(value) && value || [value];
}

// FIXME: why does underscore.string's "include" not work?
function contains(string, pattern) {
    if (!string) return false;
    return string.indexOf(pattern) > -1;
}

/**
 * ------------------------------------------
 *            application objects
 * ------------------------------------------
 */
OpsChooserApp = Backbone.Marionette.Application.extend({
    // send to server and process response
    perform_search: function(options) {
        var query = $('#query').val();
        if (!_.isEmpty(query)) {
            if (options && options.range) {
                opsChooserApp.search.perform(this.documents, this.metadata, query, options.range);
            } else {
                opsChooserApp.search.perform(this.documents, this.metadata, query);
            }
        }
    }
});
opsChooserApp = new OpsChooserApp();

opsChooserApp.addRegions({
    listRegion: "#ops-collection-region",
    paginationRegion: "#ops-pagination-region",
});

/**
 * ------------------------------------------
 *               model objects
 * ------------------------------------------
 */
OpsPublishedDataSearch = Backbone.Model.extend({
    url: '/api/ops/published-data/search',
    perform: function(documents, metadata, query, range) {

        $('#spinner').show();
        var self = this;

        this.fetch({
            data: $.param({ query: query, range: range}),
            success: function (payload) {

                $('#spinner').hide();

                //console.log("payload raw:");
                //console.log(payload);
                console.log("payload data:");
                console.log(payload['attributes']);

                // get "node" containing record list from nested json response
                var search_result = payload['attributes']['ops:world-patent-data']['ops:biblio-search']['ops:search-result'];

                // unwrap response by creating a list of model objects from records
                var entries;
                if (search_result) {
                    var exchange_documents = to_list(search_result['exchange-documents']);
                    entries = _.map(exchange_documents, function(entry) { return new OpsExchangeDocument(entry['exchange-document']); });
                }

                // propagate data to model collection instance
                documents.reset(entries);

                // propagate metadata to model
                var total_result_count = payload['attributes']['ops:world-patent-data']['ops:biblio-search']['@total-result-count'];
                metadata.set({result_count: total_result_count});

                // run action bindings here after rendering data entries
                listview_bind_actions();

            },
        });

    },
});

OpsExchangeMetadata = Backbone.Model.extend({

    defaults: {
        result_count: null,
    }

});

OpsExchangeDocument = Backbone.Model.extend({

    defaults: {
        selected: false,

        // TODO: move these methods to "viewHelpers"
        // http://lostechies.com/derickbailey/2012/04/26/view-helpers-for-underscore-templates/
        // https://github.com/marionettejs/backbone.marionette/wiki/View-helpers-for-underscore-templates#using-this-with-backbonemarionette

        get_patent_number: function() {
            return this['@country'] + this['@doc-number'] + this['@kind'];
        },

        get_applicants: function() {
            var applicants_root_node = this['bibliographic-data']['parties']['applicants'];
            applicants_root_node = applicants_root_node || [];
            var applicants_node = applicants_root_node['applicant'];
            return this.parties_to_list(applicants_node, 'applicant-name');
        },

        get_inventors: function() {
            var inventors_root_node = this['bibliographic-data']['parties']['inventors'];
            inventors_root_node = inventors_root_node || [];
            var inventors_node = inventors_root_node['inventor'];
            return this.parties_to_list(inventors_node, 'inventor-name');
        },

        parties_to_list: function(container, value_attribute_name) {

            // deserialize list of parties (applicants/inventors) from exchange payload
            var sequence_max = "0";
            var groups = {};
            _.each(container, function(item) {
                var data_format = item['@data-format'];
                var sequence = item['@sequence'];
                var value = _.string.trim(item[value_attribute_name]['name']['$'], ', ');
                groups[data_format] = groups[data_format] || {};
                groups[data_format][sequence] = value;
                if (sequence > sequence_max)
                    sequence_max = sequence;
            });
            //console.log(groups);

            // TODO: somehow display in gui which one is the "epodoc" and which one is the "original" value
            var entries = [];
            _.each(_.range(1, parseInt(sequence_max) + 1), function(sequence) {
                sequence = sequence.toString();
                var epodoc_value = groups['epodoc'][sequence];
                var original_value = groups['original'][sequence];
                //entries.push(epodoc_value + ' / ' + original_value);
                entries.push(original_value);
            });

            return entries;

        },

        get_drawing_url: function() {
            // http://ops.epo.org/3.1/rest-services/published-data/images/EP/1000000/PA/firstpage.png?Range=1
            // http://ops.epo.org/3.1/rest-services/published-data/images/US/20130311929/A1/thumbnail.tiff?Range=1
            var url_tpl = _.template('/api/ops/<%= country %><%= docnumber %>.<%= kind %>/image/drawing');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        },

        get_fullimage_url: function() {
            // http://ops.epo.org/3.1/rest-services/published-data/images/EP/1000000/A1/fullimage.pdf?Range=1
            var url_tpl = _.template('/api/ops/<%= country %><%= docnumber %>.<%= kind %>/image/full');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        },

        get_espacenet_pdf_url: function() {
            // http://worldwide.espacenet.com/espacenetDocument.pdf?flavour=trueFull&FT=D&CC=US&NR=6269530B1&KC=B1
            var url_tpl = _.template('http://worldwide.espacenet.com/espacenetDocument.pdf?flavour=trueFull&FT=D&CC=<%= country %>&NR=<%= docnumber %><%= kind %>&KC=<%= kind %>');
            var url = url_tpl({country: this['@country'], docnumber: this['@doc-number'], kind: this['@kind']});
            return url;
        }

    },

    select: function() {
        this.set('selected', true);
    },
    unselect: function() {
        this.set('selected', false);
    },

});

OpsExchangeDocumentCollection = Backbone.Collection.extend({

    model: OpsExchangeDocument,

    initialize: function(collection) {
        var self = this;
    },

});

/**
 * ------------------------------------------
 *                view objects
 * ------------------------------------------
 */
OpsExchangeDocumentView = Backbone.Marionette.ItemView.extend({
    //template: "#ops-entry-template",
    template: _.template($('#ops-entry-template').html(), this.model, {variable: 'data'}),
    tagName: 'div',
    className: 'row-fluid',

    events: {
        'click .rank_up img': 'rankUp',
        'click .rank_down img': 'rankDown',
        'click a.disqualify': 'disqualify'
    },

    // actions to run after populating the view
    // e.g. to bind click handlers on individual records
    onDomRefresh: function() {

        var patent_number = this.model.attributes.get_patent_number();
        basket_update_ui_entry(patent_number);

    },

});

OpsExchangeDocumentCollectionView = Backbone.Marionette.CompositeView.extend({
    tagName: "div",
    id: "opsexchangedocumentcollection",
    className: "container",
    template: "#ops-collection-template",
    itemView: OpsExchangeDocumentView,

    appendHtml: function(collectionView, itemView) {
        $(collectionView.el).append(itemView.el);
    },

});

PaginationView = Backbone.Marionette.ItemView.extend({
    tagName: "div",
    id: "paginationview",
    template: "#ops-pagination-template",

    initialize: function() {
        console.log('PaginationView.initialize');
        this.listenTo(this.model, "change", this.render);
    },

    onDomRefresh: function() {
        console.log('PaginationView.onDomRefresh');
        $('div.pagination a').click(function() {
            var action = $(this).attr('action');
            var range = $(this).attr('range');
            opsChooserApp.perform_search({range: range});
            return false;
        });
    },

});

/*
------------------------------------------
    utility functions
------------------------------------------
 */

function basket_add(entry) {
    var payload = $('#basket').val();
    if (!contains(payload, entry))
        payload += entry + '\n';
    $('#basket').val(payload);
    basket_update_ui_entry(entry);
}

function basket_remove(entry) {
    var payload = $('#basket').val();
    if (contains(payload, entry))
        payload = payload.replace(entry + '\n', '');
    $('#basket').val(payload);
    basket_update_ui_entry(entry);
}

function basket_update_ui_entry(entry) {
    // backpropagate current basket entries into checkbox state
    //console.log(this.model);
    var payload = $('#basket').val();
    var checkbox_element = $('#' + 'chk-patent-number-' + entry);
    var add_button_element = $('#' + 'add-patent-number-' + entry);
    var remove_button_element = $('#' + 'remove-patent-number-' + entry);
    if (contains(payload, entry)) {
        checkbox_element && checkbox_element.prop('checked', true);
        add_button_element && add_button_element.hide();
        remove_button_element && remove_button_element.show();
    } else {
        checkbox_element && checkbox_element.prop('checked', false);
        add_button_element && add_button_element.show();
        remove_button_element && remove_button_element.hide();
    }
}

function pdf_display(element_id, url, page) {
    // embed pdf object
    var pdf_object = new PDFObject({
        url: url + '?page=' + page,
        id: 'myPDF',
        pdfOpenParams: {
            navpanes: 0,
            toolbar: 0,
            statusbar: 0,
            view: 'FitB',
            //zoom: '100',
        },
    });
    pdf_object.embed(element_id);
}

function pdf_set_headline(document_number, page) {
    var headline = document_number + ' Page ' + page;
    $(".modal-header #ops-pdf-modal-label").empty().append(headline);
}

function listview_bind_actions() {

    // handle checkbox clicks by add-/remove-operations on basket
    $(".chk-patent-number").click(function() {
        var patent_number = this.value;
        if (this.checked)
            basket_add(patent_number);
        if (!this.checked)
            basket_remove(patent_number);
    });

    // handle button clicks by add-/remove-operations on basket
    $(".add-patent-number").click(function() {
        var patent_number = $(this).data('patent-number');
        basket_add(patent_number);
    });
    $(".remove-patent-number").click(function() {
        var patent_number = $(this).data('patent-number');
        basket_remove(patent_number);
    });

    // use jquery.shorten on "abstract" text
    $(".abstract").shorten({showChars: 2000, moreText: 'more', lessText: 'less'});

    // popovers
    $('.add-patent-number').popover();
    $('.remove-patent-number').popover();
    $('.pdf-open').popover();
    $('.inid-tooltip').tooltip();

    // pdf action button
    $(".pdf-open").click(function() {

        var patent_number = $(this).data('patent-number');
        var pdf_url = $(this).data('pdf-url');
        var pdf_page = 1;

        pdf_set_headline(patent_number, pdf_page);
        pdf_display('pdf', pdf_url, pdf_page);

        // pdf paging actions
        $("#pdf-previous").unbind();
        $("#pdf-previous").click(function() {
            if (pdf_page == 1) return;
            pdf_page -= 1;
            pdf_set_headline(patent_number, pdf_page);
            pdf_display('pdf', pdf_url, pdf_page);
        });
        $("#pdf-next").unbind();
        $("#pdf-next").click(function() {
            pdf_page += 1;
            pdf_set_headline(patent_number, pdf_page);
            pdf_display('pdf', pdf_url, pdf_page);
        });

    });

}

/**
 * ------------------------------------------
 *           bootstrap application
 * ------------------------------------------
 */

opsChooserApp.addInitializer(function(options) {
    // create application domain model objects
    this.search = new OpsPublishedDataSearch();
    this.metadata = new OpsExchangeMetadata();
    this.documents = new OpsExchangeDocumentCollection();
});

opsChooserApp.addInitializer(function(options) {

    // bind model objects to view objects
    var collectionView = new OpsExchangeDocumentCollectionView({
        collection: this.documents
    });
    var paginationView = new PaginationView({
        model: this.metadata
    });

    // bind view objects to region objects
    opsChooserApp.listRegion.show(collectionView);
    opsChooserApp.paginationRegion.show(paginationView);
});

opsChooserApp.addInitializer(function(options) {
    // automatically run search after bootstrapping application
    this.perform_search();
});

$(document).ready(function() {

    console.log("OpsChooserApp starting");

    // process and propagate application ingress parameters
    //var url = $.url(window.location.href);
    //var query = url.param('query');
    //query = 'applicant=IBM';
    //query = 'publicationnumber=US2013255753A1';

    opsChooserApp.start();

    $('#query-button').click(function() {
        opsChooserApp.perform_search();
    });

    $('#query-button').popover();
    $('#basket-button').popover();

});
<|endoftext|>