<commit_before>'use strict';

const { test } = require('ava');
const UnleashClientMetrics = require('./index');
const moment = require('moment');
const sinon = require('sinon');

const { EventEmitter } = require('events');

const appName = 'appName';
const instanceId = 'instanceId';

test('should work without state', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    t.truthy(metrics.getAppsWithToggles());
    t.truthy(metrics.getTogglesMetrics());

    metrics.destroy();
});

test.cb('data should expire', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: Date.now() - 2000,
            stop: Date.now() - 1000,
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    let lastHourExpires = 0;
    metrics.lastHourList.on('expire', () => {
        lastHourExpires++;
    });

    let lastMinExpires = 0;
    metrics.lastMinuteList.on('expire', () => {
        lastMinExpires++;
    });

    clock.tick(60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 0);

    clock.tick(60 * 60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 1);

    clock.restore();
    t.end();
});

test('should listen to metrics from store', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    t.truthy(metrics.apps[appName].count === 123);
    t.truthy(metrics.globalCount === 123);

    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, {
        yes: 123,
        no: 0,
    });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 123,
        no: 0,
    });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 10,
                    no: 10,
                },
            },
        },
    });

    t.truthy(metrics.globalCount === 143);
    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, {
        yes: 133,
        no: 10,
    });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 133,
        no: 10,
    });

    metrics.destroy();
});

test('should build up list of seend toggles when new metrics arrives', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
                toggleY: {
                    yes: 50,
                    no: 50,
                },
            },
        },
    });

    const appToggles = metrics.getAppsWithToggles();
    const togglesForApp = metrics.getSeenTogglesByAppName(appName);

    t.truthy(appToggles.length === 1);
    t.truthy(appToggles[0].seenToggles.length === 2);
    t.truthy(appToggles[0].seenToggles.includes('toggleX'));
    t.truthy(appToggles[0].seenToggles.includes('toggleY'));

    t.truthy(togglesForApp.length === 2);
    t.truthy(togglesForApp.includes('toggleX'));
    t.truthy(togglesForApp.includes('toggleY'));
    metrics.destroy();
});

test('should handle a lot of toggles', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const toggleCounts = {};
    for (let i = 0; i < 100; i++) {
        toggleCounts[`toggle${i}`] = { yes: i, no: i };
    }

    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: toggleCounts,
        },
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 100);
    metrics.destroy();
});

test('should have correct values for lastMinute', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const now = new Date();
    const input = [
        {
            start: moment(now).subtract(1, 'hour'),
            stop: moment(now).subtract(59, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(30, 'minutes'),
            stop: moment(now).subtract(29, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(2, 'minutes'),
            stop: moment(now).subtract(1, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(2, 'minutes'),
            stop: moment(now).subtract(59, 'seconds'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now),
            stop: moment(now).subtract(30, 'seconds'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
    ];

    input.forEach(bucket => {
        store.emit('metrics', {
            appName,
            instanceId,
            bucket,
        });
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);
    t.truthy(seenToggles.length === 1);

    // metrics.se
    let c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 20, no: 20 });

    clock.tick(10 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 10, no: 10 });

    clock.tick(20 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 0, no: 0 });

    metrics.destroy();
    clock.restore();
});

test('should have correct values for lastHour', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const now = new Date();
    const input = [
        {
            start: moment(now).subtract(1, 'hour'),
            stop: moment(now).subtract(59, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(30, 'minutes'),
            stop: moment(now).subtract(29, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(15, 'minutes'),
            stop: moment(now).subtract(14, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).add(59, 'minutes'),
            stop: moment(now).add(1, 'hour'),
            toggles: {
                toggle: { yes: 11, no: 11 },
            },
        },
    ];

    input.forEach(bucket => {
        store.emit('metrics', {
            appName,
            instanceId,
            bucket,
        });
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 1);

    // metrics.se
    let c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 41, no: 41 });

    clock.tick(10 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 41, no: 41 });

    // at 30
    clock.tick(30 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 31, no: 31 });

    // at 45
    clock.tick(15 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 21, no: 21 });

    // at 1:15
    clock.tick(30 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 11, no: 11 });

    // at 2:00
    clock.tick(45 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 0, no: 0 });

    metrics.destroy();
    clock.restore();
});

test('should not fail when toggle metrics is missing yes/no field', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    blue: 10,
                    green: 10,
                },
            },
        },
    });

    t.truthy(metrics.globalCount === 123);
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 123,
        no: 0,
    });

    metrics.destroy();
});
<commit_msg>Fix assert<commit_after>'use strict';

const { test } = require('ava');
const UnleashClientMetrics = require('./index');
const moment = require('moment');
const sinon = require('sinon');

const { EventEmitter } = require('events');

const appName = 'appName';
const instanceId = 'instanceId';

test('should work without state', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    t.truthy(metrics.getAppsWithToggles());
    t.truthy(metrics.getTogglesMetrics());

    metrics.destroy();
});

test.cb('data should expire', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: Date.now() - 2000,
            stop: Date.now() - 1000,
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    let lastHourExpires = 0;
    metrics.lastHourList.on('expire', () => {
        lastHourExpires++;
    });

    let lastMinExpires = 0;
    metrics.lastMinuteList.on('expire', () => {
        lastMinExpires++;
    });

    clock.tick(60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 0);

    clock.tick(60 * 60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 1);

    clock.restore();
    t.end();
});

test('should listen to metrics from store', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    t.truthy(metrics.apps[appName].count === 123);
    t.truthy(metrics.globalCount === 123);

    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, {
        yes: 123,
        no: 0,
    });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 123,
        no: 0,
    });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 10,
                    no: 10,
                },
            },
        },
    });

    t.truthy(metrics.globalCount === 143);
    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, {
        yes: 133,
        no: 10,
    });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 133,
        no: 10,
    });

    metrics.destroy();
});

test('should build up list of seend toggles when new metrics arrives', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
                toggleY: {
                    yes: 50,
                    no: 50,
                },
            },
        },
    });

    const appToggles = metrics.getAppsWithToggles();
    const togglesForApp = metrics.getSeenTogglesByAppName(appName);

    t.truthy(appToggles.length === 1);
    t.truthy(appToggles[0].seenToggles.length === 2);
    t.truthy(appToggles[0].seenToggles.includes('toggleX'));
    t.truthy(appToggles[0].seenToggles.includes('toggleY'));

    t.truthy(togglesForApp.length === 2);
    t.truthy(togglesForApp.includes('toggleX'));
    t.truthy(togglesForApp.includes('toggleY'));
    metrics.destroy();
});

test('should handle a lot of toggles', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const toggleCounts = {};
    for (let i = 0; i < 100; i++) {
        toggleCounts[`toggle${i}`] = { yes: i, no: i };
    }

    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: toggleCounts,
        },
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 100);
    metrics.destroy();
});

test('should have correct values for lastMinute', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const now = new Date();
    const input = [
        {
            start: moment(now).subtract(1, 'hour'),
            stop: moment(now).subtract(59, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(30, 'minutes'),
            stop: moment(now).subtract(29, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(2, 'minutes'),
            stop: moment(now).subtract(1, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(2, 'minutes'),
            stop: moment(now).subtract(59, 'seconds'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now),
            stop: moment(now).subtract(30, 'seconds'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
    ];

    input.forEach(bucket => {
        store.emit('metrics', {
            appName,
            instanceId,
            bucket,
        });
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);
    t.truthy(seenToggles.length === 1);

    // metrics.se
    let c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 20, no: 20 });

    clock.tick(10 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 10, no: 10 });

    clock.tick(20 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastMinute.toggle, { yes: 0, no: 0 });

    metrics.destroy();
    clock.restore();
});

test('should have correct values for lastHour', t => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const now = new Date();
    const input = [
        {
            start: moment(now).subtract(1, 'hour'),
            stop: moment(now).subtract(59, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(30, 'minutes'),
            stop: moment(now).subtract(29, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).subtract(15, 'minutes'),
            stop: moment(now).subtract(14, 'minutes'),
            toggles: {
                toggle: { yes: 10, no: 10 },
            },
        },
        {
            start: moment(now).add(59, 'minutes'),
            stop: moment(now).add(1, 'hour'),
            toggles: {
                toggle: { yes: 11, no: 11 },
            },
        },
    ];

    input.forEach(bucket => {
        store.emit('metrics', {
            appName,
            instanceId,
            bucket,
        });
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 1);

    // metrics.se
    let c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 41, no: 41 });

    clock.tick(10 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 41, no: 41 });

    // at 30
    clock.tick(30 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 31, no: 31 });

    // at 45
    clock.tick(15 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 21, no: 21 });

    // at 1:15
    clock.tick(30 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 11, no: 11 });

    // at 2:00
    clock.tick(45 * 60 * 1000);
    c = metrics.getTogglesMetrics();
    t.deepEqual(c.lastHour.toggle, { yes: 0, no: 0 });

    metrics.destroy();
    clock.restore();
});

test('should not fail when toggle metrics is missing yes/no field', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    blue: 10,
                    green: 10,
                },
            },
        },
    });

    t.is(metrics.globalCount, 123);
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, {
        yes: 123,
        no: 0,
    });

    metrics.destroy();
});
<|endoftext|>


<commit_before>const Deferred = require("promised-io/promise").Deferred;
const all = require('promised-io/promise').all;
const when = require('promised-io/promise').when;
const debug = require('debug')('p3api-server:TranscriptomicsGene');
const request = require('request');
const config = require("../config");
const distributeURL = config.get("distributeURL");
const workspaceAPI = config.get("workspaceAPI");

function getWorkspaceObjects(paths, metadataOnly, token){
	const def = new Deferred();

	if(!(paths instanceof Array)){
		paths = [paths];
	}
	paths = paths.map(decodeURIComponent);

	request({
		method: "POST",
		url: workspaceAPI,
		json: true,
		body: {
			id: 1,
			method: "Workspace.get",
			version: "1.1",
			params: [{objects: paths, metadata_only: metadataOnly}]
		},
		headers: {
			"accept": "application/json",
			"content-type": "application/json",
			"authorization": token
		}
	}, function(err, resObj, results){
		if(err){
			debug("Error retrieving object from workspace: ", err);

			def.reject(err);
			return;
		}
		if(results.result){

			// const data = [];

			const defs = results.result[0].map(function(obj){
				// debug("reading obj0: ", obj[0]);
				// debug("reading obj1: ", obj[1]);
				const meta = {
					name: obj[0][0],
					type: obj[0][1],
					path: obj[0][2],
					creation_time: obj[0][3],
					id: obj[0][4],
					owner_id: obj[0][5],
					size: obj[0][6],
					userMeta: obj[0][7],
					autoMeta: obj[0][8],
					user_permissions: obj[0][9],
					global_permission: obj[0][10],
					link_reference: obj[0][11]
				};
				// debug("meta: ", meta);
				if(metadataOnly){
					// data.push(meta);
					// return true;
					return meta;
					// def.resolve(meta);
					// return true;
				}
				if(!meta.link_reference){
					// data.push({metadata: meta, data: obj[1]});
					// return true;
					return ({metadata: meta, data: obj[1]});
					// def.resolve({metadata: meta, data: obj[1]});
					// return true;
				}else{

					const headers = {
						"Authorization": "OAuth " + token
					};

					// debug("headers: ", headers);
					const defShock = new Deferred();
					request({
						method: 'GET',
						url: meta.link_reference + "?download",
						headers: headers
					}, function(err, res, d){
						// debug("err", err);
						// debug("res: ", res);
						// debug("downloaded: ", d);
						defShock.resolve({
							metadata: meta,
							data: d
						});
						// return true;
					});
					return defShock.promise;
				}
			});
			// debug("defs: ", defs);
			all(defs).then(function(d){
				// debug("all defs are resolved", paths);
				// debug("body: ", d);
				def.resolve(d);
			});
		}
	});
	return def.promise;
}

function readWorkspaceExperiments(tgState, options){

	const wsExpIds = tgState['wsExpIds'];
	const wsComparisonIds = tgState['wsComparisonIds'];
	const def = new Deferred();

	when(getWorkspaceObjects(wsExpIds, true, options.token), function(data){

		// debug("getWorkspaceObjects:", data);

		const fileList = data.map(function(obj){
			const files = {};
			obj.autoMeta.output_files.forEach(function(arr){
				// debug(typeof arr, arr);
				const d = (arr instanceof Array) ? arr[0] : arr;
				const file = d.substr(d.lastIndexOf('/') + 1);
				const type = file.split('.')[0];
				files[type] = d;
			});

			return {meta: obj, files: files};
		});

		const expressionFiles = fileList.map(function(obj){
			return obj.files.expression;
		});

		// debug("expressionFiles: ", expressionFiles);

		return when(getWorkspaceObjects(expressionFiles, false, options.token), function(results){

			const p3FeatureIdSet = {};
			const p2FeatureIdSet = {};

			const expressions = results.map(function(d){

				// debug("expressionFile results", typeof d.data, wsComparisonIds);
				// debug(d.data);

				if(!wsComparisonIds){
					return JSON.parse(d.data)['expression'];
				}else{
					return JSON.parse(d.data)['expression'].filter(function(e){
						return wsComparisonIds.indexOf(e.pid) >= 0;
					});
				}
			});

			const flattened = [].concat.apply([], expressions);

			// debug("expressions: ", typeof flattened, flattened.length);

			flattened.forEach(function(expression){
				// debug("expression: ", typeof expression, expression.feature_id, expression.na_feature_id);
				if(expression.hasOwnProperty('feature_id')){
					if(!p3FeatureIdSet.hasOwnProperty(expression.feature_id)){
						p3FeatureIdSet[expression.feature_id] = true;
					}
				}else if(expression.hasOwnProperty('na_feature_id')){
					if(!p2FeatureIdSet.hasOwnProperty(expression.na_feature_id)){
						p2FeatureIdSet[expression.na_feature_id] = true;
					}
				}
			});

			// debug("expressions:", flattened.length, "p3 ids: ", Object.keys(p3FeatureIdSet).length, "p2 ids: ", Object.keys(p2FeatureIdSet).length);

			def.resolve({
				expressions: flattened,
				p3FeatureIds: Object.keys(p3FeatureIdSet),
				p2FeatureIds: Object.keys(p2FeatureIdSet)
			});
		});
	});

	return def.promise;
}

function readPublicExperiments(tgState, options){

	const def = new Deferred();

	request.post({
		url: distributeURL + 'transcriptomics_gene/',
		json: true,
		headers: {
			'Accept': "application/solr+json",
			'Content-Type': "application/rqlquery+x-www-form-urlencoded",
			'Authorization': options.token || ""
		},
		body: tgState.query + "&select(pid,refseq_locus_tag,feature_id,log_ratio,z_score)&limit(1)"
	}, function(error, res, response){

		if(error || res.statusCode !== 200){
			debug(error, res.statusCode)
		}

		const numFound = response.response.numFound;

		const fetchSize = 25000;
		const steps = Math.ceil(numFound / fetchSize);
		const allRequests = [];

		for(let i = 0; i < steps; i++){
			const deferred = new Deferred();
			const range = "items=" + (i * fetchSize) + '-' + ((i + 1) * fetchSize - 1);
			// debug("Range: ", range);

			request.post({
				url: distributeURL + 'transcriptomics_gene/',
				json: true,
				headers: {
					'Accept': "application/json",
					'Content-Type': "application/rqlquery+x-www-form-urlencoded",
					'Range': range,
					'Authorization': options.token || ""
				},
				body: tgState.query + "&select(pid,refseq_locus_tag,feature_id,log_ratio,z_score)"
			}, function(err, res, body){
				deferred.resolve(body);
			});
			allRequests.push(deferred);
		}

		all(allRequests).then(function(results){

			const expressions = [];
			const p3FeatureIdSet = {};
			// const p2FeatureIdSet = {};

			results.forEach(function(genes){

				// debug("genes count: ", genes.length);

				genes.forEach(function(gene){
					expressions.push(gene);

					if(gene.hasOwnProperty("feature_id")){
						if(!p3FeatureIdSet.hasOwnProperty(gene.feature_id)){
							p3FeatureIdSet[gene.feature_id] = true;
						}
						// }else if(gene.hasOwnProperty("na_feature_id")){
						// 	if(!p2FeatureIdSet.hasOwnProperty(gene.na_feature_id)){
						// 		p2FeatureIdSet[gene.na_feature_id] = true;
						// 	}
					}
				});
			});

			// debug("expressions:", expressions.length, "p3 ids: ", Object.keys(p3FeatureIdSet).length, "p2 ids: ", Object.keys(p2FeatureIdSet).length);

			def.resolve({expressions: expressions, p3FeatureIds: Object.keys(p3FeatureIdSet), p2FeatureIds: []});
		});
	});

	return def.promise;
}

function processTranscriptomicsGene(tgState, options){
	const def = new Deferred();

	let wsCall = new Deferred();
	if(tgState.hasOwnProperty('wsExpIds')){
		wsCall = readWorkspaceExperiments(tgState, options);
	}else{
		wsCall.resolve({expressions: [], p3FeatureIds: [], p2FeatureIds: []});
	}

	let publicCall = new Deferred();
	if(tgState.hasOwnProperty('pbExpIds')){
		publicCall = readPublicExperiments(tgState, options);
	}else{
		publicCall.resolve({expressions: [], p3FeatureIds: [], p2FeatureIds: []});
	}

	all(publicCall, wsCall).then(function(results){
		// all(publicCall).then(function(results){

		const comparisonIdList = tgState.comparisonIds;

		const wsP3FeatureIdList = results[1].p3FeatureIds;
		const wsP2FeatureIdList = results[1].p2FeatureIds;
		const wsExpressions = results[1].expressions;

		const pbP3FeatureIdList = results[0].p3FeatureIds;
		const pbP2FeatureIdList = results[0].p2FeatureIds; // []
		const pbExpressions = results[0].expressions;

		const p3FeatureIdList = wsP3FeatureIdList.concat(pbP3FeatureIdList);
		const p2FeatureIdList = wsP2FeatureIdList.concat(pbP2FeatureIdList);
		const expressions = wsExpressions.concat(pbExpressions);

		debug("p3 ids: ", p3FeatureIdList.length, "p2 ids: ", p2FeatureIdList.length);

		const query = {
			q: [(p3FeatureIdList.length > 0) ? 'feature_id:(' + p3FeatureIdList.join(' OR ') + ')' : '',
				(p3FeatureIdList.length > 0 && p2FeatureIdList.length > 0) ? ' OR ' : '',
				(p2FeatureIdList.length > 0) ? 'p2_feature_id:(' + p2FeatureIdList.join(' OR ') + ')' : ''].join(''),
			fl: 'feature_id,p2_feature_id,strand,product,accession,start,end,patric_id,refseq_locus_tag,alt_locus_tag,genome_name,genome_id,gene'
		};
		// debug("genome_feature query: ", query);
		const q = Object.keys(query).map(p => p + "=" + query[p]).join("&");

		const fetchSize = 25000;
		const steps = Math.ceil((p3FeatureIdList.length + p2FeatureIdList.length) / fetchSize);
		const allRequests = [];

		for(let i = 0; i < steps; i++){
			const subDef = Deferred();
			const range = "items=" + (i * fetchSize) + '-' + ((i + 1) * fetchSize - 1);
			// debug("Range: ", range);

			request.post({
				url: distributeURL + 'genome_feature/',
				json: true,
				headers: {
					'Accept': "application/json",
					'Content-Type': "application/solrquery+x-www-form-urlencoded",
					'Range': range,
					'Authorization': options.token || ''
				},
				body: q
			}, function(err, res, body){
				subDef.resolve(body);
			});
			allRequests.push(subDef);
		}

		all(allRequests).then(function(body){

			const features = [].concat.apply([], body);

			const expressionHash = {};

			expressions.forEach(function(expression){
				let featureId;
				if(expression.hasOwnProperty("feature_id")){
					featureId = expression.feature_id;
				}else if(expression.hasOwnProperty("na_feature_id")){
					featureId = expression.na_feature_id;
				}

				if(!expressionHash.hasOwnProperty(featureId)){

					var expr = {samples: {}};
					(expression.hasOwnProperty('feature_id')) ? expr.feature_id = expression.feature_id : '';
					(expression.hasOwnProperty('na_feature_id')) ? expr.p2_feature_id = expression.na_feature_id : '';
					(expression.hasOwnProperty('refseq_locus_tag')) ? expr.refseq_locus_tag = expression.refseq_locus_tag : '';
					var log_ratio = expression.log_ratio, z_score = expression.z_score;
					expr.samples[expression.pid.toString()] = {
						log_ratio: log_ratio || '',
						z_score: z_score || ''
					};
					expr.up = (log_ratio != null && Number(log_ratio) > 0) ? 1 : 0;
					expr.down = (log_ratio != null && Number(log_ratio) < 0) ? 1 : 0;

					expressionHash[featureId] = expr;
				}else{
					expr = expressionHash[featureId];
					if(!expr.samples.hasOwnProperty(expression.pid.toString())){
						log_ratio = expression.log_ratio;
						z_score = expression.z_score;
						expr.samples[expression.pid.toString()] = {
							log_ratio: log_ratio || '',
							z_score: z_score || ''
						};
						(log_ratio != null && Number(log_ratio) > 0) ? expr.up++ : '';
						(log_ratio != null && Number(log_ratio) < 0) ? expr.down++ : '';

						expressionHash[featureId] = expr;
					}
				}
			});

			const data = [];
			features.forEach(function(feature){

				let expr;
				if(expressionHash.hasOwnProperty(feature.feature_id)){
					expr = expressionHash[feature.feature_id];
				}else if(expressionHash.hasOwnProperty(feature.p2_feature_id)){
					expr = expressionHash[feature.p2_feature_id];
				}

				// build expr object
				let count = 0;
				expr.sample_binary = comparisonIdList.map(function(comparisonId){
					if(expr.samples.hasOwnProperty(comparisonId) && expr.samples[comparisonId].log_ratio !== ''){
						count++;
						return "1";
					}else{
						return "0";
					}
				}).join('');
				expr.sample_size = count;

				const datum = Object.assign(feature, expr);
				data.push(datum);
			});

			def.resolve(data);
		});
	});

	return def.promise;
}

module.exports = {
	requireAuthentication: false,
	validate: function(params){
		const tgState = params[0];
		return tgState && tgState.comparisonIds.length > 0;
	},
	execute: function(params){
		const def = new Deferred();

		const tgState = params[0];
		const opts = params[1];

		when(processTranscriptomicsGene(tgState, opts), function(result){
			def.resolve(result);
		}, function(err){
			def.reject("Unable to process protein family queries. " + err);
		});

		return def.promise;
	}
};<commit_msg>remove dependency on autometa<commit_after>const Deferred = require("promised-io/promise").Deferred;
const all = require('promised-io/promise').all;
const when = require('promised-io/promise').when;
const debug = require('debug')('p3api-server:TranscriptomicsGene');
const request = require('request');
const config = require("../config");
const distributeURL = config.get("distributeURL");
const workspaceAPI = config.get("workspaceAPI");

function getWorkspaceObjects(paths, metadataOnly, token){
	const def = new Deferred();

	if(!(paths instanceof Array)){
		paths = [paths];
	}
	paths = paths.map(decodeURIComponent);

	request({
		method: "POST",
		url: workspaceAPI,
		json: true,
		body: {
			id: 1,
			method: "Workspace.get",
			version: "1.1",
			params: [{objects: paths, metadata_only: metadataOnly}]
		},
		headers: {
			"accept": "application/json",
			"content-type": "application/json",
			"authorization": token
		}
	}, function(err, resObj, results){
		if(err){
			debug("Error retrieving object from workspace: ", err);

			def.reject(err);
			return;
		}
		if(results.result){

			// const data = [];

			const defs = results.result[0].map(function(obj){
				// debug("reading obj0: ", obj[0]);
				// debug("reading obj1: ", obj[1]);
				const meta = {
					name: obj[0][0],
					type: obj[0][1],
					path: obj[0][2],
					creation_time: obj[0][3],
					id: obj[0][4],
					owner_id: obj[0][5],
					size: obj[0][6],
					userMeta: obj[0][7],
					autoMeta: obj[0][8],
					user_permissions: obj[0][9],
					global_permission: obj[0][10],
					link_reference: obj[0][11]
				};
				// debug("meta: ", meta);
				if(metadataOnly){
					// data.push(meta);
					// return true;
					return meta;
					// def.resolve(meta);
					// return true;
				}
				if(!meta.link_reference){
					// data.push({metadata: meta, data: obj[1]});
					// return true;
					return ({metadata: meta, data: obj[1]});
					// def.resolve({metadata: meta, data: obj[1]});
					// return true;
				}else{

					const headers = {
						"Authorization": "OAuth " + token
					};

					// debug("headers: ", headers);
					const defShock = new Deferred();
					request({
						method: 'GET',
						url: meta.link_reference + "?download",
						headers: headers
					}, function(err, res, d){
						// debug("err", err);
						// debug("res: ", res);
						// debug("downloaded: ", d);
						defShock.resolve({
							metadata: meta,
							data: d
						});
						// return true;
					});
					return defShock.promise;
				}
			});
			// debug("defs: ", defs);
			all(defs).then(function(d){
				// debug("all defs are resolved", paths);
				// debug("body: ", d);
				def.resolve(d);
			});
		}
	});
	return def.promise;
}

function readWorkspaceExperiments(tgState, options){

	const wsExpIds = tgState['wsExpIds'];
	const wsComparisonIds = tgState['wsComparisonIds'];
	const def = new Deferred();

    const expressionFiles =  wsExpIds.map(function(exp_id){
        var parts = exp_id.split('/'),
        jobName = parts.pop(),
        dotPath = parts.join('/') + '/.' + jobName +"/expression.json";
        return dotPath;
    });

		// debug("expressionFiles: ", expressionFiles);

		when(getWorkspaceObjects(expressionFiles, false, options.token), function(results){

			const p3FeatureIdSet = {};
			const p2FeatureIdSet = {};

			const expressions = results.map(function(d){

				// debug("expressionFile results", typeof d.data, wsComparisonIds);
				// debug(d.data);

				if(!wsComparisonIds){
					return JSON.parse(d.data)['expression'];
				}else{
					return JSON.parse(d.data)['expression'].filter(function(e){
						return wsComparisonIds.indexOf(e.pid) >= 0;
					});
				}
			});

			const flattened = [].concat.apply([], expressions);

			// debug("expressions: ", typeof flattened, flattened.length);

			flattened.forEach(function(expression){
				// debug("expression: ", typeof expression, expression.feature_id, expression.na_feature_id);
				if(expression.hasOwnProperty('feature_id')){
					if(!p3FeatureIdSet.hasOwnProperty(expression.feature_id)){
						p3FeatureIdSet[expression.feature_id] = true;
					}
				}else if(expression.hasOwnProperty('na_feature_id')){
					if(!p2FeatureIdSet.hasOwnProperty(expression.na_feature_id)){
						p2FeatureIdSet[expression.na_feature_id] = true;
					}
				}
			});

			// debug("expressions:", flattened.length, "p3 ids: ", Object.keys(p3FeatureIdSet).length, "p2 ids: ", Object.keys(p2FeatureIdSet).length);

			def.resolve({
				expressions: flattened,
				p3FeatureIds: Object.keys(p3FeatureIdSet),
				p2FeatureIds: Object.keys(p2FeatureIdSet)
			});
		});

	return def.promise;
}

function readPublicExperiments(tgState, options){

	const def = new Deferred();

	request.post({
		url: distributeURL + 'transcriptomics_gene/',
		json: true,
		headers: {
			'Accept': "application/solr+json",
			'Content-Type': "application/rqlquery+x-www-form-urlencoded",
			'Authorization': options.token || ""
		},
		body: tgState.query + "&select(pid,refseq_locus_tag,feature_id,log_ratio,z_score)&limit(1)"
	}, function(error, res, response){

		if(error || res.statusCode !== 200){
			debug(error, res.statusCode)
		}

		const numFound = response.response.numFound;

		const fetchSize = 25000;
		const steps = Math.ceil(numFound / fetchSize);
		const allRequests = [];

		for(let i = 0; i < steps; i++){
			const deferred = new Deferred();
			const range = "items=" + (i * fetchSize) + '-' + ((i + 1) * fetchSize - 1);
			// debug("Range: ", range);

			request.post({
				url: distributeURL + 'transcriptomics_gene/',
				json: true,
				headers: {
					'Accept': "application/json",
					'Content-Type': "application/rqlquery+x-www-form-urlencoded",
					'Range': range,
					'Authorization': options.token || ""
				},
				body: tgState.query + "&select(pid,refseq_locus_tag,feature_id,log_ratio,z_score)"
			}, function(err, res, body){
				deferred.resolve(body);
			});
			allRequests.push(deferred);
		}

		all(allRequests).then(function(results){

			const expressions = [];
			const p3FeatureIdSet = {};
			// const p2FeatureIdSet = {};

			results.forEach(function(genes){

				// debug("genes count: ", genes.length);

				genes.forEach(function(gene){
					expressions.push(gene);

					if(gene.hasOwnProperty("feature_id")){
						if(!p3FeatureIdSet.hasOwnProperty(gene.feature_id)){
							p3FeatureIdSet[gene.feature_id] = true;
						}
						// }else if(gene.hasOwnProperty("na_feature_id")){
						// 	if(!p2FeatureIdSet.hasOwnProperty(gene.na_feature_id)){
						// 		p2FeatureIdSet[gene.na_feature_id] = true;
						// 	}
					}
				});
			});

			// debug("expressions:", expressions.length, "p3 ids: ", Object.keys(p3FeatureIdSet).length, "p2 ids: ", Object.keys(p2FeatureIdSet).length);

			def.resolve({expressions: expressions, p3FeatureIds: Object.keys(p3FeatureIdSet), p2FeatureIds: []});
		});
	});

	return def.promise;
}

function processTranscriptomicsGene(tgState, options){
	const def = new Deferred();

	let wsCall = new Deferred();
	if(tgState.hasOwnProperty('wsExpIds')){
		wsCall = readWorkspaceExperiments(tgState, options);
	}else{
		wsCall.resolve({expressions: [], p3FeatureIds: [], p2FeatureIds: []});
	}

	let publicCall = new Deferred();
	if(tgState.hasOwnProperty('pbExpIds')){
		publicCall = readPublicExperiments(tgState, options);
	}else{
		publicCall.resolve({expressions: [], p3FeatureIds: [], p2FeatureIds: []});
	}

	all(publicCall, wsCall).then(function(results){
		// all(publicCall).then(function(results){

		const comparisonIdList = tgState.comparisonIds;

		const wsP3FeatureIdList = results[1].p3FeatureIds;
		const wsP2FeatureIdList = results[1].p2FeatureIds;
		const wsExpressions = results[1].expressions;

		const pbP3FeatureIdList = results[0].p3FeatureIds;
		const pbP2FeatureIdList = results[0].p2FeatureIds; // []
		const pbExpressions = results[0].expressions;

		const p3FeatureIdList = wsP3FeatureIdList.concat(pbP3FeatureIdList);
		const p2FeatureIdList = wsP2FeatureIdList.concat(pbP2FeatureIdList);
		const expressions = wsExpressions.concat(pbExpressions);

		debug("p3 ids: ", p3FeatureIdList.length, "p2 ids: ", p2FeatureIdList.length);

		const query = {
			q: [(p3FeatureIdList.length > 0) ? 'feature_id:(' + p3FeatureIdList.join(' OR ') + ')' : '',
				(p3FeatureIdList.length > 0 && p2FeatureIdList.length > 0) ? ' OR ' : '',
				(p2FeatureIdList.length > 0) ? 'p2_feature_id:(' + p2FeatureIdList.join(' OR ') + ')' : ''].join(''),
			fl: 'feature_id,p2_feature_id,strand,product,accession,start,end,patric_id,refseq_locus_tag,alt_locus_tag,genome_name,genome_id,gene'
		};
		// debug("genome_feature query: ", query);
		const q = Object.keys(query).map(p => p + "=" + query[p]).join("&");

		const fetchSize = 25000;
		const steps = Math.ceil((p3FeatureIdList.length + p2FeatureIdList.length) / fetchSize);
		const allRequests = [];

		for(let i = 0; i < steps; i++){
			const subDef = Deferred();
			const range = "items=" + (i * fetchSize) + '-' + ((i + 1) * fetchSize - 1);
			// debug("Range: ", range);

			request.post({
				url: distributeURL + 'genome_feature/',
				json: true,
				headers: {
					'Accept': "application/json",
					'Content-Type': "application/solrquery+x-www-form-urlencoded",
					'Range': range,
					'Authorization': options.token || ''
				},
				body: q
			}, function(err, res, body){
				subDef.resolve(body);
			});
			allRequests.push(subDef);
		}

		all(allRequests).then(function(body){

			const features = [].concat.apply([], body);

			const expressionHash = {};

			expressions.forEach(function(expression){
				let featureId;
				if(expression.hasOwnProperty("feature_id")){
					featureId = expression.feature_id;
				}else if(expression.hasOwnProperty("na_feature_id")){
					featureId = expression.na_feature_id;
				}

				if(!expressionHash.hasOwnProperty(featureId)){

					var expr = {samples: {}};
					(expression.hasOwnProperty('feature_id')) ? expr.feature_id = expression.feature_id : '';
					(expression.hasOwnProperty('na_feature_id')) ? expr.p2_feature_id = expression.na_feature_id : '';
					(expression.hasOwnProperty('refseq_locus_tag')) ? expr.refseq_locus_tag = expression.refseq_locus_tag : '';
					var log_ratio = expression.log_ratio, z_score = expression.z_score;
					expr.samples[expression.pid.toString()] = {
						log_ratio: log_ratio || '',
						z_score: z_score || ''
					};
					expr.up = (log_ratio != null && Number(log_ratio) > 0) ? 1 : 0;
					expr.down = (log_ratio != null && Number(log_ratio) < 0) ? 1 : 0;

					expressionHash[featureId] = expr;
				}else{
					expr = expressionHash[featureId];
					if(!expr.samples.hasOwnProperty(expression.pid.toString())){
						log_ratio = expression.log_ratio;
						z_score = expression.z_score;
						expr.samples[expression.pid.toString()] = {
							log_ratio: log_ratio || '',
							z_score: z_score || ''
						};
						(log_ratio != null && Number(log_ratio) > 0) ? expr.up++ : '';
						(log_ratio != null && Number(log_ratio) < 0) ? expr.down++ : '';

						expressionHash[featureId] = expr;
					}
				}
			});

			const data = [];
			features.forEach(function(feature){

				let expr;
				if(expressionHash.hasOwnProperty(feature.feature_id)){
					expr = expressionHash[feature.feature_id];
				}else if(expressionHash.hasOwnProperty(feature.p2_feature_id)){
					expr = expressionHash[feature.p2_feature_id];
				}
                if(expr){
                    // build expr object
                    let count = 0;
                    expr.sample_binary = comparisonIdList.map(function(comparisonId){
                        if(expr.samples.hasOwnProperty(comparisonId) && expr.samples[comparisonId].log_ratio !== ''){
                            count++;
                            return "1";
                        }else{
                            return "0";
                        }
                    }).join('');
                    expr.sample_size = count;

                    const datum = Object.assign(feature, expr);
                    data.push(datum);
                }
			});

			def.resolve(data);
		});
	});

	return def.promise;
}

module.exports = {
	requireAuthentication: false,
	validate: function(params){
		const tgState = params[0];
		return tgState && tgState.comparisonIds.length > 0;
	},
	execute: function(params){
		const def = new Deferred();

		const tgState = params[0];
		const opts = params[1];

		when(processTranscriptomicsGene(tgState, opts), function(result){
			def.resolve(result);
		}, function(err){
			def.reject("Unable to process protein family queries. " + err);
		});

		return def.promise;
	}
};
<|endoftext|>


<commit_before>const serial = require('serialport');
const sh = require('kool-shell');

let data = null;
let buffer = [];

const WIDTH = 380;
const HEIGHT = 310;

const Plotter = {
  data, buffer,
  WIDTH, HEIGHT,

  connect(_port, _baudRate) {
    port = new serial(_port, {
      baudRate: _baudRate,
      parser: serial.parsers.readline('\n'),
    });

    return new Promise((resolve, reject) => {
      port.on('open', function() {
        sh.success(`connected to ${_port}`);
        sh.info(`waiting for handshake...`);
      });

      port.on('data', (data) => {
        resolve(data);
        this.processBuffer();
      });

      port.on('error', function(err) {
        reject(new Error(err, 'plotter.js'));
      });
    });
  },

  // -------------------------------------------------------------------------

  sendMessage(message, verbose = false) {
    if (port) {
      message = message + '\n';
      port.write(message, function(err) {
        if (err) sh.error(new Error(err, 'plotter.js'));
        else {
          if (verbose) sh.info(message);
        }
      });
    } else sh.error(new Error('port isn\'t openned.', 'plotter.js'));
  },

  addToBuffer(message, verbose = false) {
    if (verbose) sh.info(`"${message}" buffered.`);
    this.buffer.push(message);
  },

  processBuffer() {
    if (this.buffer.length > 0) {
      let message = this.buffer.shift();
      this.sendMessage(message);
    }
  },

  // -------------------------------------------------------------------------

  getPosition() {
    this.addToBuffer('P');
    return this;
  },

  pen(position, verbose = false) {
    this.addToBuffer(`M1 ${position}`, verbose);
    return this;
  },

  move(x, y, verbose = false) {
    if (x < 0 || x > WIDTH || y < 0 && y > HEIGHT) {
      sh.warning('warning : position will be outside plotter\'s boundaries.')
    }
    this.addToBuffer(`G1 X${x} Y${y}`, verbose);
    return this;
  },

  home(verbose = false) {
    this.addToBuffer('G28', verbose);
    return this;
  },

  // -------------------------------------------------------------------------

  pen_up(verbose = false) {
    this.pen(0, verbose);
    return this;
  },

  pen_down(verbose = false) {
    this.pen(90, verbose);
    return this;
  },

  draw(x, y, verbose = false) {
    this.pen_down(verbose);
    this.move(x, y, verbose);
    this.pen_up(verbose);
    return this;
  },

}

module.exports = Plotter;<commit_msg>server: many commands !<commit_after>const serial = require('serialport');
const sh = require('kool-shell');

let data = null;
let buffer = [];
const WIDTH = 310;
const HEIGHT = 380;

const Plotter = {
  data, buffer,
  WIDTH, HEIGHT,

  // -------------------------------------------------------------------------
  // SERIAL

  connect(address, baudRate, verbose = false) {
    this.verbose = verbose;
    this.port = new serial(address, {
      baudRate: baudRate,
      parser: serial.parsers.readline('\n'),
    });

    return new Promise((resolve, reject) => {
      this.port.on('open', function() {
        sh.info(`connecting to ${address}...`);
        sh.info(`waiting for handshake...`);
      });

      this.port.on('data', (data) => {
        resolve(data);
        // sh.info(data);
        this.processBuffer();
      });

      this.port.on('error', (err) => {
        reject(new Error(err, 'plotter.js'));
      });
    }).then(() => {
      sh.success('connected.');
      this.connected = true;
    });
  },

  disconnect() {
    return new Promise((resolve, reject) => {
      if (this.port) {
        this.port.close(() => {
          resolve('disconnected.');
        });
      } else reject(new Error('port is alread disconnected.'));
    }).then((res) => {
      sh.success(res);
      this.connected = false;
    }).catch((err) => {
      sh.error(err);
    });
  },

  // -------------------------------------------------------------------------
  // COMMUNICATION

  sendMessage(message) {
    if (this.port) {
      this.port.write(`${message}\n`, (err) => {
        if (err) sh.error(new Error(err, 'plotter.js'));
        else {
          if (this.verbose) sh.info(message);
        }
      });
    } else sh.error(new Error('port isn\'t openned.', 'plotter.js'));
  },

  addToBuffer(message) { this.buffer.push(message); },

  processBuffer() {
    if (this.buffer.length > 0) {
      let message = this.buffer.shift();
      if (message === -1 && this.onEndCallback) this.onEndCallback();
      else this.sendMessage(message);
    }
  },

  // -------------------------------------------------------------------------
  // SETUP COMMANDS

  setSpeed(speed) {
    this.addToBuffer(`S ${speed}`);
    return this;
  },

  drawBoundaries() {
    this
      .home()
      .pen_down()
      // FIXME : the plotter can't go from 0 to HEIGHT in one move (???)
      .move(0, this.HEIGHT / 2)
      .move(0, this.HEIGHT)
      .move(this.WIDTH, this.HEIGHT)
      .move(this.WIDTH, this.HEIGHT / 2)
      .move(this.WIDTH, 0)
      .move(0, 0);
    return this;
  },

  // -------------------------------------------------------------------------
  // UTILS COMMANDS

  getPosition() {
    this.addToBuffer('P');
    return this;
  },

  home() {
    this.pen_up();
    this.addToBuffer('G28');
    return this;
  },

  end(callback) {
    this.addToBuffer(-1);
    this.onEndCallback = callback;
    return this;
  },

  move(x, y) {
    if (x < 0 || x > this.WIDTH || y < 0 || y > this.HEIGHT) {
      sh.warning(`G1 X${x} Y${y} : position will be outside plotter's boundaries.`);
    }
    this.addToBuffer(`G1 X${x} Y${y}`);
    return this;
  },

  // -------------------------------------------------------------------------
  // PEN COMMANDS

  pen(position) {
    this.addToBuffer(`M1 ${position}`);
    return this;
  },

  pen_up(force_motion = false) {
    if (!this.up ||force_motion) {
      this.up = true;
      this.pen(0);
    }
    return this;
  },

  pen_down(force_motion = false) {
    if (this.up || force_motion) {
      this.up = false;
      this.pen(90);
    }
    return this;
  },

  // -------------------------------------------------------------------------
  // 2D PRIMITIVES

  point(x, y) {
    this.move(x, y);
    // TODO : find a better temporisation method
    for (let i = 0; i < 2; i++) this.pen_down(true);
    this.pen_up();
    return this;
  },

  polygon(points) {
    this
      .pen_up()
      .move(points[0][0], points[0][1])
      .pen_down();

    for (let i = 1; i < points.length; i++) {
      let point = points[i];
      this.move(point[0], point[1]);
    }
    return this;
  },

  line(x1, y1, x2, y2) {
    return this.polygon([[x1, y1], [x2, y2]]);
  },

  ellipse(cx, cy, w, h, sides = 100) {
    let points = [];
    for (let theta = 0; theta <= Math.PI * 2; theta += (Math.PI / sides)) {
      let x = cx + Math.sin(theta) * w;
      let y = cy + Math.cos(theta) * h;
      points.push([x, y]);
    }
    return this.polygon(points);
  },

  circle(cx, cy, r, sides = 100) {
    return this.ellipse(cx, cy, r, r, sides);
  },

  triangle(x1, y1, x2, y2, x3, y3) {
    return this.polygon([[x1, y1], [x2, y2], [x3, y3], [x1, y1]]);
  },

  quad(x1, y1, x2, y2, x3, y3, x4, y4) {
    return this.polygon([[x1, y1], [x2, y2], [x3, y3], [x4, y4], [x1, y1]]);
  },

  rect(x, y, w, h) {
    return this.polygon([[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]]);
  }

}

module.exports = Plotter;<|endoftext|>


<commit_before>/** @module deliteful/ViewStack */
define(["dcl/dcl",
	"decor/sniff",
	"dojo/on",
	"dojo/Deferred",
	"dojo/dom-class",
	"delite/register",
	"delite/Widget",
	"delite/DisplayContainer",
	"delite/theme!./ViewStack/themes/{{theme}}/ViewStack.css",
	"delite/css!./ViewStack/transitions/slide.css",
	"delite/css!./ViewStack/transitions/reveal.css"],
	function (dcl, has, on, Deferred, domClass, register, Widget, DisplayContainer) {
		function setVisibility(node, val) {
			if (node) {
				if (val) {
					node.style.visibility = "visible";
					node.style.display = "";
				} else {
					node.style.visibility = "hidden";
					node.style.display = "none";
				}
			}
		}
		function setReverse(node) {
			if (node) {
				domClass.add(node, "-d-view-stack-reverse");
			}
		}
		function cleanCSS(node) {
			if (node) {
				node.className = node.className.split(/ +/).filter(function (x) {
					return !/^-d-view-stack/.test(x);
				}).join(" ");
			}
		}
		function transitionClass(s) {
			return "-d-view-stack-" + s;
		}

		/**
		 * ViewStack container widget. Display one child at a time.
		 *
		 * The first child is displayed by default.
		 * The methods 'show' is used to change the visible child.
		 *
		 * Styling
		 * The following CSS attributes must not be changed.
		 *  ViewStack node:  position, box-sizing, overflow-x
		 *  ViewStack children:  position, box-sizing, width, height
		 *
		 * @example:
		 * <d-view-stack id="vs">
		 *
...

		 *
...

		 *
...

		 * </d-view-stack>
		 * <button onclick="vs.show(childB, {transition: 'reveal', reverse: true})">...</button>
		 * @class module:deliteful/ViewStack
		 * @augments module:delite/DisplayContainer
		 */
		return register("d-view-stack", [HTMLElement, DisplayContainer],
			/** @lends module:deliteful/ViewStack# */{
			/**
			 * The name of the CSS class of this widget.
			 * @member {string}
			 * @default "d-view-stack"
			 */
			baseClass: "d-view-stack",

			/**
			 * The transition type used if not specified in the second argument of the show method.
			 * Transitions type are: "none", "slide", "reveal", "flip", "fade".
			 * @member {string}
			 * @default "slide"
			 */
			transition: "slide",

			/**
			 * If true, the transition animation is reversed.
			 * This attribute is supported by "slide" and "reveal" transition types.
			 * @member {boolean}
			 * @default false
			 */
			reverse: false,

			/**
			 * The selected child id, can be set explicitly or through the show() method.
			 * The effect of setting this property (i.e. getting the value through the getter) might be
			 * asynchronous when an animated transition occurs.
			 * @member {string}
			 * @default ""
			 */
			selectedChildId: "",

			_pendingChild: null,

			_setSelectedChildIdAttr: function (child) {
				if (this.ownerDocument.getElementById(child)) {
					if (this._started) {
						this.show(child);
					} else {
						this._pendingChild = child;
					}
				}
			},

			_getSelectedChildIdAttr: function () {
				return this._visibleChild ? this._visibleChild.id : "";
			},

			startup: function () {
				var noTransition = {transition: "none"};
				if (this._pendingChild) {
					this.show(this._pendingChild, noTransition);
					this._pendingChild = null;
				} else if (this.children.length > 0) {
					this.show(this.children[0], noTransition);
				}
			},

			_timing: 0,

			_setChildrenVisibility: function () {
				var cdn = this.children;
				if (!this._visibleChild && cdn.length > 0) {
					this._visibleChild = cdn[0];
				}
				for (var i = 0; i < cdn.length; i++) {
					setVisibility(cdn[i], cdn[i] === this._visibleChild);
				}
			},

			preCreate: function () {
				this._transitionTiming = {default: 0, chrome: 20, ios: 20, android: 100, mozilla: 100};
				for (var o in this._transitionTiming) {
					if (has(o) && this._timing < this._transitionTiming[o]) {
						this._timing = this._transitionTiming[o];
					}
				}
				if (typeof MutationObserver !== "undefined") {
					new MutationObserver(this._setChildrenVisibility.bind(this)).observe(this, {childList: true});
				}
				else {
					this._setupContainerMethods();
				}
			},

			_setupContainerMethods: function () {
				var superAppendChild = this.appendChild;
				this.appendChild = function (newChild) {
					var res = superAppendChild.apply(this, [newChild]);
					this._setChildrenVisibility();
					return res;
				};
				var superInsertBefore = this.insertBefore;
				this.insertBefore = function (newChild, refChild) {
					var res = superInsertBefore.apply(this, [newChild, refChild]);
					this._setChildrenVisibility();
					return res;
				};
			},

			buildRendering: function () {
				this._setChildrenVisibility();
			},

			showNext: function (props) {
				//		Shows the next child in the container.
				this._showPreviousNext("nextElementSibling", props);
			},

			showPrevious: function (props) {
				//		Shows the previous child in the container.
				this._showPreviousNext("previousElementSibling", props);
			},

			_showPreviousNext: function (direction, props) {
				if (!this._visibleChild && this.children.length > 0) {
					this._visibleChild = this.children[0];
				}
				if (this._visibleChild) {
					var target = this._visibleChild[direction];
					if (target) {
						this.show(target, props);
					}
				}
			},

			_doTransition: function (origin, target, event, transition, reverse, deferred) {
				if (transition !== "none") {
					if (origin) {
						this._setAfterTransitionHandlers(origin, event, deferred);
						domClass.add(origin, transitionClass(transition));
					}
					if (target) {
						this._setAfterTransitionHandlers(target, event, deferred);
						domClass.add(target, [transitionClass(transition), "-d-view-stack-in"]);
					}
					if (reverse) {
						setReverse(origin);
						setReverse(target);
					}
					this.defer(function () {
						if (target) {
							domClass.add(target, "-d-view-stack-transition");
						}
						if (origin) {
							domClass.add(origin, ["-d-view-stack-transition", "-d-view-stack-out"]);
						}
						if (reverse) {
							setReverse(origin);
							setReverse(target);
						}
						if (target) {
							domClass.add(target, "-d-view-stack-in");
						}
					}, this._timing);
				} else {
					if (origin !== target) {
						setVisibility(origin, false);
					}
					deferred.resolve();
				}
			},

			changeDisplay: function (widget, event) {
				// Resolved when display is completed.
				var deferred = new Deferred();

				if (!widget || widget.parentNode !== this) {
					deferred.resolve();
					return deferred.promise;
				}

				var origin = this._visibleChild;

				// Needed because the CSS state of a node can be incorrect
				// if a previous transitionend has been dropped
				cleanCSS(origin);
				cleanCSS(widget);

				setVisibility(widget, true);
				this._visibleChild = widget;

				var transition  = (origin === widget) ? "none" : (event.transition || "slide");
				var reverse = this.isLeftToRight() ? event.reverse : !event.reverse;
				this._doTransition(origin, widget, event, transition, reverse, deferred);

				return deferred.promise;
			},
			/**
			 * Shows a children of the ViewStack. The parameter 'params' is optional. If not specified,
			 * this.transition, and this.reverse are used.
			 * This method must be called to display a particular destination child on this container.
			 * @param {dest} Widget or HTMLElement or id that points to the child this container must display.
			 * @param {params} Optional params. A hash like {transition: "reveal", reverse: true}. The transition value
			 * can be "slide", "overlay", "fade" or "flip". Reverse transition applies to "slide" and
			 * "reveal".
			 * @returns {module:dojo/promise/Promise} A promise that will be resolved when the display and
			 * transition effect will have been performed.
			 */
			show: dcl.superCall(function (sup) {
				return function (dest, params) {
					if (this._visibleChild && this._visibleChild.parentNode !== this) {
						// The visible child has been removed.
						this._visibleChild = null;
					}
					if (!this._visibleChild && this.children.length > 0) {
						// The default visible child is the first one.
						this._visibleChild = this.children[0];
					}
					return sup.apply(this, [dest, params]);
				};
			}),

			_setAfterTransitionHandlers: function (node, event, deferred) {
				var self = this, endProps = {
					node: node,
					handle: function () { self._afterTransitionHandle(endProps); },
					props: event,
					deferred: deferred
				};

				domClass.add(this, "-d-view-stack-transition");
				node.addEventListener("webkitTransitionEnd", endProps.handle);
				node.addEventListener("transitionend", endProps.handle); // IE10 + FF
			},

			_afterTransitionHandle: function (item) {
				// Workaround for FF transitionend randomly dropped.
				// This method should be called once, when the target view transition is done.
				// But when 2 transitions (ex: slide) start at the same time, the transitionend event of the
				// target view can be dropped.
				if (!item.deferred.isResolved()) {
					// First call
					var vb;
					for (var i = 0; i < this.children.length; i++) {
						vb = this._visibleChild === this.children[i];
						setVisibility(this.children[i], vb);
						if (!vb) {
							cleanCSS(this.children[i]);
						}
					}

					item.node.removeEventListener("webkitTransitionEnd", item.handle);
					item.node.removeEventListener("transitionend", item.handle);
					item.deferred.resolve();
				} else {
					// Second call (occurs randomly on FF). The following code is not critical but try
					// to keep a clean DOM tree as much as possible.
					cleanCSS(item.node);
					domClass.remove(this, "-d-view-stack-transition");
				}
			}
		});
	});

<commit_msg>Fix #264 ViewStack: leverage delite/Container#onAddChild()<commit_after>/** @module deliteful/ViewStack */
define(["dcl/dcl",
	"decor/sniff",
	"dojo/on",
	"dojo/Deferred",
	"dojo/dom-class",
	"delite/register",
	"delite/Widget",
	"delite/DisplayContainer",
	"delite/theme!./ViewStack/themes/{{theme}}/ViewStack.css",
	"delite/css!./ViewStack/transitions/slide.css",
	"delite/css!./ViewStack/transitions/reveal.css"],
	function (dcl, has, on, Deferred, domClass, register, Widget, DisplayContainer) {
		function setVisibility(node, val) {
			if (node) {
				if (val) {
					node.style.visibility = "visible";
					node.style.display = "";
				} else {
					node.style.visibility = "hidden";
					node.style.display = "none";
				}
			}
		}
		function setReverse(node) {
			if (node) {
				domClass.add(node, "-d-view-stack-reverse");
			}
		}
		function cleanCSS(node) {
			if (node) {
				node.className = node.className.split(/ +/).filter(function (x) {
					return !/^-d-view-stack/.test(x);
				}).join(" ");
			}
		}
		function transitionClass(s) {
			return "-d-view-stack-" + s;
		}

		/**
		 * ViewStack container widget. Display one child at a time.
		 *
		 * The first child is displayed by default.
		 * The methods 'show' is used to change the visible child.
		 *
		 * Styling
		 * The following CSS attributes must not be changed.
		 *  ViewStack node:  position, box-sizing, overflow-x
		 *  ViewStack children:  position, box-sizing, width, height
		 *
		 * @example:
		 * <d-view-stack id="vs">
		 *
...

		 *
...

		 *
...

		 * </d-view-stack>
		 * <button onclick="vs.show(childB, {transition: 'reveal', reverse: true})">...</button>
		 * @class module:deliteful/ViewStack
		 * @augments module:delite/DisplayContainer
		 */
		return register("d-view-stack", [HTMLElement, DisplayContainer],
			/** @lends module:deliteful/ViewStack# */{
			/**
			 * The name of the CSS class of this widget.
			 * @member {string}
			 * @default "d-view-stack"
			 */
			baseClass: "d-view-stack",

			/**
			 * The transition type used if not specified in the second argument of the show method.
			 * Transitions type are: "none", "slide", "reveal", "flip", "fade".
			 * @member {string}
			 * @default "slide"
			 */
			transition: "slide",

			/**
			 * If true, the transition animation is reversed.
			 * This attribute is supported by "slide" and "reveal" transition types.
			 * @member {boolean}
			 * @default false
			 */
			reverse: false,

			/**
			 * The selected child id, can be set explicitly or through the show() method.
			 * The effect of setting this property (i.e. getting the value through the getter) might be
			 * asynchronous when an animated transition occurs.
			 * @member {string}
			 * @default ""
			 */
			selectedChildId: "",

			_pendingChild: null,

			_setSelectedChildIdAttr: function (child) {
				if (this.ownerDocument.getElementById(child)) {
					if (this._started) {
						this.show(child);
					} else {
						this._pendingChild = child;
					}
				}
			},

			_getSelectedChildIdAttr: function () {
				return this._visibleChild ? this._visibleChild.id : "";
			},

			startup: function () {
				var noTransition = {transition: "none"};
				if (this._pendingChild) {
					this.show(this._pendingChild, noTransition);
					this._pendingChild = null;
				} else if (this.children.length > 0) {
					this.show(this.children[0], noTransition);
				}
			},

			_timing: 0,

			_setChildrenVisibility: function () {
				var cdn = this.children;
				if (!this._visibleChild && cdn.length > 0) {
					this._visibleChild = cdn[0];
				}
				for (var i = 0; i < cdn.length; i++) {
					setVisibility(cdn[i], cdn[i] === this._visibleChild);
				}
			},

			preCreate: function () {
				this._transitionTiming = {default: 0, chrome: 20, ios: 20, android: 100, mozilla: 100};
				for (var o in this._transitionTiming) {
					if (has(o) && this._timing < this._transitionTiming[o]) {
						this._timing = this._transitionTiming[o];
					}
				}
			},

			/*
			 * @private
			 */
			onAddChild: dcl.superCall(function (sup) {
				return function (node) {
					var res = sup.call(this, node);
					this._setChildrenVisibility();
					return res;
				};
			}),

			buildRendering: function () {
				this._setChildrenVisibility();
			},

			showNext: function (props) {
				//		Shows the next child in the container.
				this._showPreviousNext("nextElementSibling", props);
			},

			showPrevious: function (props) {
				//		Shows the previous child in the container.
				this._showPreviousNext("previousElementSibling", props);
			},

			_showPreviousNext: function (direction, props) {
				if (!this._visibleChild && this.children.length > 0) {
					this._visibleChild = this.children[0];
				}
				if (this._visibleChild) {
					var target = this._visibleChild[direction];
					if (target) {
						this.show(target, props);
					}
				}
			},

			_doTransition: function (origin, target, event, transition, reverse, deferred) {
				if (transition !== "none") {
					if (origin) {
						this._setAfterTransitionHandlers(origin, event, deferred);
						domClass.add(origin, transitionClass(transition));
					}
					if (target) {
						this._setAfterTransitionHandlers(target, event, deferred);
						domClass.add(target, [transitionClass(transition), "-d-view-stack-in"]);
					}
					if (reverse) {
						setReverse(origin);
						setReverse(target);
					}
					this.defer(function () {
						if (target) {
							domClass.add(target, "-d-view-stack-transition");
						}
						if (origin) {
							domClass.add(origin, ["-d-view-stack-transition", "-d-view-stack-out"]);
						}
						if (reverse) {
							setReverse(origin);
							setReverse(target);
						}
						if (target) {
							domClass.add(target, "-d-view-stack-in");
						}
					}, this._timing);
				} else {
					if (origin !== target) {
						setVisibility(origin, false);
					}
					deferred.resolve();
				}
			},

			changeDisplay: function (widget, event) {
				// Resolved when display is completed.
				var deferred = new Deferred();

				if (!widget || widget.parentNode !== this) {
					deferred.resolve();
					return deferred.promise;
				}

				var origin = this._visibleChild;

				// Needed because the CSS state of a node can be incorrect
				// if a previous transitionend has been dropped
				cleanCSS(origin);
				cleanCSS(widget);

				setVisibility(widget, true);
				this._visibleChild = widget;

				var transition  = (origin === widget) ? "none" : (event.transition || "slide");
				var reverse = this.isLeftToRight() ? event.reverse : !event.reverse;
				this._doTransition(origin, widget, event, transition, reverse, deferred);

				return deferred.promise;
			},
			/**
			 * Shows a children of the ViewStack. The parameter 'params' is optional. If not specified,
			 * this.transition, and this.reverse are used.
			 * This method must be called to display a particular destination child on this container.
			 * @param {dest} Widget or HTMLElement or id that points to the child this container must display.
			 * @param {params} Optional params. A hash like {transition: "reveal", reverse: true}. The transition value
			 * can be "slide", "overlay", "fade" or "flip". Reverse transition applies to "slide" and
			 * "reveal".
			 * @returns {module:dojo/promise/Promise} A promise that will be resolved when the display and
			 * transition effect will have been performed.
			 */
			show: dcl.superCall(function (sup) {
				return function (dest, params) {
					if (this._visibleChild && this._visibleChild.parentNode !== this) {
						// The visible child has been removed.
						this._visibleChild = null;
					}
					if (!this._visibleChild && this.children.length > 0) {
						// The default visible child is the first one.
						this._visibleChild = this.children[0];
					}
					return sup.apply(this, [dest, params]);
				};
			}),

			_setAfterTransitionHandlers: function (node, event, deferred) {
				var self = this, endProps = {
					node: node,
					handle: function () { self._afterTransitionHandle(endProps); },
					props: event,
					deferred: deferred
				};

				domClass.add(this, "-d-view-stack-transition");
				node.addEventListener("webkitTransitionEnd", endProps.handle);
				node.addEventListener("transitionend", endProps.handle); // IE10 + FF
			},

			_afterTransitionHandle: function (item) {
				// Workaround for FF transitionend randomly dropped.
				// This method should be called once, when the target view transition is done.
				// But when 2 transitions (ex: slide) start at the same time, the transitionend event of the
				// target view can be dropped.
				if (!item.deferred.isResolved()) {
					// First call
					var vb;
					for (var i = 0; i < this.children.length; i++) {
						vb = this._visibleChild === this.children[i];
						setVisibility(this.children[i], vb);
						if (!vb) {
							cleanCSS(this.children[i]);
						}
					}

					item.node.removeEventListener("webkitTransitionEnd", item.handle);
					item.node.removeEventListener("transitionend", item.handle);
					item.deferred.resolve();
				} else {
					// Second call (occurs randomly on FF). The following code is not critical but try
					// to keep a clean DOM tree as much as possible.
					cleanCSS(item.node);
					domClass.remove(this, "-d-view-stack-transition");
				}
			}
		});
	});

<|endoftext|>


<commit_before>/**
 * Provides Form and input features.
 *
 * @module furnace
 * @submodule furnace-forms
 */
import Panel from './panel';
import Proxy from 'furnace-forms/proxy';
import Conditional from 'furnace-forms/mixins/controls/conditional';
import { defaultCondition } from 'furnace-forms/utils/conditions';
import lookupProxy from 'furnace-forms/utils/lookup-proxy';
import Ember from 'ember';
/**
 * Form control component proxy
 *
 * @class Form
 * @namespace Furnace.Forms.Controls
 * @extends Furnace.Forms.Controls.Abstract
 * @protected
 */
var Form = Panel.extend({
	_isForm : true,

	_decoratorName: 'form',

	_decoratorType: 'form',

	_syncFromSource : true,

	_syncToSource : true,

	_observer : null,

	_validationCache : null,

	_messageCache : null,

	_setValidations : function(result,silent) {
		var validations=result.get('validations');
		var messages=result.get('messages');

		this._validationCache=validations;
		this._messageCache=messages;
		var setControlMessages=function(control){
			control.setMessages(messages[name].toArray(),silent);
		};
		for(var name in validations) {
			if(this._controlsByPath[name]!==undefined) {
				this._controlsByPath[name].invoke('setValid',validations[name]);
				if(messages[name]!==undefined) {
					this._controlsByPath[name].forEach(setControlMessages);
				}
				else {
					this._controlsByPath[name].invoke('setMessages',null);
				}
			}
		}
	},

	_name: Ember.computed.oneWay('_modelName'),

	_modelPath : Ember.computed('_form,_name',{
		get : function() {
			if(!this._form) {
				return this.get('_name');
			} else {
				return this._panel.get('_path');
			}
			return '';
		}
	}).readOnly(),

	_controlsByPath: null,

	_forms: null,

	actions : {
		validate : function(paths) {

			this._validate(paths);

		},

		reset : function() {
			var hard=false;
			var action=null;
			if(arguments.length===1) {
				if(typeof arguments[0]==='boolean') {
					hard=arguments[0];
				} else {
					action=arguments[0];
				}
			} else if(arguments.length>1) {
				action=arguments[0];
				hard=arguments[1];
			}
			this._reset(hard);
			if(this._observer) {
				this._observer.run();
			}
			if(action) {
				this.send(action,this);
			}
		},

		submit: function(action) {
			action= action===undefined  ?  'submit' : action;
			var form=this;
			this._validate().then(function(valid) {
				if(valid) {
					form._submit();
					form.get('target').send(action,form);
				} else {
					// Notice about invalid for development?
					Ember.Logger.info(form+' was submitted but was marked invalid');
				}

			});

		}
	},

	_registerForm: function(form) {
		this._forms.pushObject(form);
		if(this._form) {
			this._form._registerForm(form);
		}
	},

	_unregisterForm: function(form) {
		this._forms.removeObject(form);
		if(this._form) {
			this._form._unregisterForm(form);
		}
	},

	_registerControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix) {
			name+='.'+prefix;
		}
		// We should register ourself nameless. If there is a parent it should register with our name.
		if(control!==this) {
			// If a control has path, register it with that path
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		} else {
			if(!name) {
				name='form';
			}
		}

		Ember.assert("Could not determine name for control "+control,name);
		if(this._controlsByPath[name]===undefined) {
			this._controlsByPath[name]=Ember.A();
		}

		this._controlsByPath[name].pushObject(control);

		if(this._validationCache[name]!==undefined) {
			control.setValid(this._validationCache[name]);
		}

		if(this._messageCache[name]!==undefined) {
			control.setMessages(this._messageCache[name],true);
		}

		if(this._form) {
			if(control===this) {
				this._form._registerForm(control);
			}
			else {
				this._form._registerControl(control, this._panel._path || this._name);
			}
		}
	},

	_unregisterControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix) {
			name+='.'+prefix;
		}

		if(control!==this) {
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		}else {
			if(!name) {
				name='form';
			}
		}

		Ember.warn(this+" cannot unregister control "+control+" with name "+name,this._controlsByPath[name]!==undefined,{id:'furnace-forms:control.form.unregister'});
		if(this._controlsByPath[name]!==undefined) {
			this._controlsByPath[name].removeObject(control);
		} else {
			console.log(this._controlsByPath);
		}
		if(this._form) {
			if(control===this) {
				this._form._unregisterForm(control);
			} else {
				this._form._unregisterControl(control, this._panel._path|| this._name);
			}
		}
	},

	validator : Ember.computed('_model',{
		get  :function() {
			// 	Always return our validator name, regardless of parent form validators
			return this.constructor.typeKey;
		},
		set : function(key,value) {
			return value;
		}
	}),

	validationDetached : Ember.computed('isEnabled,_validationDetached',{
		get : function() {
			return !this.get('isEnabled') || this.get('_validationDetached');
		}
	}).readOnly(),

	_validationDetached:false,

	_validate : function(paths) {
		var form=this;
		var target=this.get('_model');
		var modelName=this.get('_modelName');
		var validator=this.get('_validator');
		var promisses=Ember.A();
		if(this._panel && this._panel._isFormOption) {
			if(!this._panel.get('selected')) {
				return new Ember.RSVP.Promise(function(resolve) {
					resolve(true);
				});
			}
		}
		if(!validator) {
			return new Ember.RSVP.Promise(function(resolve) {
				resolve(true);
			});
		}
		if(paths) {
			if(typeof paths==='string'){
				paths=Ember.A(paths);
			}
			paths.toArray().forEach(function(path) {
				var _path=path.replace(/^_form\./,'');
				var input=form.get(_path);
				if(input) {
					paths.removeObject(path);
					validator=input.get('_form._validator');
					target=input.get('_form._model');
					path=input.get('_path');
					if(path) {
						Ember.assert('No target for validation of path ' + path + " in form " +form,target);
//						target=target.get(path);
//						if(validator)
//							validator=validator.get(path);
//						if(validator) {
							path=modelName+'.'+path;
							promisses.pushObject(validator.validate(target,modelName,null,[path]));
//						}
					}
					if(input instanceof Form) {
						promisses.pushObject(input._validate());
					}
				} else {
					Ember.warn('Tried to validate path "'+_path+'" but it does not exist in '+form);
				}

			});
			promisses.pushObjects(this._forms.invoke('_validate',paths));
		} else {
			promisses.pushObjects(this._forms.filterBy('validationDetached',false).invoke('_validate'));
			promisses.pushObject(validator.validate(target,modelName));
		}
		return Ember.RSVP.all(promisses).then(function(result){
			var outcome=true;
			result.forEach(function(result) {
				if(typeof result==='boolean') {
					if(!result) {
						outcome=false;
						return false;
					}
					return true;
				}
				if(!result.hasFinished()) {
					outcome=false;
					return false;
				} else {
					form._setValidations(result);
					result.reset();
					if(!result.isValid()) {
						outcome = false;
					}
				}
			});
			return outcome;

		});
	},

	_reset:function(modelChanged) {
		this._validationCache={};
		this._messageCache={};
		this._super(modelChanged);
		this._forms.invoke('_reset',modelChanged);
	},

	_validator: function() {
		var validator=this.get('validator');
		if(typeof validator==='object') {
			return validator;
		}
		else {
			var fn=this.container.lookup('validator:lookup');
			if(fn) {
				try {
					return fn.call(this,validator);
				}
				catch(e) {
					Ember.warn('A form tried to resolve a validator for "'+validator+'" but got the following error: '+e.message);
					return null;
				}
			}
			return null;
		}
	}.property('validator'),

	'for' : null,

	_model : Ember.computed.alias('for'),

	init : function() {
		this._forms=Ember.A();
		this._controlsByPath={};
		this._validationCache={};
		this._messageCache={};

		if(!this._syncFromSource || !this._syncToSource) {
			this._asyncObserver();
		}

		this._super();

		this.set('_path',null);

//		var targetObject=this._syncToSource ? this['for'] : this.get('_proxy');
		Ember.run.scheduleOnce('afterRender',this,this._setupValidator);
	},

	_setupValidator : function() {
		var validator=this.get('_validator');
		// Initialize validaton if a validator was resolved and we're the root form, or the root form validator has no validation for our path
		if(validator && (!this._form || !(this._form.get("_validator."+this._path) instanceof validator.constructor))) {
			var form= this;
			this._observer = validator.observe(this,'_model',function(result,sender) {
				var silent=sender===null || sender===form;
				// If our target model changed, immediate show validations unless it's a new model
				if(silent && ((form.get('_model.hasDirtyAttributes')===true || (form.get('_model.hasDirtyAttributes')===undefined && form.get('isDirty'))) && !form.get('_model.isNew'))) {
					silent=false;
				}
//
//				Ember.debug(form);
//				console.log('Ran validation',sender ? sender.toString() : null,key,silent);
//				console.log(result.get('validations'));
//				console.log(result.get('messages'));
//				Ember.debug(sender);
//				console.log('--------------');

				form._setValidations(result,silent);

				// We only want to receive changed status on next run, so reset the result if it has finished
				if(result.hasFinished()) {
					result.reset();
				}
			}, this.get('_modelName'));

			if(this.get('_model')) {
				this._currentModel=this.get('_model');
				this._observer.run();
			}
		}
	},

	_currentModel : undefined,

	_modelObserver : Ember.observer('_model',function(){
		// Only reset if 'for' actually changed
		if(this.get('_model')!==this._currentModel) {
			this._reset(true);
			this._currentModel=this.get('_model');
		}
	}),

	willSubmit : null,

	didSubmit : null,

	_submit : function() {
		this._apply();
	},

	submit : function(action) {
		this.send('submit',action);
	},

	destroy: function() {
		if(this._observer) {
			this._observer.destroy();
		}
		this._super();
	},

	getComponent : function() {
		var component=this.getComponentClass();
		return component.extend({_debugContainerKey : this._debugContainerKey});
	},

}).reopenClass({
	async : function() {
		this.reopen({
			_syncFromSource : false,

			_syncToSource : false,

			_asyncObserver : Ember.observer('for',function() {
				if(this._syncFromSource && this._syncToSource) {
					return;
				}
				var model=this.get('for');
				if(this.get('_model') instanceof Proxy) {
					this.get('_model').destroy();
				}
				if(model) {
					if(!this._syncFromSource || !this._syncToSource) {
						model= lookupProxy(model,null,{
							fromSource: this._syncFromSource,
							toSource: this._syncToSource});
					}
				}
				this.set('_model',model);
			}),

			_model : null
		});
		return this;
	},
	model : function() {
		switch(arguments.length) {
			case 2:
				this.reopen({
					_modelName : arguments[0],
					'for' : arguments[1]
				});
				break;
			case 1:
				if(typeof arguments[0]==='string') {
					this.reopen({
						_modelName : arguments[0]
					});
				} else {
					this.reopen({
						'for' : arguments[0]
					});
				}
				break;
		}
		return this;

	},
	on : function(props,fn) {
		props=props.split(',');
		var _conditionFn;
		if(arguments.length===1) {
			_conditionFn=defaultCondition;
		}
		else {
			_conditionFn=function customCondition() {
				this.get('conditionProperties').forEach(function(property) {
					this.get(property);
				},this);
				if(!this.hasModel()) {
					return false;
				}
				return fn.call(this);
			};
		}
		return this.extend(Conditional,{
			_conditionProps: props.join(','),
			_conditionFn: _conditionFn
		});
	},
});
export default Form;<commit_msg>Fixed regression with form async class method<commit_after>/**
 * Provides Form and input features.
 *
 * @module furnace
 * @submodule furnace-forms
 */
import Panel from './panel';
import Proxy from 'furnace-forms/proxy';
import Conditional from 'furnace-forms/mixins/controls/conditional';
import { defaultCondition } from 'furnace-forms/utils/conditions';
import lookupProxy from 'furnace-forms/utils/lookup-proxy';
import Ember from 'ember';
/**
 * Form control component proxy
 *
 * @class Form
 * @namespace Furnace.Forms.Controls
 * @extends Furnace.Forms.Controls.Abstract
 * @protected
 */
var Form = Panel.extend({
	_isForm : true,

	_decoratorName: 'form',

	_decoratorType: 'form',

	_syncFromSource : true,

	_syncToSource : true,

	_observer : null,

	_validationCache : null,

	_messageCache : null,

	_setValidations : function(result,silent) {
		var validations=result.get('validations');
		var messages=result.get('messages');

		this._validationCache=validations;
		this._messageCache=messages;
		var setControlMessages=function(control){
			control.setMessages(messages[name].toArray(),silent);
		};
		for(var name in validations) {
			if(this._controlsByPath[name]!==undefined) {
				this._controlsByPath[name].invoke('setValid',validations[name]);
				if(messages[name]!==undefined) {
					this._controlsByPath[name].forEach(setControlMessages);
				}
				else {
					this._controlsByPath[name].invoke('setMessages',null);
				}
			}
		}
	},

	_name: Ember.computed.oneWay('_modelName'),

	_modelPath : Ember.computed('_form,_name',{
		get : function() {
			if(!this._form) {
				return this.get('_name');
			} else {
				return this._panel.get('_path');
			}
			return '';
		}
	}).readOnly(),

	_controlsByPath: null,

	_forms: null,

	actions : {
		validate : function(paths) {

			this._validate(paths);

		},

		reset : function() {
			var hard=false;
			var action=null;
			if(arguments.length===1) {
				if(typeof arguments[0]==='boolean') {
					hard=arguments[0];
				} else {
					action=arguments[0];
				}
			} else if(arguments.length>1) {
				action=arguments[0];
				hard=arguments[1];
			}
			this._reset(hard);
			if(this._observer) {
				this._observer.run();
			}
			if(action) {
				this.send(action,this);
			}
		},

		submit: function(action) {
			action= action===undefined  ?  'submit' : action;
			var form=this;
			this._validate().then(function(valid) {
				if(valid) {
					form._submit();
					form.get('target').send(action,form);
				} else {
					// Notice about invalid for development?
					Ember.Logger.info(form+' was submitted but was marked invalid');
				}

			});

		}
	},

	_registerForm: function(form) {
		this._forms.pushObject(form);
		if(this._form) {
			this._form._registerForm(form);
		}
	},

	_unregisterForm: function(form) {
		this._forms.removeObject(form);
		if(this._form) {
			this._form._unregisterForm(form);
		}
	},

	_registerControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix) {
			name+='.'+prefix;
		}
		// We should register ourself nameless. If there is a parent it should register with our name.
		if(control!==this) {
			// If a control has path, register it with that path
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		} else {
			if(!name) {
				name='form';
			}
		}

		Ember.assert("Could not determine name for control "+control,name);
		if(this._controlsByPath[name]===undefined) {
			this._controlsByPath[name]=Ember.A();
		}

		this._controlsByPath[name].pushObject(control);

		if(this._validationCache[name]!==undefined) {
			control.setValid(this._validationCache[name]);
		}

		if(this._messageCache[name]!==undefined) {
			control.setMessages(this._messageCache[name],true);
		}

		if(this._form) {
			if(control===this) {
				this._form._registerForm(control);
			}
			else {
				this._form._registerControl(control, this._panel._path || this._name);
			}
		}
	},

	_unregisterControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix) {
			name+='.'+prefix;
		}

		if(control!==this) {
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		}else {
			if(!name) {
				name='form';
			}
		}

		Ember.warn(this+" cannot unregister control "+control+" with name "+name,this._controlsByPath[name]!==undefined,{id:'furnace-forms:control.form.unregister'});
		if(this._controlsByPath[name]!==undefined) {
			this._controlsByPath[name].removeObject(control);
		} else {
			console.log(this._controlsByPath);
		}
		if(this._form) {
			if(control===this) {
				this._form._unregisterForm(control);
			} else {
				this._form._unregisterControl(control, this._panel._path|| this._name);
			}
		}
	},

	validator : Ember.computed('_model',{
		get  :function() {
			// 	Always return our validator name, regardless of parent form validators
			return this.constructor.typeKey;
		},
		set : function(key,value) {
			return value;
		}
	}),

	validationDetached : Ember.computed('isEnabled,_validationDetached',{
		get : function() {
			return !this.get('isEnabled') || this.get('_validationDetached');
		}
	}).readOnly(),

	_validationDetached:false,

	_validate : function(paths) {
		var form=this;
		var target=this.get('_model');
		var modelName=this.get('_modelName');
		var validator=this.get('_validator');
		var promisses=Ember.A();
		if(this._panel && this._panel._isFormOption) {
			if(!this._panel.get('selected')) {
				return new Ember.RSVP.Promise(function(resolve) {
					resolve(true);
				});
			}
		}
		if(!validator) {
			return new Ember.RSVP.Promise(function(resolve) {
				resolve(true);
			});
		}
		if(paths) {
			if(typeof paths==='string'){
				paths=Ember.A(paths);
			}
			paths.toArray().forEach(function(path) {
				var _path=path.replace(/^_form\./,'');
				var input=form.get(_path);
				if(input) {
					paths.removeObject(path);
					validator=input.get('_form._validator');
					target=input.get('_form._model');
					path=input.get('_path');
					if(path) {
						Ember.assert('No target for validation of path ' + path + " in form " +form,target);
//						target=target.get(path);
//						if(validator)
//							validator=validator.get(path);
//						if(validator) {
							path=modelName+'.'+path;
							promisses.pushObject(validator.validate(target,modelName,null,[path]));
//						}
					}
					if(input instanceof Form) {
						promisses.pushObject(input._validate());
					}
				} else {
					Ember.warn('Tried to validate path "'+_path+'" but it does not exist in '+form);
				}

			});
			promisses.pushObjects(this._forms.invoke('_validate',paths));
		} else {
			promisses.pushObjects(this._forms.filterBy('validationDetached',false).invoke('_validate'));
			promisses.pushObject(validator.validate(target,modelName));
		}
		return Ember.RSVP.all(promisses).then(function(result){
			var outcome=true;
			result.forEach(function(result) {
				if(typeof result==='boolean') {
					if(!result) {
						outcome=false;
						return false;
					}
					return true;
				}
				if(!result.hasFinished()) {
					outcome=false;
					return false;
				} else {
					form._setValidations(result);
					result.reset();
					if(!result.isValid()) {
						outcome = false;
					}
				}
			});
			return outcome;

		});
	},

	_reset:function(modelChanged) {
		this._validationCache={};
		this._messageCache={};
		this._super(modelChanged);
		this._forms.invoke('_reset',modelChanged);
	},

	_validator: function() {
		var validator=this.get('validator');
		if(typeof validator==='object') {
			return validator;
		}
		else {
			var fn=this.container.lookup('validator:lookup');
			if(fn) {
				try {
					return fn.call(this,validator);
				}
				catch(e) {
					Ember.warn('A form tried to resolve a validator for "'+validator+'" but got the following error: '+e.message);
					return null;
				}
			}
			return null;
		}
	}.property('validator'),

	'for' : null,

	_model : Ember.computed.alias('for'),

	init : function() {
		this._forms=Ember.A();
		this._controlsByPath={};
		this._validationCache={};
		this._messageCache={};

		if(!this._syncFromSource || !this._syncToSource) {
			this._asyncObserver();
		}
		if(typeof this['for']==='function') {
			this.set('_model',this['for'].apply(this));
		}
		this._super();

		this.set('_path',null);

//		var targetObject=this._syncToSource ? this['for'] : this.get('_proxy');
		Ember.run.scheduleOnce('afterRender',this,this._setupValidator);
	},

	_setupValidator : function() {
		var validator=this.get('_validator');
		// Initialize validaton if a validator was resolved and we're the root form, or the root form validator has no validation for our path
		if(validator && (!this._form || !(this._form.get("_validator."+this._path) instanceof validator.constructor))) {
			var form= this;
			this._observer = validator.observe(this,'_model',function(result,sender) {
				var silent=sender===null || sender===form;
				// If our target model changed, immediate show validations unless it's a new model
				if(silent && ((form.get('_model.hasDirtyAttributes')===true || (form.get('_model.hasDirtyAttributes')===undefined && form.get('isDirty'))) && !form.get('_model.isNew'))) {
					silent=false;
				}
//
//				Ember.debug(form);
//				console.log('Ran validation',sender ? sender.toString() : null,key,silent);
//				console.log(result.get('validations'));
//				console.log(result.get('messages'));
//				Ember.debug(sender);
//				console.log('--------------');

				form._setValidations(result,silent);

				// We only want to receive changed status on next run, so reset the result if it has finished
				if(result.hasFinished()) {
					result.reset();
				}
			}, this.get('_modelName'));

			if(this.get('_model')) {
				this._currentModel=this.get('_model');
				this._observer.run();
			}
		}
	},

	_currentModel : undefined,

	_modelObserver : Ember.observer('_model',function(){
		// Only reset if 'for' actually changed
		if(this.get('_model')!==this._currentModel) {
			this._reset(true);
			this._currentModel=this.get('_model');
		}
	}),

	willSubmit : null,

	didSubmit : null,

	_submit : function() {
		this._apply();
	},

	submit : function(action) {
		this.send('submit',action);
	},

	destroy: function() {
		if(this._observer) {
			this._observer.destroy();
		}
		this._super();
	},

	getComponent : function() {
		var component=this.getComponentClass();
		return component.extend({_debugContainerKey : this._debugContainerKey});
	},

}).reopenClass({
	async : function(options) {
		this.reopen({
			_syncFromSource : options ? options.fromSource || false : false,

			_syncToSource : options ? options.toSource || false : false,

			_asyncObserver : Ember.observer('for',function() {
				if(this._syncFromSource && this._syncToSource) {
					return;
				}
				var model=this.get('for');
				if(this.get('_model') instanceof Proxy) {
					this.get('_model').destroy();
				}
				if(model) {
					if(!this._syncFromSource || !this._syncToSource) {
						model= lookupProxy.call(this,model,null,{
							fromSource: this._syncFromSource,
							toSource: this._syncToSource});
					}
				}
				this.set('_model',model);
			}),

			_model : null
		});
		return this;
	},
	model : function() {
		switch(arguments.length) {
			case 2:
				this.reopen({
					_modelName : arguments[0],
					'for' : arguments[1]
				});
				break;
			case 1:
				if(typeof arguments[0]==='string') {
					this.reopen({
						_modelName : arguments[0]
					});
				} else {
					this.reopen({
						'for' : arguments[0]
					});
				}
				break;
		}
		return this;

	},
	on : function(props,fn) {
		props=props.split(',');
		var _conditionFn;
		if(arguments.length===1) {
			_conditionFn=defaultCondition;
		}
		else {
			_conditionFn=function customCondition() {
				this.get('conditionProperties').forEach(function(property) {
					this.get(property);
				},this);
				if(!this.hasModel()) {
					return false;
				}
				return fn.call(this);
			};
		}
		return this.extend(Conditional,{
			_conditionProps: props.join(','),
			_conditionFn: _conditionFn
		});
	},
});
export default Form;<|endoftext|>