<commit_before>Minionette.View = Backbone.View.extend({
    constructor: function(options) {
        options || (options = {});

        // Ensure we have a Region to initialize
        // new regions from.
        this._ensureRegion(options);
        this._initializeRegions(options);

        Backbone.View.apply(this, arguments);

        // Have the view listenTo the model and collection.
        this._listenToEvents(this.model, _.result(this, 'modelEvents'));
        this._listenToEvents(this.collection, _.result(this, 'collectionEvents'));
    },

    // A default template that will clear this.$el.
    // Override this in a subclass to something useful.
    template: function() { return ''; },

    // A default function that will have it's return passed
    // to this.template
    // Override this in a subclass to something useful.
    serialize: function() {
        return {};
    },

    // The actual "serialize" that is fed into the this.template.
    // Used so a subclass can override this.serialize and still
    // have the `view` helper.
    _serialize: function() {
        return _.extend({view: _.bind(this._viewHelper, this)}, this.serialize());
    },

    // A useful remove method that triggers events.
    remove: function() {
        if (this._isRemoving) { return; }
        this._isRemoving = true;
        this.trigger('remove', this);

        this._removeFromParent();
        _.invoke(this._regions, 'remove');

        Minionette.View.__super__.remove.apply(this, arguments);

        this.trigger('removed', this);
        this.unbind();
    },

    // A useful default render method.
    render: function() {
        this.trigger('render', this);

        // Detach all our regions, so they don't need to be re-rendered.
        _.invoke(this._regions, 'detach');

        this.$el.html(this.template(this._serialize()));

        // Reattach all our regions
        _.invoke(this._regions, 'reattach');

        this.trigger('rendered', this);
        return this;
    },

    // Adds the region "name" to this as this[name].
    // Also attaches it to this._regions[name], for
    // internal management.
    addRegion: function(name, view) {
        var region = new this.Region({view: view});
        // Remove the region, if it exists already
        attempt(this._regions[name], 'remove');

        region.name = name;
        region._parent = this;
        this[name] = this._regions[name] = region;

        return region;
    },

    // Adds multiple regions to the view. Takes
    // an object with {regioneName: view} syntax
    addRegions: function(regions) {
        _.each(regions, function(view, name) {
            this.addRegion(name, view);
        }, this);
    },

    // A remove helper to remove this view from it's parent
    _removeFromParent: function() {
        // Remove this view from _parent, if it exists
        attempt(this._parent, '_removeView', this);
    },

    _removeRegion: function(region) {
        delete this[region.name];
        delete this._regions[region.name];
    },

    // Loop through the events given, and listen to
    // entity's event.
    _listenToEvents: function(entity, events) {
        if (!entity) { return; }
        _.each(events, function(method, event) {
            if (!_.isFunction(method)) { method = this[method]; }
            this.listenTo(entity, event, method);
        }, this);
    },

    // Sets this.Region. Prioritizes instantiated options.Region,
    // then a subclass' prototype Region, and defaults to Minionette.Region
    _ensureRegion: function(options) {
        this.Region = options.Region || this.Region || Minionette.Region;
    },

    // Takes the #regions object and creates the regions,
    // using the keys as the name and the values as the original
    // view. Keys are all that is required, passing in a false-y
    // value will make Region use a placeholder span element.
    _initializeRegions: function(options) {
        // Initialize our regions object
        this._regions = {};

        // Pull regions from instantiated options.
        var regions = _.result(options, 'regions') || _.result(this, 'regions');

        // Add the regions
        this.addRegions(regions);
    },

    // A helper that is passed to #template() that will
    // render regions inline.
    _viewHelper: function(view) {
        var region = this._regions[view];
        if (region) {
            return region.render().el.outerHTML;
        }
        return '';
    }
});
<commit_msg>Use better syntax<commit_after>Minionette.View = Backbone.View.extend({
    constructor: function(options) {
        options = options || {};

        // Ensure we have a Region to initialize
        // new regions from.
        this._ensureRegion(options);
        this._initializeRegions(options);

        Backbone.View.apply(this, arguments);

        // Have the view listenTo the model and collection.
        this._listenToEvents(this.model, _.result(this, 'modelEvents'));
        this._listenToEvents(this.collection, _.result(this, 'collectionEvents'));
    },

    // A default template that will clear this.$el.
    // Override this in a subclass to something useful.
    template: function() { return ''; },

    // A default function that will have it's return passed
    // to this.template
    // Override this in a subclass to something useful.
    serialize: function() {
        return {};
    },

    // The actual "serialize" that is fed into the this.template.
    // Used so a subclass can override this.serialize and still
    // have the `view` helper.
    _serialize: function() {
        return _.extend({view: _.bind(this._viewHelper, this)}, this.serialize());
    },

    // A useful remove method that triggers events.
    remove: function() {
        if (this._isRemoving) { return; }
        this._isRemoving = true;
        this.trigger('remove', this);

        this._removeFromParent();
        _.invoke(this._regions, 'remove');

        Minionette.View.__super__.remove.apply(this, arguments);

        this.trigger('removed', this);
        this.unbind();
    },

    // A useful default render method.
    render: function() {
        this.trigger('render', this);

        // Detach all our regions, so they don't need to be re-rendered.
        _.invoke(this._regions, 'detach');

        this.$el.html(this.template(this._serialize()));

        // Reattach all our regions
        _.invoke(this._regions, 'reattach');

        this.trigger('rendered', this);
        return this;
    },

    // Adds the region "name" to this as this[name].
    // Also attaches it to this._regions[name], for
    // internal management.
    addRegion: function(name, view) {
        var region = new this.Region({view: view});
        // Remove the region, if it exists already
        attempt(this._regions[name], 'remove');

        region.name = name;
        region._parent = this;
        this[name] = this._regions[name] = region;

        return region;
    },

    // Adds multiple regions to the view. Takes
    // an object with {regioneName: view} syntax
    addRegions: function(regions) {
        _.each(regions, function(view, name) {
            this.addRegion(name, view);
        }, this);
    },

    // A remove helper to remove this view from it's parent
    _removeFromParent: function() {
        // Remove this view from _parent, if it exists
        attempt(this._parent, '_removeView', this);
    },

    _removeRegion: function(region) {
        delete this[region.name];
        delete this._regions[region.name];
    },

    // Loop through the events given, and listen to
    // entity's event.
    _listenToEvents: function(entity, events) {
        if (!entity) { return; }
        _.each(events, function(method, event) {
            if (!_.isFunction(method)) { method = this[method]; }
            this.listenTo(entity, event, method);
        }, this);
    },

    // Sets this.Region. Prioritizes instantiated options.Region,
    // then a subclass' prototype Region, and defaults to Minionette.Region
    _ensureRegion: function(options) {
        this.Region = options.Region || this.Region || Minionette.Region;
    },

    // Takes the #regions object and creates the regions,
    // using the keys as the name and the values as the original
    // view. Keys are all that is required, passing in a false-y
    // value will make Region use a placeholder span element.
    _initializeRegions: function(options) {
        // Initialize our regions object
        this._regions = {};

        // Pull regions from instantiated options.
        var regions = _.result(options, 'regions') || _.result(this, 'regions');

        // Add the regions
        this.addRegions(regions);
    },

    // A helper that is passed to #template() that will
    // render regions inline.
    _viewHelper: function(view) {
        var region = this._regions[view];
        if (region) {
            return region.render().el.outerHTML;
        }
        return '';
    }
});
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';

import etch from 'etch';
import until from 'test-until';

import {cloneRepository, buildRepository, setUpLocalAndRemoteRepositories} from '../helpers';
import StatusBarTileController from '../../lib/controllers/status-bar-tile-controller';

describe('StatusBarTileController', function() {
  let atomEnvironment, workspace;

  beforeEach(function() {
    atomEnvironment = global.buildAtomEnvironment();
    workspace = atomEnvironment.workspace;
  });

  afterEach(function() {
    atomEnvironment.destroy();
  });

  describe('branches', function() {
    it('indicates the current branch and toggles visibility of the branch menu when clicked', async function() {
      const workdirPath = await cloneRepository('three-files');
      const repository = await buildRepository(workdirPath);

      const controller = new StatusBarTileController({workspace, repository});
      await controller.getLastModelDataRefreshPromise();

      const branchView = controller.refs.branchView;
      assert.equal(branchView.element.textContent, 'master');

      // FIXME: Remove this guard when 1.13 is on stable.
      if (parseFloat(atom.getVersion() >= 1.13)) {
        assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]);
        branchView.element.click();
        assert.isDefined(document.querySelectorAll('.github-BranchMenuView')[0]);
        branchView.element.click();
        assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]);
      }
    });

    describe('the branch menu', function() {
      describe('checking out an existing branch', function() {
        it('can check out existing branches with no conflicts', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          // create branch called 'branch'
          await repository.git.exec(['branch', 'branch']);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.deepEqual(branches, ['branch', 'master']);
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');

          list.selectedIndex = branches.indexOf('branch');
          list.onchange();
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          list.selectedIndex = branches.indexOf('master');
          list.onchange();
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');
        });

        it('displays an error message if checkout fails', async function() {
          const {localRepoPath} = await setUpLocalAndRemoteRepositories('three-files');
          const repository = await buildRepository(localRepoPath);
          await repository.git.exec(['branch', 'branch']);

          // create a conflict
          fs.writeFileSync(path.join(localRepoPath, 'a.txt'), 'a change');

          await repository.git.exec(['commit', '-a', '-m', 'change on master']);
          await repository.checkout('branch');
          fs.writeFileSync(path.join(localRepoPath, 'a.txt'), 'a change that conflicts');

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, message} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          list.selectedIndex = branches.indexOf('master');
          list.onchange();
          await etch.getScheduler().getNextUpdatePromise();
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');
          await until(() => message.innerHTML.match(/local changes.*would be overwritten/), 'error message displays');
        });
      });

      describe('checking out newly created branches', function() {
        it('can check out newly created branches', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, newBranchButton} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.deepEqual(branches, ['master']);
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');

          assert.isDefined(branchMenuView.refs.list);
          assert.isUndefined(branchMenuView.refs.editor);
          newBranchButton.click();
          await etch.getScheduler().getNextUpdatePromise();
          assert.isUndefined(branchMenuView.refs.list);
          assert.isDefined(branchMenuView.refs.editor);

          branchMenuView.refs.editor.setText('new-branch');
          await newBranchButton.onclick();
          repository.refresh();
          await controller.getLastModelDataRefreshPromise();

          assert.isUndefined(branchMenuView.refs.editor);
          assert.isDefined(branchMenuView.refs.list);

          assert.equal(await repository.getCurrentBranch(), 'new-branch');
          assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'new-branch');
        });

        it('displays an error message if branch already exists', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          await repository.git.exec(['checkout', '-b', 'branch']);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, newBranchButton, message} = branchMenuView.refs;

          const branches = Array.from(branchMenuView.refs.list.options).map(option => option.value);
          assert.deepEqual(branches, ['branch', 'master']);
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          await newBranchButton.onclick();

          branchMenuView.refs.editor.setText('master');
          await newBranchButton.onclick();
          assert.match(message.innerHTML, /branch.*already exists/);

          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'branch');
        });
      });
    });
  });

  describe('pushing and pulling', function() {
    it('indicates the ahead and behind counts and toggles visibility of the push pull menu when clicked', async function() {
      const {localRepoPath} = await setUpLocalAndRemoteRepositories();
      const repository = await buildRepository(localRepoPath);

      const controller = new StatusBarTileController({workspace, repository});
      await controller.getLastModelDataRefreshPromise();

      const pushPullView = controller.refs.pushPullView;
      const {aheadCount, behindCount} = pushPullView.refs;
      assert.equal(aheadCount.textContent, '');
      assert.equal(behindCount.textContent, '');

      await repository.git.exec(['reset', '--hard', 'head~2']);
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();
      assert.equal(aheadCount.textContent, '');
      assert.equal(behindCount.textContent, '2');

      await repository.git.commit('new local commit', {allowEmpty: true});
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();
      assert.equal(aheadCount.textContent, '1');
      assert.equal(behindCount.textContent, '2');

      // FIXME: Remove this guard when 1.13 is on stable.
      if (parseFloat(atom.getVersion() >= 1.13)) {
        assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
        pushPullView.element.click();
        assert.isDefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
        pushPullView.element.click();
        assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
      }
    });

    describe('the push/pull menu', function() {
      it('disables the pull button when there are changed files', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pullButton} = pushPullMenuView.refs;
        await repository.git.exec(['reset', '--hard', 'head~2']);
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.isFalse(pullButton.disabled);

        fs.writeFileSync(path.join(localRepoPath, 'file.txt'), 'a change\n');
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isTrue(pullButton.disabled);

        await repository.stageFiles(['file.txt']);
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isTrue(pullButton.disabled);

        await repository.commit('commit changes');
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isFalse(pullButton.disabled);
      });

      it('disables the fetch and pull buttons when there is no remote tracking branch and displays informative message', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);
        await repository.git.exec(['checkout', '-b', 'new-branch']);

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pushButton, pullButton, fetchButton, message} = pushPullMenuView.refs;

        assert.isTrue(pullButton.disabled);
        assert.isTrue(fetchButton.disabled);
        assert.match(message.innerHTML, /No remote detected.*Pushing will set up a remote tracking branch/);

        pushButton.dispatchEvent(new MouseEvent('click'));
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.isFalse(pullButton.disabled);
        assert.isFalse(fetchButton.disabled);
        assert.equal(message.textContent, '');
      });

      it('displays an error message if push fails and allows force pushing if meta key is pressed', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);
        await repository.git.exec(['reset', '--hard', 'head~2']);
        await repository.git.commit('another commit', {allowEmpty: true});

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pushButton, pullButton, message} = pushPullMenuView.refs;

        assert.equal(pushButton.textContent, 'Push (1)');
        assert.equal(pullButton.textContent, 'Pull (2)');

        pushButton.dispatchEvent(new MouseEvent('click'));
        await until('error message appears', () => /Push rejected/.test(message.innerHTML));

        pushButton.dispatchEvent(new MouseEvent('click', {metaKey: true}));
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.equal(pushButton.textContent, 'Push ');
        assert.equal(pullButton.textContent, 'Pull ');
      });
    });
  });

  describe('changed files', function() {
    it('shows the changed files count view when the repository data is loaded', async function() {
      const workdirPath = await cloneRepository('three-files');
      const repository = await buildRepository(workdirPath);

      const toggleGitPanel = sinon.spy();
      const controller = new StatusBarTileController({workspace, repository, toggleGitPanel});
      await controller.getLastModelDataRefreshPromise();

      const changedFilesCountView = controller.refs.changedFilesCountView;

      assert.equal(changedFilesCountView.element.textContent, '0 files');

      fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'a change\n');
      fs.unlinkSync(path.join(workdirPath, 'b.txt'));
      repository.refresh();
      await repository.stageFiles(['a.txt']);
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();

      assert.equal(changedFilesCountView.element.textContent, '2 files');

      changedFilesCountView.element.click();
      assert(toggleGitPanel.calledOnce);
    });
  });
});
<commit_msg>Test github:fetch command dispatch<commit_after>import fs from 'fs';
import path from 'path';

import etch from 'etch';
import until from 'test-until';

import {cloneRepository, buildRepository, setUpLocalAndRemoteRepositories} from '../helpers';
import StatusBarTileController from '../../lib/controllers/status-bar-tile-controller';

describe('StatusBarTileController', function() {
  let atomEnvironment, workspace, workspaceElement, commandRegistry;

  beforeEach(function() {
    atomEnvironment = global.buildAtomEnvironment();
    workspace = atomEnvironment.workspace;
    commandRegistry = atomEnvironment.commands;

    workspaceElement = atomEnvironment.views.getView(workspace);
  });

  afterEach(function() {
    atomEnvironment.destroy();
  });

  describe('branches', function() {
    it('indicates the current branch and toggles visibility of the branch menu when clicked', async function() {
      const workdirPath = await cloneRepository('three-files');
      const repository = await buildRepository(workdirPath);

      const controller = new StatusBarTileController({workspace, repository});
      await controller.getLastModelDataRefreshPromise();

      const branchView = controller.refs.branchView;
      assert.equal(branchView.element.textContent, 'master');

      // FIXME: Remove this guard when 1.13 is on stable.
      if (parseFloat(atom.getVersion() >= 1.13)) {
        assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]);
        branchView.element.click();
        assert.isDefined(document.querySelectorAll('.github-BranchMenuView')[0]);
        branchView.element.click();
        assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]);
      }
    });

    describe('the branch menu', function() {
      describe('checking out an existing branch', function() {
        it('can check out existing branches with no conflicts', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          // create branch called 'branch'
          await repository.git.exec(['branch', 'branch']);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.deepEqual(branches, ['branch', 'master']);
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');

          list.selectedIndex = branches.indexOf('branch');
          list.onchange();
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          list.selectedIndex = branches.indexOf('master');
          list.onchange();
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');
        });

        it('displays an error message if checkout fails', async function() {
          const {localRepoPath} = await setUpLocalAndRemoteRepositories('three-files');
          const repository = await buildRepository(localRepoPath);
          await repository.git.exec(['branch', 'branch']);

          // create a conflict
          fs.writeFileSync(path.join(localRepoPath, 'a.txt'), 'a change');

          await repository.git.exec(['commit', '-a', '-m', 'change on master']);
          await repository.checkout('branch');
          fs.writeFileSync(path.join(localRepoPath, 'a.txt'), 'a change that conflicts');

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, message} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          list.selectedIndex = branches.indexOf('master');
          list.onchange();
          await etch.getScheduler().getNextUpdatePromise();
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');
          await until(() => message.innerHTML.match(/local changes.*would be overwritten/), 'error message displays');
        });
      });

      describe('checking out newly created branches', function() {
        it('can check out newly created branches', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, newBranchButton} = branchMenuView.refs;

          const branches = Array.from(list.options).map(option => option.value);
          assert.deepEqual(branches, ['master']);
          assert.equal(await repository.getCurrentBranch(), 'master');
          assert.equal(list.selectedOptions[0].value, 'master');

          assert.isDefined(branchMenuView.refs.list);
          assert.isUndefined(branchMenuView.refs.editor);
          newBranchButton.click();
          await etch.getScheduler().getNextUpdatePromise();
          assert.isUndefined(branchMenuView.refs.list);
          assert.isDefined(branchMenuView.refs.editor);

          branchMenuView.refs.editor.setText('new-branch');
          await newBranchButton.onclick();
          repository.refresh();
          await controller.getLastModelDataRefreshPromise();

          assert.isUndefined(branchMenuView.refs.editor);
          assert.isDefined(branchMenuView.refs.list);

          assert.equal(await repository.getCurrentBranch(), 'new-branch');
          assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'new-branch');
        });

        it('displays an error message if branch already exists', async function() {
          const workdirPath = await cloneRepository('three-files');
          const repository = await buildRepository(workdirPath);

          await repository.git.exec(['checkout', '-b', 'branch']);

          const controller = new StatusBarTileController({workspace, repository});
          await controller.getLastModelDataRefreshPromise();

          const branchMenuView = controller.branchMenuView;
          const {list, newBranchButton, message} = branchMenuView.refs;

          const branches = Array.from(branchMenuView.refs.list.options).map(option => option.value);
          assert.deepEqual(branches, ['branch', 'master']);
          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(list.selectedOptions[0].value, 'branch');

          await newBranchButton.onclick();

          branchMenuView.refs.editor.setText('master');
          await newBranchButton.onclick();
          assert.match(message.innerHTML, /branch.*already exists/);

          assert.equal(await repository.getCurrentBranch(), 'branch');
          assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'branch');
        });
      });
    });
  });

  describe('pushing and pulling', function() {
    it('indicates the ahead and behind counts and toggles visibility of the push pull menu when clicked', async function() {
      const {localRepoPath} = await setUpLocalAndRemoteRepositories();
      const repository = await buildRepository(localRepoPath);

      const controller = new StatusBarTileController({workspace, repository});
      await controller.getLastModelDataRefreshPromise();

      const pushPullView = controller.refs.pushPullView;
      const {aheadCount, behindCount} = pushPullView.refs;
      assert.equal(aheadCount.textContent, '');
      assert.equal(behindCount.textContent, '');

      await repository.git.exec(['reset', '--hard', 'head~2']);
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();
      assert.equal(aheadCount.textContent, '');
      assert.equal(behindCount.textContent, '2');

      await repository.git.commit('new local commit', {allowEmpty: true});
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();
      assert.equal(aheadCount.textContent, '1');
      assert.equal(behindCount.textContent, '2');

      // FIXME: Remove this guard when 1.13 is on stable.
      if (parseFloat(atom.getVersion() >= 1.13)) {
        assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
        pushPullView.element.click();
        assert.isDefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
        pushPullView.element.click();
        assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]);
      }
    });

    describe('the push/pull menu', function() {
      it('disables the pull button when there are changed files', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pullButton} = pushPullMenuView.refs;
        await repository.git.exec(['reset', '--hard', 'head~2']);
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.isFalse(pullButton.disabled);

        fs.writeFileSync(path.join(localRepoPath, 'file.txt'), 'a change\n');
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isTrue(pullButton.disabled);

        await repository.stageFiles(['file.txt']);
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isTrue(pullButton.disabled);

        await repository.commit('commit changes');
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();
        assert.isFalse(pullButton.disabled);
      });

      it('disables the fetch and pull buttons when there is no remote tracking branch and displays informative message', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);
        await repository.git.exec(['checkout', '-b', 'new-branch']);

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pushButton, pullButton, fetchButton, message} = pushPullMenuView.refs;

        assert.isTrue(pullButton.disabled);
        assert.isTrue(fetchButton.disabled);
        assert.match(message.innerHTML, /No remote detected.*Pushing will set up a remote tracking branch/);

        pushButton.dispatchEvent(new MouseEvent('click'));
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.isFalse(pullButton.disabled);
        assert.isFalse(fetchButton.disabled);
        assert.equal(message.textContent, '');
      });

      it('displays an error message if push fails and allows force pushing if meta key is pressed', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories();
        const repository = await buildRepository(localRepoPath);
        await repository.git.exec(['reset', '--hard', 'head~2']);
        await repository.git.commit('another commit', {allowEmpty: true});

        const controller = new StatusBarTileController({workspace, repository});
        await controller.getLastModelDataRefreshPromise();

        const pushPullMenuView = controller.pushPullMenuView;
        const {pushButton, pullButton, message} = pushPullMenuView.refs;

        assert.equal(pushButton.textContent, 'Push (1)');
        assert.equal(pullButton.textContent, 'Pull (2)');

        pushButton.dispatchEvent(new MouseEvent('click'));
        await until('error message appears', () => /Push rejected/.test(message.innerHTML));

        pushButton.dispatchEvent(new MouseEvent('click', {metaKey: true}));
        repository.refresh();
        await controller.getLastModelDataRefreshPromise();

        assert.equal(pushButton.textContent, 'Push ');
        assert.equal(pullButton.textContent, 'Pull ');
      });
    });

    describe('fetch and pull commands', function() {
      it('fetches when github:fetch is triggered', async function() {
        const {localRepoPath} = await setUpLocalAndRemoteRepositories('multiple-commits', {remoteAhead: true});
        const repository = await buildRepository(localRepoPath);

        const controller = new StatusBarTileController({workspace, repository, commandRegistry});
        await controller.getLastModelDataRefreshPromise();

        sinon.spy(repository, 'fetch');

        commandRegistry.dispatch(workspaceElement, 'github:fetch');

        assert.isTrue(repository.fetch.called);
      });

      it('pulls when github:pull is triggered');
      it('pushes when github:push is triggered');
      it('force pushes when github:push-force is triggered');
    });
  });

  describe('changed files', function() {
    it('shows the changed files count view when the repository data is loaded', async function() {
      const workdirPath = await cloneRepository('three-files');
      const repository = await buildRepository(workdirPath);

      const toggleGitPanel = sinon.spy();
      const controller = new StatusBarTileController({workspace, repository, toggleGitPanel});
      await controller.getLastModelDataRefreshPromise();

      const changedFilesCountView = controller.refs.changedFilesCountView;

      assert.equal(changedFilesCountView.element.textContent, '0 files');

      fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'a change\n');
      fs.unlinkSync(path.join(workdirPath, 'b.txt'));
      repository.refresh();
      await repository.stageFiles(['a.txt']);
      repository.refresh();
      await controller.getLastModelDataRefreshPromise();

      assert.equal(changedFilesCountView.element.textContent, '2 files');

      changedFilesCountView.element.click();
      assert(toggleGitPanel.calledOnce);
    });
  });
});
<|endoftext|>


<commit_before>
const express = require('express');
const router = express.Router();

const connection = require('../mysql.js');
var sync= require('sync');
const util = require('util');
const date = new Date();
const currentDate = date.toISOString().replace(/T/, ' ').replace(/\..+/, '');

router.route('/forest/add').post((req,res)=>{
    var treeIndex= req.body.tree_idx;
    // var hasarray= req.body.hashtags;
    var hasarray= ['hi','ho','he'];
    var hashIndex;
    console.log('in forest add');
    connection.query('select is_shared from tree',(err, result)=>{
        if(err){
            throw err;
            console.log('is_shared select err');
            res.sendStatus(204);
        }
        if(result){
            connection.query('update tree set is_shared=? where tree_idx=?',[1,treeIndex],(err,result)=>{
                if(err){
                    throw err;

                    console.log('update is_shared');
                    res.sendStatus(203);
                }
                console.log(result);
            });
        }
    });
    var forestInput=[treeIndex,currentDate];
    connection.query('insert into forest(tree_idx,creation_time) values(?,?)',forestInput,(err, result)=>{
        if(err){
            throw err;
            console.log('err');
            res.sendStatus(204);
        }
        console.log(result);

        connection.query('select hash_idx from forest where tree_idx=?',treeIndex,(err,result)=>{
            if(err){
                throw err;
                console.log(err);
                res.sendStatus(204);
            }
            hashIndex=result[0].hash_idx;

            for(var i=0; i<hasarray.length;i++){
                var tagsInput=[hashIndex,hasarray[i]];
                connection.query('insert into tags(hash_idx,contant) values(?,?)',tagsInput,(err, result)=>{
                    if(err){
                        throw err;
                        console.log('tags err');
                        res.sendStatus(204);
                    }
                    console.log(result);
                })
                if(i==3){
                    res.sendStatus(200);
                }
            }
        })
    })
});
router.route('/forest/random/select').get((req,res)=>{
    console.log('in forest random');
    connection.query('select * from tree where is_shared=?',1,(err,result)=>{
        if(err){
            throw err;
            console.log('select random tree err');
            res.sendStatus(204);
        }
        console.log(result);
        res.json(result);
        res.sendStatus(200);
    })
});
router.route('/forest/tag/select').post((req,res)=>{
    console.log('in forest tag');
    var tag=req.body.contant;
    connection.query('select hash_idx from tags where contant=?',tag,(err,result)=>{
        if(err){
            throw err;
            console.log('select tag tree err');
            res.sendStatus(204);
        }
        var tagArray=result;

        function forfunction(){
            var treeArray=[];
                for(var i=0;i<tagArray.length;i++){
                    connection.query('select tree_idx from forest where hash_idx=?',tagArray[i].hash_idx,(err,result)=>{
                        if(err){
                            throw err;
                            console.log('select tag tree err');
                            res.sendStatus(204);
                        }
                        // console.log(result);

                        treeArray.push(result[0].tree_idx);
                        console.log(result[0].tree_idx);
                        // connection.query('select ')
                    })
                }
            return treeArray;
        }
        sync(function(){

            var result= forfunction.sync();
            console.log(result);
            console.log('fsdf');
        })

    })
});

module.exports = router;
<commit_msg>update select<commit_after>
const express = require('express');
const router = express.Router();

const connection = require('../mysql.js');
const util = require('util');
const date = new Date();
const currentDate = date.toISOString().replace(/T/, ' ').replace(/\..+/, '');

router.route('/forest/add').post((req,res)=>{
    var treeIndex= req.body.tree_idx;
    // var hasarray= req.body.hashtags;
    var hasarray= ['hi','ho','he'];
    var hashIndex;
    console.log('in forest add');
    connection.query('select is_shared from tree',(err, result)=>{
        if(err){
            throw err;
            console.log('is_shared select err');
            res.sendStatus(204);
        }
        if(result){
            connection.query('update tree set is_shared=? where tree_idx=?',[1,treeIndex],(err,result)=>{
                if(err){
                    throw err;

                    console.log('update is_shared');
                    res.sendStatus(203);
                }
                console.log(result);
            });
        }
    });
    var forestInput=[treeIndex,currentDate];
    connection.query('insert into forest(tree_idx,creation_time) values(?,?)',forestInput,(err, result)=>{
        if(err){
            throw err;
            console.log('err');
            res.sendStatus(204);
        }
        console.log(result);

        connection.query('select hash_idx from forest where tree_idx=?',treeIndex,(err,result)=>{
            if(err){
                throw err;
                console.log(err);
                res.sendStatus(204);
            }
            hashIndex=result[0].hash_idx;

            for(var i=0; i<hasarray.length;i++){
                var tagsInput=[hashIndex,hasarray[i]];
                connection.query('insert into tags(hash_idx,contant) values(?,?)',tagsInput,(err, result)=>{
                    if(err){
                        throw err;
                        console.log('tags err');
                        res.sendStatus(204);
                    }
                    console.log(result);
                })
                if(i==3){
                    res.sendStatus(200);
                }
            }
        })
    })
});
router.route('/forest/random/select').get((req,res)=>{
    console.log('in forest random');
    connection.query('select * from tree where is_shared=?',1,(err,result)=>{
        if(err){
            throw err;
            console.log('select random tree err');
            res.sendStatus(204);
        }
        console.log(result);
        res.json(result);
        res.sendStatus(200);
    })
});
router.route('/forest/tag/select').post((req,res)=>{
    console.log('in forest tag');
    var tag=req.body.contant;
    connection.query('select hash_idx from tags where contant=?',tag,(err,result)=>{
        if(err){
            throw err;
            console.log('select tag tree err');
            res.sendStatus(204);
        }
        var tagArray=result;

            var treeArray=[];
            for(var i=0;i<tagArray.length;i++){
                connection.query('select tree_idx from forest where hash_idx=?',tagArray[i].hash_idx,(err,result)=>{
                    if(err){
                        throw err;
                        console.log('select tag tree err');
                        res.sendStatus(204);
                    }
                        // console.log(result);

                    treeArray.push(result[0].tree_idx);
                    console.log(result[0].tree_idx);
                    // connection.query('select ')
                    if(i === tagArray.length-1){
                        res.status(200).json(treeArray);
                    }
                });
            }

    })

});

module.exports = router;
<|endoftext|>


<commit_before>cleverNote.Views.allNotesIndex = Backbone.CompositeView.extend({
  template: JST['notes/index'],
  className: 'notes-index',

  initialize: function (options) {
    this.setRight = !options.stopRight;
    var that = this;
    this.collection.fetch({
      remove: false,
      data: { page: 1 },
      success: function() {
        that.setRightView;
        $('.notes').append(JST['notes/loading']);
      }
    });
    this.listenTo(this.collection, 'add', this.addItemView);
    this.listenTo(this.collection, 'remove', this.removeItemView);
    this.collection.each(this.addItemView.bind(this));
  },

  setRightView: function() {
      this.setRight && window.router.showNote(this.collection.first().id, true);
  },

  addItemView: function(note) {
    var subview = new cleverNote.Views.notesIndexItem({ model: note });
    this.addSubview('.notes', subview);
    $('abbr.timeago').timeago();
  },

  removeItemView: function(note) {
    this.removeModelSubview('.notes', note);
  },

  listenForScroll: function () {
    this.$('div.notes').off('scroll');
    var throttled = _.throttle(this.nextPage.bind(this), 200);
    this.$('div.notes').on("scroll", throttled);
  },

  nextPage: function () {
    var that = this;
    var notesDiv = this.$('div.notes');
    if (notesDiv.scrollTop() >= notesDiv.prop('scrollHeight') - notesDiv.height() - 5) {
      if (that.collection.page < that.collection.totalPages) {
        that.collection.fetch({
          data: { page: that.collection.page + 1 },
          remove: false,
          success: function () {
            $('#loading-gif').remove();
            $('.notes').append(JST['notes/loading']);
          }
        });
      } else {
        $('#loading-gif').remove();
      }
    }
  },

  render: function () {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    $('abbr.timeago').timeago();
    this.listenForScroll();
    return this;
  }
});
<commit_msg>fix bug where first note wouldn't open when opening all notes index<commit_after>cleverNote.Views.allNotesIndex = Backbone.CompositeView.extend({
  template: JST['notes/index'],
  className: 'notes-index',

  initialize: function (options) {
    this.setRight = !options.stopRight;
    var that = this;
    this.collection.fetch({
      remove: false,
      data: { page: 1 },
      success: function() {
        that.setRightView();
        $('.notes').append(JST['notes/loading']);
      }
    });
    this.listenTo(this.collection, 'add', this.addItemView);
    this.listenTo(this.collection, 'remove', this.removeItemView);
    this.collection.each(this.addItemView.bind(this));
  },

  setRightView: function() {
      this.setRight && window.router.showNote(this.collection.first().id, true);
  },

  addItemView: function(note) {
    var subview = new cleverNote.Views.notesIndexItem({ model: note });
    this.addSubview('.notes', subview);
    $('abbr.timeago').timeago();
  },

  removeItemView: function(note) {
    this.removeModelSubview('.notes', note);
  },

  listenForScroll: function () {
    this.$('div.notes').off('scroll');
    var throttled = _.throttle(this.nextPage.bind(this), 200);
    this.$('div.notes').on("scroll", throttled);
  },

  nextPage: function () {
    var that = this;
    var notesDiv = this.$('div.notes');
    if (notesDiv.scrollTop() >= notesDiv.prop('scrollHeight') - notesDiv.height() - 5) {
      if (that.collection.page < that.collection.totalPages) {
        that.collection.fetch({
          data: { page: that.collection.page + 1 },
          remove: false,
          success: function () {
            $('#loading-gif').remove();
            $('.notes').append(JST['notes/loading']);
          }
        });
      } else {
        //$('#loading-gif').remove();
      }
    }
  },

  render: function () {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    $('abbr.timeago').timeago();
    this.listenForScroll();
    return this;
  }
});
<|endoftext|>


<commit_before>'use strict';
import React from 'react';
import fetch from 'isomorphic-fetch';
import { connect } from 'react-redux';
import * as d3 from 'd3';
import c from 'classnames';
import _ from 'lodash';
import moment from 'moment';
import { stringify as buildAPIQS } from 'qs';
import OpenAQ from 'openaq-design-system';
const { Modal, ModalHeader, ModalBody } = OpenAQ.Modal;

import { toggleValue } from '../utils/array';
import {
  fetchLocationsByCountry,
  invalidateLocationsByCountry
} from '../actions/action-creators';
import config from '../config';
import { formatThousands } from '../utils/format';

var ModalDownload = React.createClass({
  displayName: 'ModalDownload',

  propTypes: {
    _fetchLocationsByCountry: React.PropTypes.func,
    _invalidateLocationsByCountry: React.PropTypes.func,
    onModalClose: React.PropTypes.func,

    countries: React.PropTypes.array,
    parameters: React.PropTypes.array,

    locationsByCountry: React.PropTypes.object,

    // Preselect
    country: React.PropTypes.string,
    area: React.PropTypes.string,
    location: React.PropTypes.string
  },

  apiLimit: 65536,

  getInitialState: function () {
    return {
      locCountry: this.props.country || '--',
      locArea: this.props.area || '--',
      locLocation: this.props.location || '--',

      startYear: '--',
      startMonth: '--',
      startDay: '--',

      endYear: '--',
      endMonth: '--',
      endDay: '--',

      parameters: [],

      selectionCount: null
    };
  },

  //
  // Event Listeners
  //

  onOptSelect: function (key, e) {
    let state = {[key]: e.target.value};
    if (key === 'locCountry') {
      this.props._invalidateLocationsByCountry();
      this.props._fetchLocationsByCountry(e.target.value);
      state.locArea = '--';
      state.locLocation = '--';
    } else if (key === 'locArea') {
      state.locLocation = '--';
    }

    this.setState(state);
  },

  onParameterSelect: function (value) {
    let parameters = this.state.parameters;
    parameters = toggleValue(parameters, value);

    this.setState({parameters});
  },

  computeApiUrl: function (values, initalQS = {}) {
    let state = _.clone(values);
    _.forEach(state, (o, i) => {
      if (o === null || o === '--' || !o.length) {
        delete state[i];
      }
    });

    if (state.startYear && state.startMonth && state.startDay) {
      let sDate = moment(`${state.startYear}/${parseInt(state.startMonth) + 1}/${state.startDay}`, 'YYYY/M/D');
      if (sDate.isValid()) {
        state.sDate = sDate.toISOString();
      }
    }

    if (state.endYear && state.endMonth && state.endDay) {
      let eDate = moment(`${state.endYear}/${parseInt(state.endMonth) + 1}/${state.endDay}`, 'YYYY/M/D');
      if (eDate.isValid()) {
        state.eDate = eDate.toISOString();
      }
    }

    // Build url.
    let qs = initalQS;

    // It's enough to have one of these.
    if (state.locLocation) {
      qs.location = state.locLocation;
    } else if (state.locArea) {
      qs.city = state.locArea;
    } else if (state.locCountry) {
      qs.country = state.locCountry;
    }

    if (state.sDate) {
      qs.date_from = state.sDate;
    }

    if (state.eDate) {
      qs.date_to = state.eDate;
    }

    if (state.parameters) {
      qs.parameter = state.parameters;
    }

    qs = `${config.api}/measurements?${buildAPIQS(qs)}`;
    // console.log('computeDownloadUrl', qs);

    return qs;
  },

  computeDownloadUrl: function (values) {
    return this.computeApiUrl(values, {format: 'csv'});
  },

  checkSelectionCount: function (url) {
    // What is going on here?
    // The api is limited to 65,536 results, but some download options
    // combinations yield a lot more results. They could potentially
    // return the whole database (when nothing is selected). This is not
    // really an option, so the results are limited.
    // Nevertheless the user has to be warned of this, so on every parameter
    // change we query the api to know how many results would be returned, and
    // show a message in case the number is above the limit.
    // This is clearly a redux antipattern specially because we're not using
    // action but this is supposed to be temporary although we all know this
    // will probably stay here for a while.
    // Alas, TODO: do it properly!
    fetch(url)
      .then(response => {
        if (response.status >= 400) {
          throw new Error('Bad response');
        }
        return response.json();
      })
      .then(
        json => this.setState({selectionCount: json.meta.found}),
        e => console.log('e', e)
      );
  },

  //
  // life-cycle Methods
  //

  componentDidMount: function () {
    if (this.props.country) {
      this.props._fetchLocationsByCountry(this.props.country);
    }
  },

  componentWillUpdate: function (nextProps, nextState) {
    let prevUrl = this.computeApiUrl(this.state, {limit: 1});
    let nextUrl = this.computeApiUrl(nextState, {limit: 1});
    prevUrl !== nextUrl && this.checkSelectionCount(nextUrl);
  },

  //
  // Render Methods
  //

  renderDateSelector: function (what) {
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    let days = d3.range(1, 32, 1);
    let years = d3.range(2015, (new Date()).getFullYear() + 1, 1);
    return (
      <fieldset className='form__fieldset form__fieldset--date'>
        <legend className='form__legend'>{what === 'start' ? 'Start' : 'End'} Date</legend>

          <label htmlFor={`${what}-year`} className='form__label'>Year
          <select id={`${what}-year`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Year`]} onChange={this.onOptSelect.bind(null, `${what}Year`)}>
            <option value='--'>Year</option>
            {years.map(o => <option key={`startyear-${o}`} value={o}>{o}</option>)}
          </select>

          <label htmlFor={`${what}-month`} className='form__label'>Month
          <select id={`${what}-month`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Month`]} onChange={this.onOptSelect.bind(null, `${what}Month`)}>
            <option value='--'>Month</option>
            {months.map((o, i) => <option key={o} value={i}>{o}</option>)}
          </select>

          <label htmlFor={`${what}-day`} className='form__label'>Day
          <select id={`${what}-day`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Day`]} onChange={this.onOptSelect.bind(null, `${what}Day`)}>
            <option value='--'>Day</option>
            {days.map(o => <option key={`startday-${o}`} value={o}>{o}</option>)}
          </select>

      </fieldset>
    );
  },

  renderLocationSelector: function () {
    let {fetching: fetchingLocations, fetched: fetchedLocations, data: {results: locations}} = this.props.locationsByCountry;

    // Mental Sanity note: we use area to designate the broader region where a sensor
    // is while the API call it city.
    // Areas and Locations belonging to the selected country.
    // Will be filtered from the this.props.locations;
    let compareAreas = [];
    let compareLocations = [];

    if (locations) {
      compareAreas = _(locations)
        .filter(o => o.country === this.state.locCountry)
        .uniqBy('city')
        .sortBy('city')
        .value();

      if (locations && this.state.locArea !== '--') {
        compareLocations = _(locations)
          .filter(o => {
            // Has to belong to the correct area and can't have been selected before.
            return o.city === this.state.locArea;
          })
          .uniqBy('location')
          .sortBy('location')
          .value();
      }
    }

    let compareAreasLabel = fetchingLocations ? 'Loading Data' : 'Select an Area';
    let compareLocationsLabel = fetchingLocations ? 'Loading Data' : 'Select a Location';

    // Disable area while the locations are not fetched.
    let disableArea = !fetchedLocations || fetchingLocations;
    // Disable locations if locations are not fetched or are not selected
    let disableLocation = disableArea || this.state.locArea === '--';

    return (
      <fieldset className='form__fieldset form__fieldset--location'>
        <legend className='form__legend'>Location Selector</legend>

          Country
          <select id='loc-country' className='form__control form__control--medium select--base-bounded' value={this.state.locCountry} onChange={this.onOptSelect.bind(null, 'locCountry')}>
            <option value='--'>Select a Country</option>
            {this.props.countries.map(o => <option key={o.code} value={o.code}>{o.name}</option>)}
          </select>

          Area
          <select id='loc-area' className='form__control form__control--medium select--base-bounded' value={this.state.locArea} onChange={this.onOptSelect.bind(null, 'locArea')}>
            <option value='--'>{compareAreasLabel}</option>
            {compareAreas.map(o => <option key={o.city} value={o.city}>{o.city}</option>)}
          </select>

          Location
          <select id='loc-location' className='form__control form__control--medium select--base-bounded' value={this.state.locLocation} onChange={this.onOptSelect.bind(null, 'locLocation')}>
            <option value='--'>{compareLocationsLabel}</option>
            {compareLocations.map(o => <option key={o.location} value={o.location}>{o.location}</option>)}
          </select>

      </fieldset>
    );
  },

  renderParameters: function () {
    return (
      <fieldset className='form__fieldset form__fieldset--parameters'>
        <legend className='form__legend'>Parameters</legend>

          {this.props.parameters.map(o => {
            let checked = this.state.parameters.indexOf(o.id) !== -1;
            let onChange = this.onParameterSelect.bind(null, o.id);
            return (

                <input type='checkbox' value={o.id} id={o.id} name='form-checkbox' onChange={onChange} checked={checked} />
                {o.name}

            );
          })}

      </fieldset>
    );
  },

  renderResultCountMessage: function () {
    let countMessage = null;
    if (this.state.selectionCount !== null && this.state.selectionCount > this.apiLimit) {
      countMessage =
The API has a limit of {formatThousands(this.apiLimit)} measurements and your query yields {formatThousands(this.state.selectionCount)}, therefore results will be limited.
;
    }

    return countMessage;
  },

  render: function () {
    return (
      <Modal
        id='modal-download'
        className='modal--medium'
        onCloseClick={this.props.onModalClose}
        revealed >

        <ModalHeader>

Data Download

        </ModalHeader>
        <ModalBody>

Customize the data you want to download.

            <form className='form form--download'>
              {this.renderLocationSelector()}
              {this.renderDateSelector('start')}
              {this.renderDateSelector('end')}
              {this.renderParameters()}
              {this.renderResultCountMessage()}

                <button type='button' className='button button--primary-bounded' onClick={this.props.onModalClose}>Cancel</button>
                Download Selection (csv)

            </form>

        </ModalBody>
      </Modal>
    );
  }
});

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function selector (state) {
  return {
    countries: state.baseData.data.countries,
    parameters: state.baseData.data.parameters,

    locationsByCountry: state.locationsByCountry
  };
}

function dispatcher (dispatch) {
  return {
    _invalidateLocationsByCountry: (...args) => dispatch(invalidateLocationsByCountry(...args)),
    _fetchLocationsByCountry: (...args) => dispatch(fetchLocationsByCountry(...args))
  };
}

module.exports = connect(selector, dispatcher)(ModalDownload);
<commit_msg>Updating download modal text<commit_after>'use strict';
import React from 'react';
import fetch from 'isomorphic-fetch';
import { connect } from 'react-redux';
import * as d3 from 'd3';
import c from 'classnames';
import _ from 'lodash';
import moment from 'moment';
import { stringify as buildAPIQS } from 'qs';
import OpenAQ from 'openaq-design-system';
const { Modal, ModalHeader, ModalBody } = OpenAQ.Modal;

import { toggleValue } from '../utils/array';
import {
  fetchLocationsByCountry,
  invalidateLocationsByCountry
} from '../actions/action-creators';
import config from '../config';
import { formatThousands } from '../utils/format';

var ModalDownload = React.createClass({
  displayName: 'ModalDownload',

  propTypes: {
    _fetchLocationsByCountry: React.PropTypes.func,
    _invalidateLocationsByCountry: React.PropTypes.func,
    onModalClose: React.PropTypes.func,

    countries: React.PropTypes.array,
    parameters: React.PropTypes.array,

    locationsByCountry: React.PropTypes.object,

    // Preselect
    country: React.PropTypes.string,
    area: React.PropTypes.string,
    location: React.PropTypes.string
  },

  apiLimit: 65536,

  getInitialState: function () {
    return {
      locCountry: this.props.country || '--',
      locArea: this.props.area || '--',
      locLocation: this.props.location || '--',

      startYear: '--',
      startMonth: '--',
      startDay: '--',

      endYear: '--',
      endMonth: '--',
      endDay: '--',

      parameters: [],

      selectionCount: null
    };
  },

  //
  // Event Listeners
  //

  onOptSelect: function (key, e) {
    let state = {[key]: e.target.value};
    if (key === 'locCountry') {
      this.props._invalidateLocationsByCountry();
      this.props._fetchLocationsByCountry(e.target.value);
      state.locArea = '--';
      state.locLocation = '--';
    } else if (key === 'locArea') {
      state.locLocation = '--';
    }

    this.setState(state);
  },

  onParameterSelect: function (value) {
    let parameters = this.state.parameters;
    parameters = toggleValue(parameters, value);

    this.setState({parameters});
  },

  computeApiUrl: function (values, initalQS = {}) {
    let state = _.clone(values);
    _.forEach(state, (o, i) => {
      if (o === null || o === '--' || !o.length) {
        delete state[i];
      }
    });

    if (state.startYear && state.startMonth && state.startDay) {
      let sDate = moment(`${state.startYear}/${parseInt(state.startMonth) + 1}/${state.startDay}`, 'YYYY/M/D');
      if (sDate.isValid()) {
        state.sDate = sDate.toISOString();
      }
    }

    if (state.endYear && state.endMonth && state.endDay) {
      let eDate = moment(`${state.endYear}/${parseInt(state.endMonth) + 1}/${state.endDay}`, 'YYYY/M/D');
      if (eDate.isValid()) {
        state.eDate = eDate.toISOString();
      }
    }

    // Build url.
    let qs = initalQS;

    // It's enough to have one of these.
    if (state.locLocation) {
      qs.location = state.locLocation;
    } else if (state.locArea) {
      qs.city = state.locArea;
    } else if (state.locCountry) {
      qs.country = state.locCountry;
    }

    if (state.sDate) {
      qs.date_from = state.sDate;
    }

    if (state.eDate) {
      qs.date_to = state.eDate;
    }

    if (state.parameters) {
      qs.parameter = state.parameters;
    }

    qs = `${config.api}/measurements?${buildAPIQS(qs)}`;
    // console.log('computeDownloadUrl', qs);

    return qs;
  },

  computeDownloadUrl: function (values) {
    return this.computeApiUrl(values, {format: 'csv'});
  },

  checkSelectionCount: function (url) {
    // What is going on here?
    // The api is limited to 65,536 results, but some download options
    // combinations yield a lot more results. They could potentially
    // return the whole database (when nothing is selected). This is not
    // really an option, so the results are limited.
    // Nevertheless the user has to be warned of this, so on every parameter
    // change we query the api to know how many results would be returned, and
    // show a message in case the number is above the limit.
    // This is clearly a redux antipattern specially because we're not using
    // action but this is supposed to be temporary although we all know this
    // will probably stay here for a while.
    // Alas, TODO: do it properly!
    fetch(url)
      .then(response => {
        if (response.status >= 400) {
          throw new Error('Bad response');
        }
        return response.json();
      })
      .then(
        json => this.setState({selectionCount: json.meta.found}),
        e => console.log('e', e)
      );
  },

  //
  // life-cycle Methods
  //

  componentDidMount: function () {
    if (this.props.country) {
      this.props._fetchLocationsByCountry(this.props.country);
    }
  },

  componentWillUpdate: function (nextProps, nextState) {
    let prevUrl = this.computeApiUrl(this.state, {limit: 1});
    let nextUrl = this.computeApiUrl(nextState, {limit: 1});
    prevUrl !== nextUrl && this.checkSelectionCount(nextUrl);
  },

  //
  // Render Methods
  //

  renderDateSelector: function (what) {
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    let days = d3.range(1, 32, 1);
    let years = d3.range(2015, (new Date()).getFullYear() + 1, 1);
    return (
      <fieldset className='form__fieldset form__fieldset--date'>
        <legend className='form__legend'>{what === 'start' ? 'Start' : 'End'} Date</legend>

          <label htmlFor={`${what}-year`} className='form__label'>Year
          <select id={`${what}-year`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Year`]} onChange={this.onOptSelect.bind(null, `${what}Year`)}>
            <option value='--'>Year</option>
            {years.map(o => <option key={`startyear-${o}`} value={o}>{o}</option>)}
          </select>

          <label htmlFor={`${what}-month`} className='form__label'>Month
          <select id={`${what}-month`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Month`]} onChange={this.onOptSelect.bind(null, `${what}Month`)}>
            <option value='--'>Month</option>
            {months.map((o, i) => <option key={o} value={i}>{o}</option>)}
          </select>

          <label htmlFor={`${what}-day`} className='form__label'>Day
          <select id={`${what}-day`} className='form__control form__control--medium select--base-bounded' value={this.state[`${what}Day`]} onChange={this.onOptSelect.bind(null, `${what}Day`)}>
            <option value='--'>Day</option>
            {days.map(o => <option key={`startday-${o}`} value={o}>{o}</option>)}
          </select>

      </fieldset>
    );
  },

  renderLocationSelector: function () {
    let {fetching: fetchingLocations, fetched: fetchedLocations, data: {results: locations}} = this.props.locationsByCountry;

    // Mental Sanity note: we use area to designate the broader region where a sensor
    // is while the API call it city.
    // Areas and Locations belonging to the selected country.
    // Will be filtered from the this.props.locations;
    let compareAreas = [];
    let compareLocations = [];

    if (locations) {
      compareAreas = _(locations)
        .filter(o => o.country === this.state.locCountry)
        .uniqBy('city')
        .sortBy('city')
        .value();

      if (locations && this.state.locArea !== '--') {
        compareLocations = _(locations)
          .filter(o => {
            // Has to belong to the correct area and can't have been selected before.
            return o.city === this.state.locArea;
          })
          .uniqBy('location')
          .sortBy('location')
          .value();
      }
    }

    let compareAreasLabel = fetchingLocations ? 'Loading Data' : 'Select an Area';
    let compareLocationsLabel = fetchingLocations ? 'Loading Data' : 'Select a Location';

    // Disable area while the locations are not fetched.
    let disableArea = !fetchedLocations || fetchingLocations;
    // Disable locations if locations are not fetched or are not selected
    let disableLocation = disableArea || this.state.locArea === '--';

    return (
      <fieldset className='form__fieldset form__fieldset--location'>
        <legend className='form__legend'>Location Selector</legend>

          Country
          <select id='loc-country' className='form__control form__control--medium select--base-bounded' value={this.state.locCountry} onChange={this.onOptSelect.bind(null, 'locCountry')}>
            <option value='--'>Select a Country</option>
            {this.props.countries.map(o => <option key={o.code} value={o.code}>{o.name}</option>)}
          </select>

          Area
          <select id='loc-area' className='form__control form__control--medium select--base-bounded' value={this.state.locArea} onChange={this.onOptSelect.bind(null, 'locArea')}>
            <option value='--'>{compareAreasLabel}</option>
            {compareAreas.map(o => <option key={o.city} value={o.city}>{o.city}</option>)}
          </select>

          Location
          <select id='loc-location' className='form__control form__control--medium select--base-bounded' value={this.state.locLocation} onChange={this.onOptSelect.bind(null, 'locLocation')}>
            <option value='--'>{compareLocationsLabel}</option>
            {compareLocations.map(o => <option key={o.location} value={o.location}>{o.location}</option>)}
          </select>

      </fieldset>
    );
  },

  renderParameters: function () {
    return (
      <fieldset className='form__fieldset form__fieldset--parameters'>
        <legend className='form__legend'>Parameters</legend>

          {this.props.parameters.map(o => {
            let checked = this.state.parameters.indexOf(o.id) !== -1;
            let onChange = this.onParameterSelect.bind(null, o.id);
            return (

                <input type='checkbox' value={o.id} id={o.id} name='form-checkbox' onChange={onChange} checked={checked} />
                {o.name}

            );
          })}

      </fieldset>
    );
  },

  renderResultCountMessage: function () {
    let countMessage = null;
    if (this.state.selectionCount !== null && this.state.selectionCount > this.apiLimit) {
      countMessage =
The API has a limit of {formatThousands(this.apiLimit)} measurements and your query yields {formatThousands(this.state.selectionCount)}, therefore results will be limited.
;
    }

    return countMessage;
  },

  render: function () {
    const measurementsCount = this.props.countries.reduce((p, c) => p + Number(c.count), 0);
    return (
      <Modal
        id='modal-download'
        className='modal--medium'
        onCloseClick={this.props.onModalClose}
        revealed >

        <ModalHeader>

Data Download

        </ModalHeader>
        <ModalBody>

Customize the data you want to download. Currently, only the last 90 days is availble from this form. Realtime and daily archives of all {formatThousands(measurementsCount)} are also available. More Info

            <form className='form form--download'>
              {this.renderLocationSelector()}
              {this.renderDateSelector('start')}
              {this.renderDateSelector('end')}
              {this.renderParameters()}
              {this.renderResultCountMessage()}

                <button type='button' className='button button--primary-bounded' onClick={this.props.onModalClose}>Cancel</button>
                Download Selection (csv)

            </form>

        </ModalBody>
      </Modal>
    );
  }
});

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function selector (state) {
  return {
    countries: state.baseData.data.countries,
    parameters: state.baseData.data.parameters,

    locationsByCountry: state.locationsByCountry
  };
}

function dispatcher (dispatch) {
  return {
    _invalidateLocationsByCountry: (...args) => dispatch(invalidateLocationsByCountry(...args)),
    _fetchLocationsByCountry: (...args) => dispatch(fetchLocationsByCountry(...args))
  };
}

module.exports = connect(selector, dispatcher)(ModalDownload);
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   xTuple Postbooks - Business Management System Framework
// Copyright: ©2012 OpenMFG LLC, d/b/a xTuple
// ==========================================================================

/*globals XM */

sc_require('mixins/_layout_income_statement_detail');

/**
  @class

  @extends XM.Record
*/
XM.LayoutIncomeStatementDetail = XT.Record.extend(XM._LayoutIncomeStatementDetail,
  /** @scope XM.LayoutIncomeStatementDetail.prototype */ {

  // .................................................
  // CALCULATED PROPERTIES
  //

  /**
    TODO: move to XM.SubAccountType as a function
          mixin

  XM.SubAccountType.getTypes = function() {
    if(!this._subAccountTypes) {
      var qry = SC.Query.local(XM.SubAccountType),
          store = this.get('store');

      this._subAccountTypes = store.find(qry);
    }
    return this._subAccountTypes;
  }

  */

  filteredSubAccounts: function() {
    var subAccountTypes = XM.SubAccountType.getTypes(),
        record = this,
        ret;

    ret = subAccountTypes.filter(function(subAccountType) {
      var accountType = record.get('accountType');

      if(accountType) {
      return subAccountType.get('accountType') === accountType;
      } else {
        return subAccountType;
      }
    }, this);

    return ret;
  }.property('accountType').cacheable(),

  //..................................................
  // METHODS
  //

  init: function() {
    this.subAccounts = XM.SubAccountType();
  },

  updateSubAccountTypes: function() {

  },

  //..................................................
  // OBSERVERS
  //

});

XM.LayoutIncomeStatementDetail.mixin( /** @scope XM.LayoutIncomeStatementDetail */ {

/**
  @static
  @constant
  @type String
  @default A
*/
  ASSET: A,

/**
  @static
  @constant
  @type String
  @default E
*/
  EXPENSE: E,

/**
  @static
  @constant
  @type String
  @default L
*/
  LIABILITY: L,

/**
  @static
  @constant
  @type String
  @default Q
*/
  EQUITY: Q,

/**
  @static
  @constant
  @type String
  @default R
*/
  REVENUE: R

});
<commit_msg>account type constants done belong here.<commit_after>// ==========================================================================
// Project:   xTuple Postbooks - Business Management System Framework
// Copyright: ©2012 OpenMFG LLC, d/b/a xTuple
// ==========================================================================

/*globals XM */

sc_require('mixins/_layout_income_statement_detail');

/**
  @class

  @extends XM.Record
*/
XM.LayoutIncomeStatementDetail = XT.Record.extend(XM._LayoutIncomeStatementDetail,
  /** @scope XM.LayoutIncomeStatementDetail.prototype */ {

  // .................................................
  // CALCULATED PROPERTIES
  //

  /**
    TODO: move to XM.SubAccountType as a function
          mixin

  XM.SubAccountType.getTypes = function() {
    if(!this._subAccountTypes) {
      var qry = SC.Query.local(XM.SubAccountType),
          store = this.get('store');

      this._subAccountTypes = store.find(qry);
    }
    return this._subAccountTypes;
  }

  */

  filteredSubAccounts: function() {
    var subAccountTypes = XM.SubAccountType.getTypes(),
        record = this,
        ret;

    ret = subAccountTypes.filter(function(subAccountType) {
      var accountType = record.get('accountType');

      if(accountType) {
      return subAccountType.get('accountType') === accountType;
      } else {
        return subAccountType;
      }
    }, this);

    return ret;
  }.property('accountType').cacheable(),

  //..................................................
  // METHODS
  //

  init: function() {
    this.subAccounts = XM.SubAccountType();
  },

  updateSubAccountTypes: function() {

  },

  //..................................................
  // OBSERVERS
  //

});
<|endoftext|>


<commit_before>/*! JSON.minify()
 v0.1 (c) Kyle Simpson
 MIT License
 */

(function(global){
  if (typeof global.JSON == "undefined" || !global.JSON) {
    global.JSON = {};
  }

  global.JSON.minify = function(json) {

    var tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r/g,
        in_string = false,
        in_multiline_comment = false,
        in_singleline_comment = false,
        tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc
        ;

    tokenizer.lastIndex = 0;

    while (tmp = tokenizer.exec(json)) {
      lc = RegExp.leftContext;
      rc = RegExp.rightContext;
      if (!in_multiline_comment && !in_singleline_comment) {
        tmp2 = lc.substring(from);
        if (!in_string) {
          tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
        }
        new_str[ns++] = tmp2;
      }
      from = tokenizer.lastIndex;

      if (tmp[0] == "\"" && !in_multiline_comment && !in_singleline_comment) {
        tmp2 = lc.match(/(\\)*$/);
        if (!in_string || !tmp2 || (tmp2[0].length % 2) == 0) {	// start of string with ", or unescaped " character found to end string
          in_string = !in_string;
        }
        from--; // include " character in next catch
        rc = json.substring(from);
      }
      else if (tmp[0] == "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
        in_multiline_comment = true;
      }
      else if (tmp[0] == "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
        in_multiline_comment = false;
      }
      else if (tmp[0] == "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
        in_singleline_comment = true;
      }
      else if ((tmp[0] == "\n" || tmp[0] == "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
        in_singleline_comment = false;
      }
      else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
        new_str[ns++] = tmp[0];
      }
    }
    new_str[ns++] = rc;
    return new_str.join("");
  };
})(this);
<commit_msg>Add workaround for json-minify to minify json object without line feed correctly. (#287)<commit_after>/*! JSON.minify()
 v0.1 (c) Kyle Simpson
 MIT License
 */

(function(global){
  if (typeof global.JSON == "undefined" || !global.JSON) {
    global.JSON = {};
  }

  global.JSON.minify = function(json) {

    var tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r/g,
        in_string = false,
        in_multiline_comment = false,
        in_singleline_comment = false,
        tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc
        ;

    if (!tokenizer.exec(json)) {
        return json;
    }

    tokenizer.lastIndex = 0;

    while (tmp = tokenizer.exec(json)) {
      lc = RegExp.leftContext;
      rc = RegExp.rightContext;
      if (!in_multiline_comment && !in_singleline_comment) {
        tmp2 = lc.substring(from);
        if (!in_string) {
          tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
        }
        new_str[ns++] = tmp2;
      }
      from = tokenizer.lastIndex;

      if (tmp[0] == "\"" && !in_multiline_comment && !in_singleline_comment) {
        tmp2 = lc.match(/(\\)*$/);
        if (!in_string || !tmp2 || (tmp2[0].length % 2) == 0) {	// start of string with ", or unescaped " character found to end string
          in_string = !in_string;
        }
        from--; // include " character in next catch
        rc = json.substring(from);
      }
      else if (tmp[0] == "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
        in_multiline_comment = true;
      }
      else if (tmp[0] == "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
        in_multiline_comment = false;
      }
      else if (tmp[0] == "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
        in_singleline_comment = true;
      }
      else if ((tmp[0] == "\n" || tmp[0] == "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
        in_singleline_comment = false;
      }
      else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
        new_str[ns++] = tmp[0];
      }
    }
    new_str[ns++] = rc;
    return new_str.join("");
  };
})(this);
<|endoftext|>