<commit_before>'use strict';
(function(){
  angular.module('cla.controllers',[]);
  angular.module('cla.services',['ngResource']);
  angular.module('cla.services.operator', ['ngResource', 'cla.services']);
  angular.module('cla.services.provider', ['ngResource', 'cla.services']);
  angular.module('cla.filters',[]);
  angular.module('cla.directives',[]);
  angular.module('cla.states',[]);
  angular.module('cla.utils',[]);
  angular.module('cla.templates',[]);
  angular.module('cla.routes', ['cla.states']);

  var common_run;
  var common_config;

  common_run = ['$rootScope', '$state', '$stateParams', 'Timer', 'flash', 'cla.bus', 'History', '$uibModal', 'AssignProviderValidation', 'postal',
    function ($rootScope, $state, $stateParams, Timer, flash, bus, History, $uibModal, AssignProviderValidation, postal) {
      $rootScope.$state = $state;
      $rootScope.$stateParams = $stateParams;

      // handle state change errors
      $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){
        // generic state change error / redirect
        if (error.msg && !error.modal) {
          flash('error', error.msg);
        }
        // if content should appear in modal
        if (error.modal) {
          var opts = {
            templateUrl: 'invalid_modal.html',
            controller: 'InvalidCtrl',
            resolve: {
              tplVars: function () {
                return {
                  title: error.title,
                  message: error.msg,
                  errors: error.errors,
                  warnings: error.warnings
                };
              }
            }
          };
          var onConfirmSuccess = function (result) {
            if (result) {
              AssignProviderValidation.setWarned(true);
              $state.go(error.next, {caseref: error.case});
            } else {
              $state.go(error.goto, {caseref: error.case});
            }
          };
          var onDismiss = function () {
            $state.go(error.goto, {caseref: error.case});
          };

          $uibModal.open(opts).result.then(onConfirmSuccess, onDismiss);
        } else if (error.goto) {
          $state.go(error.goto, {caseref: error.case});
        }
      });
      $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        // log the previous state in the History
        ga(function(tracker) {
          var location = tracker.get('location');
          if(location.includes('search')) {
            var newURL = location.replace(/search=[^&]+/gi, 'search=redacted');
            ga('set', 'location', newURL);
          }
        });
        History.previousState = fromState;
        History.previousState.params = fromParams;
      });

      Timer.install();
      bus.install();

      // set Piwik user id as logged in username
      postal.subscribe({
        channel: 'system',
        topic: 'user.identified',
        callback: function (user) {
          try {
            ga('set', '&uid', user.username);
          } catch (err) {
            console.warn('Google analytics is not installed', err);
          }
        }
      });
    }];

  common_config = ['$resourceProvider', 'cfpLoadingBarProvider', '$analyticsProvider',
    function($resourceProvider, cfpLoadingBarProvider, $analyticsProvider) {
      $resourceProvider.defaults.stripTrailingSlashes = false;
      cfpLoadingBarProvider.includeBar = false;
      $analyticsProvider.queryKeysBlacklist(['search']);
  }];

  //
  // Operator App
  //
  angular.module('cla.states.operator',['cla.states']);
  angular.module('cla.controllers.operator',[]);
  angular.module('cla.settings.operator', []).constant('AppSettings', {
    BASE_URL: '/call_centre/',
    timerEnabled: function() {
      return true;
    },
    callScriptEnabled: true,
    callMeBackEnabled: true,
    caseListRefreshDelay: 150000,  // in ms. -1 to disable it
    statesModule: 'cla.states.operator',
    appName: 'operator',
    tabWarningEnabled: false
  });

  angular.module('cla.operatorApp',
    [
      'cla.settings.operator',
      'cla.states.operator',
      'ngSanitize',
      'ngMessages',
      'angularMoment',
      'angular-blocks',
      'xeditable',
      'ui.router',
      'cla.constants',
      'cla.controllers',
      'cla.controllers.operator',
      'cla.services',
      'cla.services.operator',
      'cla.filters',
      'cla.directives',
      'cla.utils',
      'cla.templates',
      'cla.routes',
      'ui.bootstrap',
      'ui.select2',
      'hl.sticky',
      'angular-loading-bar',
      'angulartics',
      'angulartics.google.analytics',
      'cfp.hotkeys',
      'LocalStorageModule',
      'diff-match-patch',
      'angularUtils.directives.dirPagination',
      'ngTextTruncate',
      'ngIdle'
    ])
    .config(common_config)
    .run(common_run);

  //
  // Provider App
  //
  angular.module('cla.states.provider',['cla.states']);
  angular.module('cla.controllers.provider',[]);
  angular.module('cla.settings.provider', []).constant('AppSettings', {
    BASE_URL: '/provider/',
    timerEnabled: function() {
      return false;
    },
    callScriptEnabled: false,
    callMeBackEnabled: true,
    caseListRefreshDelay: 150000,  // in ms. -1 to disable it
    statesModule: 'cla.states.provider',
    appName: 'provider',
    tabWarningEnabled: false
  });

  angular.module('cla.providerApp',
    [
      'cla.settings.provider',
      'cla.states.provider',
      'ngSanitize',
      'ngMessages',
      'angularMoment',
      'angular-blocks',
      'xeditable',
      'ui.router',
      'cla.constants',
      'cla.controllers',
      'cla.controllers.provider',
      'cla.services',
      'cla.services.provider',
      'cla.filters',
      'cla.directives',
      'cla.utils',
      'cla.templates',
      'cla.routes',
      'ui.bootstrap',
      'ui.select2',
      'hl.sticky',
      'angular-loading-bar',
      'angulartics',
      'angulartics.google.analytics',
      'cfp.hotkeys',
      'LocalStorageModule',
      'diff-match-patch',
      'angularUtils.directives.dirPagination',
      'ngTextTruncate',
      'ngIdle'
    ])
    .config(common_config)
    .run(common_run);
})();

if($("#wrapper"))
{
  var target = $("#wrapper")[0];

  var config = {
    childList: true
  };

  function callback(mutationsList, observer) {
    var pillsSectionLismt = document.getElementById('pills-section-list');
    if (pillsSectionList) {
      observer.disconnect();
      $(".Pills-pillLink").click(function(){
        var heightHeaderAndCaseBar = $(".CaseBar").height()*2*!$(".CaseBar.is-sticky").length + $('header').height();
        $([document.documentElement, document.body]).animate({
          scrollTop: ($("#pills-section-list").offset().top - heightHeaderAndCaseBar)
        }, 0);
      });
    }
  };

  var observer = new MutationObserver(callback);

  observer.observe(target, config);
}

<commit_msg>Fix variable naming and errors due to not following coding rules set<commit_after>'use strict';
(function(){
  angular.module('cla.controllers',[]);
  angular.module('cla.services',['ngResource']);
  angular.module('cla.services.operator', ['ngResource', 'cla.services']);
  angular.module('cla.services.provider', ['ngResource', 'cla.services']);
  angular.module('cla.filters',[]);
  angular.module('cla.directives',[]);
  angular.module('cla.states',[]);
  angular.module('cla.utils',[]);
  angular.module('cla.templates',[]);
  angular.module('cla.routes', ['cla.states']);

  var common_run;
  var common_config;

  common_run = ['$rootScope', '$state', '$stateParams', 'Timer', 'flash', 'cla.bus', 'History', '$uibModal', 'AssignProviderValidation', 'postal',
    function ($rootScope, $state, $stateParams, Timer, flash, bus, History, $uibModal, AssignProviderValidation, postal) {
      $rootScope.$state = $state;
      $rootScope.$stateParams = $stateParams;

      // handle state change errors
      $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){
        // generic state change error / redirect
        if (error.msg && !error.modal) {
          flash('error', error.msg);
        }
        // if content should appear in modal
        if (error.modal) {
          var opts = {
            templateUrl: 'invalid_modal.html',
            controller: 'InvalidCtrl',
            resolve: {
              tplVars: function () {
                return {
                  title: error.title,
                  message: error.msg,
                  errors: error.errors,
                  warnings: error.warnings
                };
              }
            }
          };
          var onConfirmSuccess = function (result) {
            if (result) {
              AssignProviderValidation.setWarned(true);
              $state.go(error.next, {caseref: error.case});
            } else {
              $state.go(error.goto, {caseref: error.case});
            }
          };
          var onDismiss = function () {
            $state.go(error.goto, {caseref: error.case});
          };

          $uibModal.open(opts).result.then(onConfirmSuccess, onDismiss);
        } else if (error.goto) {
          $state.go(error.goto, {caseref: error.case});
        }
      });
      $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        // log the previous state in the History
        ga(function(tracker) {
          var location = tracker.get('location');
          if(location.includes('search')) {
            var newURL = location.replace(/search=[^&]+/gi, 'search=redacted');
            ga('set', 'location', newURL);
          }
        });
        History.previousState = fromState;
        History.previousState.params = fromParams;
      });

      Timer.install();
      bus.install();

      // set Piwik user id as logged in username
      postal.subscribe({
        channel: 'system',
        topic: 'user.identified',
        callback: function (user) {
          try {
            ga('set', '&uid', user.username);
          } catch (err) {
            console.warn('Google analytics is not installed', err);
          }
        }
      });
    }];

  common_config = ['$resourceProvider', 'cfpLoadingBarProvider', '$analyticsProvider',
    function($resourceProvider, cfpLoadingBarProvider, $analyticsProvider) {
      $resourceProvider.defaults.stripTrailingSlashes = false;
      cfpLoadingBarProvider.includeBar = false;
      $analyticsProvider.queryKeysBlacklist(['search']);
  }];

  //
  // Operator App
  //
  angular.module('cla.states.operator',['cla.states']);
  angular.module('cla.controllers.operator',[]);
  angular.module('cla.settings.operator', []).constant('AppSettings', {
    BASE_URL: '/call_centre/',
    timerEnabled: function() {
      return true;
    },
    callScriptEnabled: true,
    callMeBackEnabled: true,
    caseListRefreshDelay: 150000,  // in ms. -1 to disable it
    statesModule: 'cla.states.operator',
    appName: 'operator',
    tabWarningEnabled: false
  });

  angular.module('cla.operatorApp',
    [
      'cla.settings.operator',
      'cla.states.operator',
      'ngSanitize',
      'ngMessages',
      'angularMoment',
      'angular-blocks',
      'xeditable',
      'ui.router',
      'cla.constants',
      'cla.controllers',
      'cla.controllers.operator',
      'cla.services',
      'cla.services.operator',
      'cla.filters',
      'cla.directives',
      'cla.utils',
      'cla.templates',
      'cla.routes',
      'ui.bootstrap',
      'ui.select2',
      'hl.sticky',
      'angular-loading-bar',
      'angulartics',
      'angulartics.google.analytics',
      'cfp.hotkeys',
      'LocalStorageModule',
      'diff-match-patch',
      'angularUtils.directives.dirPagination',
      'ngTextTruncate',
      'ngIdle'
    ])
    .config(common_config)
    .run(common_run);

  //
  // Provider App
  //
  angular.module('cla.states.provider',['cla.states']);
  angular.module('cla.controllers.provider',[]);
  angular.module('cla.settings.provider', []).constant('AppSettings', {
    BASE_URL: '/provider/',
    timerEnabled: function() {
      return false;
    },
    callScriptEnabled: false,
    callMeBackEnabled: true,
    caseListRefreshDelay: 150000,  // in ms. -1 to disable it
    statesModule: 'cla.states.provider',
    appName: 'provider',
    tabWarningEnabled: false
  });

  angular.module('cla.providerApp',
    [
      'cla.settings.provider',
      'cla.states.provider',
      'ngSanitize',
      'ngMessages',
      'angularMoment',
      'angular-blocks',
      'xeditable',
      'ui.router',
      'cla.constants',
      'cla.controllers',
      'cla.controllers.provider',
      'cla.services',
      'cla.services.provider',
      'cla.filters',
      'cla.directives',
      'cla.utils',
      'cla.templates',
      'cla.routes',
      'ui.bootstrap',
      'ui.select2',
      'hl.sticky',
      'angular-loading-bar',
      'angulartics',
      'angulartics.google.analytics',
      'cfp.hotkeys',
      'LocalStorageModule',
      'diff-match-patch',
      'angularUtils.directives.dirPagination',
      'ngTextTruncate',
      'ngIdle'
    ])
    .config(common_config)
    .run(common_run);
})();

if($("#wrapper"))
{
  var target = $("#wrapper")[0];

  var config = {
    childList: true
  };

  var callback = function(mutationsList, observer) {
    var pillsSectionList = document.getElementById("pills-section-list");
    if (pillsSectionList) {
      observer.disconnect();
      $(".Pills-pillLink").click(function(){
        var heightHeaderAndCaseBar = $(".CaseBar").height()*2*!$(".CaseBar.is-sticky").length + $("header").height();
        $([document.documentElement, document.body]).animate({
          scrollTop: ($("#pills-section-list").offset().top - heightHeaderAndCaseBar)
        }, 0);
      });
    }
  }

  var observer = new MutationObserver(callback);

  observer.observe(target, config);
}

<|endoftext|>


<commit_before>require('coffeescript/register')
pageHelper = require('../helpers/page_helper.coffee')

module.exports = {
  'preselects current group': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_group')
    page.ensureSidebar()
    page.click('.sidebar__list-item-button--start-thread')
    page.expectText('.discussion-form__group-select', 'Dirty Dancing Shoes')
  },

  'should display content for a public thread': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_visitor')
    page.expectText('.group-theme__name', 'Open Dirty Dancing Shoes')
    page.expectText('.thread-previews-container--unpinned', 'I carried a watermelon')
    page.expectText('.navbar__right', 'LOG IN')
    page.click('.thread-preview__link')
    page.expectText('.context-panel__heading', 'I carried a watermelon')
  },

  'should display timestamps on content': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_non_member')
    page.click('.thread-preview__link')
    page.expectElement('.timeago')
  },

  'can close and reopen a thread': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_open_and_closed_discussions')
    page.expectText('.discussions-card__header', 'Open threads')
    page.expectText('.discussions-card__header', '1 Closed')
    page.expectNoText('.thread-preview__title', 'This thread is old and closed')
    page.expectText('.thread-preview__title', 'What star sign are you?')

    page.click('.thread-preview')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--close')
    page.expectText('.flash-root__message', 'Thread closed')
    page.click('.flash-root__action')
    page.expectText('.flash-root__message', 'Thread reopened')
  },

  'doesnt store drafts after submission': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'This is a comment')
    page.click('.comment-form__submit-button')
    page.expectNoText('.comment-form textarea', 'This is a comment')
  },

  'lets you edit title, context and privacy': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')
    page.fillIn('.discussion-form__title-input', 'better title')
    page.fillIn('.discussion-form textarea', 'improved description')
    page.click('.discussion-form__private')
    page.click('.discussion-form__submit')
    page.click('.dismiss-modal-button')
    page.pause()
    page.expectText('.context-panel__heading', 'better title')
    page.expectText('.context-panel__description', 'improved description')
    page.expectText('.context-panel', 'Private')
  },

  'does not store cancelled thread info': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')

    page.fillIn('.discussion-form__title-input', 'dumb title')
    page.fillIn('.discussion-form textarea', 'rubbish description')

    page.click('.modal-cancel')
    page.pause()

    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')

    page.expectNoText('.discussion-form__title-input', 'dumb title')
    page.expectNoText('.discussion-form textarea', 'rubbish description')
  },

  'can display an unread content line': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_unread_discussion')
    page.expectElement('.thread-item--unread')
  },

  'lets you mute and unmute': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_multiple_discussions')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--mute')
    page.click('.confirm-modal__submit')
    page.expectText('.flash-root__message', 'Thread muted')
    page.pause()

    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--unmute')
    page.expectText('.flash-root__message', 'Thread unmuted')
  },

  'lets you move a thread': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_multiple_discussions')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--move')
    page.click('.move-thread-form__group-dropdown')
    page.click('.md-select-menu-container.md-active md-option')
    page.click('.move-thread-form__submit')
    page.expectText('.flash-root__message', 'Thread has been moved to Point Break')
    page.expectText('.thread-item__title', 'Patrick Swayze moved the thread from Dirty Dancing Shoes')
    page.expectText('.group-theme__name--compact','Point Break')
  },

  'marks a discussion as seen': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_for_jennifer')
    page.ensureSidebar()
    page.expectText('.md-sidenav-left', 'Unread threads')
  },

  'lets coordinators and thread authors delete threads': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--delete button')
    page.click('.confirm-modal__submit')

    page.expectText('.flash-root__message', 'Thread deleted')
    page.expectText('.group-theme__name', 'Dirty Dancing Shoes')
    page.expectNoText('.discussions-card', 'What star sign are you?')
  },

  'can pin from the discussion page': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--pin button')

    page.expectText('.confirm-modal', 'Pinned threads always appear')
    page.click('.confirm-modal__submit')

    page.expectText('.flash-root__message', 'Thread pinned')
    page.expectElement('.context-panel__status .mdi-pin')
  },

  'lets you change thread volume': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--email-settings')
    page.click('#volume-loud')
    page.click('.change-volume-form__submit')
    page.expectText('.flash-root__message', 'You will be emailed activity in this thread.')
  },

  'lets you change the volume for all threads in the group': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--email-settings')
    page.click('#volume-loud')
    page.click('.change-volume-form__apply-to-all')
    page.click('.change-volume-form__submit')
    page.expectText('.flash-root__message', 'You will be emailed all activity in this group.')
  },

  'allows logged in users to join a group and comment': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_non_member')
    page.click('.thread-preview__link')
    page.click('.join-group-button__join-group')
    page.expectText('.flash-root__message', 'You are now a member of Open Dirty Dancing Shoes')

    page.fillIn('.comment-form textarea', 'I am new!')
    page.click('.comment-form__submit-button')
    page.expectText('.flash-root__message', 'Comment added')
  },

  'allows guests to comment and view thread in dashboard': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_as_guest')
    page.fillIn('.comment-form textarea', 'I am a guest!')
    page.click('.comment-form__submit-button')
    page.expectText('.flash-root__message', 'Comment added')

    page.ensureSidebar()
    page.click('.sidebar__list-item-button--recent')
    page.expectText('.thread-preview__text-container', 'Dirty Dancing Shoes')
  },

  'allows logged in users to request to join a closed group': (test) => {
    page = pageHelper(test)

    page.loadPath('view_closed_group_as_non_member')
    page.click('.thread-preview__link')
    page.click('.join-group-button__ask-to-join-group')
    page.click('.membership-request-form__submit-btn')
    page.expectText('.flash-root__message', 'You have requested membership to Closed Dirty Dancing Shoes')
  },

  'adds a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'hi this is my comment')
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment', 'hi this is my comment', 8000)
  },

  'can add emojis': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'Here is a dragon!')
    page.click('.comment-form .emoji-picker__toggle')
    page.fillIn('.md-open-menu-container.md-active .emoji-picker__search input', 'dragon_face')
    page.pause(250)
    page.click('.md-open-menu-container.md-active .emoji-picker__link')
    page.pause(250)
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment .thread-item__body','Here is a dragon!')
    page.expectElement('.new-comment .thread-item__body img')
  },

  'replies to a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right heerrr')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--reply_to_comment')
    page.fillIn('.comment-form textarea', 'hi this is my comment')
    page.click('.comment-form__submit-button')
    page.expectText('.thread-item--indent .new-comment__body', 'hi this is my comment')
    page.expectText('.flash-root__message', 'Patrick Swayze notified of reply')
  },

  'can react to a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.expectNoElement('.reaction')
    page.click('.action-dock__button--react')
    page.click('.md-active .emoji-picker__link:first-child')
    page.pause()
    page.click('.action-dock__button--react')
    page.expectElement('.reaction__emoji', 10000)
  },

  'mentions a user': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', '@jennifer')
    page.expectText('.mentio-menu', 'Jennifer Grey')
    page.click('.mentio-menu md-menu-item')
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment', '@jennifergrey')
  },

  'edits a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right hur')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--edit_comment')
    page.fillIn('.edit-comment-form textarea', 'edited comment right thur')
    page.click('.edit-comment-form .comment-form__submit-button')
    page.expectText('.new-comment', 'edited comment right thur')
  },

  'lets you view comment revision history': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'Comment!')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--edit_comment', 8000)
    page.fillIn('.edit-comment-form textarea', 'Revised comment!')
    page.click( '.edit-comment-form .comment-form__submit-button')
    page.pause()
    page.click('.action-dock__button--show_history')
    page.expectText('.revision-history-modal__body', 'Revised comment!')
    page.expectText('.revision-history-modal__body', 'Comment!')
  },

  'deletes a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right hur')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--delete_comment')
    page.click('.confirm-modal__submit')
    page.expectNoText('.activity-card', 'original comment right thur')
  },

  'invites_a_user_to_a_discussion': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_mailer_new_discussion_email')
    page.click('.thread-mailer__subject a')
    page.expectText('.context-panel__heading', 'go to the moon')
    page.expectText('.context-panel__description', 'A description for this discussion')
    page.fillIn('.comment-form textarea', 'Hello world!')
    page.click('.comment-form__submit-button')
    page.expectText('.thread-item__title', 'Jennifer Grey', 10000)
    page.expectText('.thread-item__body', 'Hello world!')
    page.expectText('.group-theme__name--compact', 'Girdy Dancing Shoes')
    page.ensureSidebar()
    page.expectNoElement('.sidebar__list-item-button--group')
  },

  'invites_an_email_to_a_discussion': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_mailer_invitation_created_email')
    page.click('.thread-mailer__subject a')
    page.expectValue('.auth-email-form__email input', 'jen@example.com')
    page.click('.auth-email-form__submit')
    page.fillIn('.auth-signin-form__name', 'Jennifer')
    page.click('.auth-signin-form__submit')
    page.expectText('.context-panel__heading', 'go to the moon', 10000)
    page.expectText('.context-panel__description', 'A description for this discussion')
    page.expectText('.new-comment__body', 'body of the comment')
  },

  'sends_missed_yesterday': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_thread_missed_yesterday')
    page.expectText('.activity-feed', 'body of the comment')
    page.expectText('.activity-feed', 'Patrick Swayze closed the discussion')
  }
}
<commit_msg>Pause on mention<commit_after>require('coffeescript/register')
pageHelper = require('../helpers/page_helper.coffee')

module.exports = {
  'preselects current group': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_group')
    page.ensureSidebar()
    page.click('.sidebar__list-item-button--start-thread')
    page.expectText('.discussion-form__group-select', 'Dirty Dancing Shoes')
  },

  'should display content for a public thread': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_visitor')
    page.expectText('.group-theme__name', 'Open Dirty Dancing Shoes')
    page.expectText('.thread-previews-container--unpinned', 'I carried a watermelon')
    page.expectText('.navbar__right', 'LOG IN')
    page.click('.thread-preview__link')
    page.expectText('.context-panel__heading', 'I carried a watermelon')
  },

  'should display timestamps on content': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_non_member')
    page.click('.thread-preview__link')
    page.expectElement('.timeago')
  },

  'can close and reopen a thread': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_open_and_closed_discussions')
    page.expectText('.discussions-card__header', 'Open threads')
    page.expectText('.discussions-card__header', '1 Closed')
    page.expectNoText('.thread-preview__title', 'This thread is old and closed')
    page.expectText('.thread-preview__title', 'What star sign are you?')

    page.click('.thread-preview')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--close')
    page.expectText('.flash-root__message', 'Thread closed')
    page.click('.flash-root__action')
    page.expectText('.flash-root__message', 'Thread reopened')
  },

  'doesnt store drafts after submission': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'This is a comment')
    page.click('.comment-form__submit-button')
    page.expectNoText('.comment-form textarea', 'This is a comment')
  },

  'lets you edit title, context and privacy': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')
    page.fillIn('.discussion-form__title-input', 'better title')
    page.fillIn('.discussion-form textarea', 'improved description')
    page.click('.discussion-form__private')
    page.click('.discussion-form__submit')
    page.click('.dismiss-modal-button')
    page.pause()
    page.expectText('.context-panel__heading', 'better title')
    page.expectText('.context-panel__description', 'improved description')
    page.expectText('.context-panel', 'Private')
  },

  'does not store cancelled thread info': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')

    page.fillIn('.discussion-form__title-input', 'dumb title')
    page.fillIn('.discussion-form textarea', 'rubbish description')

    page.click('.modal-cancel')
    page.pause()

    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--edit')

    page.expectNoText('.discussion-form__title-input', 'dumb title')
    page.expectNoText('.discussion-form textarea', 'rubbish description')
  },

  'can display an unread content line': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_unread_discussion')
    page.expectElement('.thread-item--unread')
  },

  'lets you mute and unmute': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_multiple_discussions')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--mute')
    page.click('.confirm-modal__submit')
    page.expectText('.flash-root__message', 'Thread muted')
    page.pause()

    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--unmute')
    page.expectText('.flash-root__message', 'Thread unmuted')
  },

  'lets you move a thread': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_multiple_discussions')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--move')
    page.click('.move-thread-form__group-dropdown')
    page.click('.md-select-menu-container.md-active md-option')
    page.click('.move-thread-form__submit')
    page.expectText('.flash-root__message', 'Thread has been moved to Point Break')
    page.expectText('.thread-item__title', 'Patrick Swayze moved the thread from Dirty Dancing Shoes')
    page.expectText('.group-theme__name--compact','Point Break')
  },

  'marks a discussion as seen': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_for_jennifer')
    page.ensureSidebar()
    page.expectText('.md-sidenav-left', 'Unread threads')
  },

  'lets coordinators and thread authors delete threads': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--delete button')
    page.click('.confirm-modal__submit')

    page.expectText('.flash-root__message', 'Thread deleted')
    page.expectText('.group-theme__name', 'Dirty Dancing Shoes')
    page.expectNoText('.discussions-card', 'What star sign are you?')
  },

  'can pin from the discussion page': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--pin button')

    page.expectText('.confirm-modal', 'Pinned threads always appear')
    page.click('.confirm-modal__submit')

    page.expectText('.flash-root__message', 'Thread pinned')
    page.expectElement('.context-panel__status .mdi-pin')
  },

  'lets you change thread volume': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--email-settings')
    page.click('#volume-loud')
    page.click('.change-volume-form__submit')
    page.expectText('.flash-root__message', 'You will be emailed activity in this thread.')
  },

  'lets you change the volume for all threads in the group': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.click('.context-panel-dropdown__button')
    page.click('.context-panel-dropdown__option--email-settings')
    page.click('#volume-loud')
    page.click('.change-volume-form__apply-to-all')
    page.click('.change-volume-form__submit')
    page.expectText('.flash-root__message', 'You will be emailed all activity in this group.')
  },

  'allows logged in users to join a group and comment': (test) => {
    page = pageHelper(test)

    page.loadPath('view_open_group_as_non_member')
    page.click('.thread-preview__link')
    page.click('.join-group-button__join-group')
    page.expectText('.flash-root__message', 'You are now a member of Open Dirty Dancing Shoes')

    page.fillIn('.comment-form textarea', 'I am new!')
    page.click('.comment-form__submit-button')
    page.expectText('.flash-root__message', 'Comment added')
  },

  'allows guests to comment and view thread in dashboard': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_as_guest')
    page.fillIn('.comment-form textarea', 'I am a guest!')
    page.click('.comment-form__submit-button')
    page.expectText('.flash-root__message', 'Comment added')

    page.ensureSidebar()
    page.click('.sidebar__list-item-button--recent')
    page.expectText('.thread-preview__text-container', 'Dirty Dancing Shoes')
  },

  'allows logged in users to request to join a closed group': (test) => {
    page = pageHelper(test)

    page.loadPath('view_closed_group_as_non_member')
    page.click('.thread-preview__link')
    page.click('.join-group-button__ask-to-join-group')
    page.click('.membership-request-form__submit-btn')
    page.expectText('.flash-root__message', 'You have requested membership to Closed Dirty Dancing Shoes')
  },

  'adds a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'hi this is my comment')
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment', 'hi this is my comment', 8000)
  },

  'can add emojis': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'Here is a dragon!')
    page.click('.comment-form .emoji-picker__toggle')
    page.fillIn('.md-open-menu-container.md-active .emoji-picker__search input', 'dragon_face')
    page.pause(250)
    page.click('.md-open-menu-container.md-active .emoji-picker__link')
    page.pause(250)
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment .thread-item__body','Here is a dragon!')
    page.expectElement('.new-comment .thread-item__body img')
  },

  'replies to a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right heerrr')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--reply_to_comment')
    page.fillIn('.comment-form textarea', 'hi this is my comment')
    page.click('.comment-form__submit-button')
    page.expectText('.thread-item--indent .new-comment__body', 'hi this is my comment')
    page.expectText('.flash-root__message', 'Patrick Swayze notified of reply')
  },

  'can react to a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.expectNoElement('.reaction')
    page.click('.action-dock__button--react')
    page.click('.md-active .emoji-picker__link:first-child')
    page.pause()
    page.click('.action-dock__button--react')
    page.expectElement('.reaction__emoji', 10000)
  },

  'mentions a user': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', '@jennifer')
    page.pause()
    page.expectText('.mentio-menu', 'Jennifer Grey')
    page.click('.mentio-menu md-menu-item')
    page.click('.comment-form__submit-button')
    page.expectText('.new-comment', '@jennifergrey')
  },

  'edits a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right hur')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--edit_comment')
    page.fillIn('.edit-comment-form textarea', 'edited comment right thur')
    page.click('.edit-comment-form .comment-form__submit-button')
    page.expectText('.new-comment', 'edited comment right thur')
  },

  'lets you view comment revision history': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'Comment!')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--edit_comment', 8000)
    page.fillIn('.edit-comment-form textarea', 'Revised comment!')
    page.click( '.edit-comment-form .comment-form__submit-button')
    page.pause()
    page.click('.action-dock__button--show_history')
    page.expectText('.revision-history-modal__body', 'Revised comment!')
    page.expectText('.revision-history-modal__body', 'Comment!')
  },

  'deletes a comment': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion')
    page.fillIn('.comment-form textarea', 'original comment right hur')
    page.click('.comment-form__submit-button')
    page.click('.action-dock__button--delete_comment')
    page.click('.confirm-modal__submit')
    page.expectNoText('.activity-card', 'original comment right thur')
  },

  'invites_a_user_to_a_discussion': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_mailer_new_discussion_email')
    page.click('.thread-mailer__subject a')
    page.expectText('.context-panel__heading', 'go to the moon')
    page.expectText('.context-panel__description', 'A description for this discussion')
    page.fillIn('.comment-form textarea', 'Hello world!')
    page.click('.comment-form__submit-button')
    page.expectText('.thread-item__title', 'Jennifer Grey', 10000)
    page.expectText('.thread-item__body', 'Hello world!')
    page.expectText('.group-theme__name--compact', 'Girdy Dancing Shoes')
    page.ensureSidebar()
    page.expectNoElement('.sidebar__list-item-button--group')
  },

  'invites_an_email_to_a_discussion': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_discussion_mailer_invitation_created_email')
    page.click('.thread-mailer__subject a')
    page.expectValue('.auth-email-form__email input', 'jen@example.com')
    page.click('.auth-email-form__submit')
    page.fillIn('.auth-signin-form__name', 'Jennifer')
    page.click('.auth-signin-form__submit')
    page.expectText('.context-panel__heading', 'go to the moon', 10000)
    page.expectText('.context-panel__description', 'A description for this discussion')
    page.expectText('.new-comment__body', 'body of the comment')
  },

  'sends_missed_yesterday': (test) => {
    page = pageHelper(test)

    page.loadPath('setup_thread_missed_yesterday')
    page.expectText('.activity-feed', 'body of the comment')
    page.expectText('.activity-feed', 'Patrick Swayze closed the discussion')
  }
}
<|endoftext|>


<commit_before>/**
 *
 * NodeCollectionList
 *
 */

import React, { PropTypes } from 'react';
import { connect } from 'react-redux'
import enhanceWithClickOutside from 'react-onclickoutside'
import Chip from 'material-ui/Chip';

import NodeSearch from '../../containers/NodeSearch'
import { primaryColor, secondaryColor } from '../../containers/App/muitheme'

import {
    addNodeToCollection,
    removeNodeFromCollection,
} from '../../actions/collection'

import {
    createNode
} from '../../actions/node'

import './styles.scss';

const styles = {
    chip: {
        margin: 4,
    },
    wrapper: {
        display: 'flex',
        flexWrap: 'wrap',
    },
};

class NodeCollectionList extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            searchToggle: false,
            open: false,
            defaultValues: {
                name: "",
                desription: "",
            }
        }

        this.toggleSearch = this.toggleSearch.bind(this)
        this.addNodeToCollection = this.addNodeToCollection.bind(this)
        this.createAndAddNodeToCollection = this.createAndAddNodeToCollection.bind(this)
        this.removeNodeFromCollection = this.removeNodeFromCollection.bind(this)
        this.handleClickOutside = this.handleClickOutside.bind(this)
    }

    toggleSearch() {
        this.setState({ searchToggle: true })
    }

    addNodeToCollection(ESCollection) {
        // _id here, because this is an elastic search node representation
        this.props.addNodeToCollection(ESCollection._id, this.props.node.id)

        this.setState({ searchToggle: false })
    }

    createAndAddNodeToCollection(label) {
        // add a new node to the graph
        if (!label) {
            return;
        }

        console.log("creating!")

        this.props.createNode({ name: label })
            .then(action => action.response.result)
            .then(id => this.props.addNodeToCollection(
                id,
                this.props.node.id,
            ))

        this.setState({ searchToggle: false })
    }

    removeNodeFromCollection(collectionId) {
        this.props.removeNodeFromCollection(collectionId, this.props.node.id)
    }

    handleClickOutside(event) {
        if (this.state.open) {
            return;
        }
        this.setState({ searchToggle: false })
    }

    render() {
        const { collections } = this.props

        const handlers = {
            'addCollection': this.toggleSearch
        }

        return (

                { _.map(collections, col => (
                    <NodeCollectionItem
                        key={col.id}
                        id={col.id}
                        nodeId={this.props.node.id}
                        name={col.name}
                        onDelete={this.removeNodeFromCollection}
                    />)
                )}

                {
                    !this.state.searchToggle ?
                        <Chip
                            backgroundColor={secondaryColor}
                            onClick={this.toggleSearch}
                            style={styles.chip}
                        >Add Parent
                        </Chip>
                        :

                            <NodeSearch
                                floatBelow={ true }
                                floatingLabel={"Search for a collection..."}
                                onSearchClick={this.addNodeToCollection}
                                onClick={this.createAndAddNodeToCollection}
                            />

                }

        );
    }
}

import { withRouter } from 'react-router-dom'

// TODO: change to Link components - 2016-07-12
export const NodeCollectionItem = withRouter((props) => {
    // {/*
    //     <a href={`/app/collections/${id}`} className='collection-item'>{ name }
    // */}
    return (
        <Chip
            onRequestDelete={() => props.onDelete(props.id)}
            onClick={() => props.history.push(`/app/collections/${props.id}/nodes/${props.nodeId}`)}
            style={styles.chip}
        >
            { props.name }
        </Chip>
    )

})

export default connect(null, {
    addNodeToCollection,
    removeNodeFromCollection,
    createNode,
})(enhanceWithClickOutside(NodeCollectionList))
<commit_msg>navigate to collection when clicking on nodeCollectionItem without focussing<commit_after>/**
 *
 * NodeCollectionList
 *
 */

import React, { PropTypes } from 'react';
import { connect } from 'react-redux'
import enhanceWithClickOutside from 'react-onclickoutside'
import Chip from 'material-ui/Chip';

import NodeSearch from '../../containers/NodeSearch'
import { primaryColor, secondaryColor } from '../../containers/App/muitheme'

import {
    addNodeToCollection,
    removeNodeFromCollection,
} from '../../actions/collection'

import {
    createNode
} from '../../actions/node'

import './styles.scss';

const styles = {
    chip: {
        margin: 4,
    },
    wrapper: {
        display: 'flex',
        flexWrap: 'wrap',
    },
};

class NodeCollectionList extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            searchToggle: false,
            open: false,
            defaultValues: {
                name: "",
                desription: "",
            }
        }

        this.toggleSearch = this.toggleSearch.bind(this)
        this.addNodeToCollection = this.addNodeToCollection.bind(this)
        this.createAndAddNodeToCollection = this.createAndAddNodeToCollection.bind(this)
        this.removeNodeFromCollection = this.removeNodeFromCollection.bind(this)
        this.handleClickOutside = this.handleClickOutside.bind(this)
    }

    toggleSearch() {
        this.setState({ searchToggle: true })
    }

    addNodeToCollection(ESCollection) {
        // _id here, because this is an elastic search node representation
        this.props.addNodeToCollection(ESCollection._id, this.props.node.id)

        this.setState({ searchToggle: false })
    }

    createAndAddNodeToCollection(label) {
        // add a new node to the graph
        if (!label) {
            return;
        }

        this.props.createNode({ name: label })
            .then(action => action.response.result)
            .then(id => this.props.addNodeToCollection(
                id,
                this.props.node.id,
            ))

        this.setState({ searchToggle: false })
    }

    removeNodeFromCollection(collectionId) {
        this.props.removeNodeFromCollection(collectionId, this.props.node.id)
    }

    handleClickOutside(event) {
        if (this.state.open) {
            return;
        }
        this.setState({ searchToggle: false })
    }

    render() {
        const { collections } = this.props

        const handlers = {
            'addCollection': this.toggleSearch
        }

        return (

                { _.map(collections, col => (
                    <NodeCollectionItem
                        key={col.id}
                        id={col.id}
                        nodeId={this.props.node.id}
                        name={col.name}
                        onDelete={this.removeNodeFromCollection}
                    />)
                )}

                {
                    !this.state.searchToggle ?
                        <Chip
                            backgroundColor={secondaryColor}
                            onClick={this.toggleSearch}
                            style={styles.chip}
                        >Add Parent
                        </Chip>
                        :

                            <NodeSearch
                                floatBelow={ true }
                                floatingLabel={"Search for a collection..."}
                                onSearchClick={this.addNodeToCollection}
                                onClick={this.createAndAddNodeToCollection}
                            />

                }

        );
    }
}

import { withRouter } from 'react-router-dom'

// TODO: change to Link components - 2016-07-12
export const NodeCollectionItem = withRouter((props) => {
    // {/*
    //     <a href={`/app/collections/${id}`} className='collection-item'>{ name }
    // */}
    return (
        <Chip
            onRequestDelete={() => props.onDelete(props.id)}
            onClick={() => props.history.push(`/app/collections/${props.id}/nodes`)}
            style={styles.chip}
        >
            { props.name }
        </Chip>
    )

})

export default connect(null, {
    addNodeToCollection,
    removeNodeFromCollection,
    createNode,
})(enhanceWithClickOutside(NodeCollectionList))
<|endoftext|>


<commit_before>class NegociacaoController {

    constructor() {

        let $ = document.querySelector.bind(document);
        this._inputData = $('#data');
        this._inputQuantidade = $('#quantidade');
        this._inputValor = $('#valor');

        this._listaNegociacoes = new Bind(
            new ListaNegociacoes(),
            new NegociacoesView($('#negociacoesView')),
            'adiciona', 'esvazia');

        this._mensagem = new Bind(
            new Mensagem(),
            new MensagemView($('#mensagemView')),
            'texto');

    }

    apaga() {
        this._listaNegociacoes.esvazia();
        this._mensagem.texto = 'Negociações apagadas com sucesso';
    }

    adiciona(event) {

        event.preventDefault();
        this._listaNegociacoes.adiciona(this._criaNegociacao());
        this._limpaFormulario();
        this._mensagem.texto = 'Cadastrado com sucesso';

    }

    importaNegociacoes() {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'negociacoes/semana');
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    JSON.parse(xhr.responseText)
                    .map( o =>
                        new Negociacao(new Date(o.data), o.quantidade, o.valor))
                    .forEach(negociacao => this._listaNegociacoes.adiciona(negociacao));
                    this._mensagem.texto = 'Negociações importadas com sucesso';

                } else {
                    console.log(`Erro: ${xhr.responseText}`);
                }
            }
        };
        xhr.send();
    }

    _criaNegociacao() {

        return new Negociacao(
            DateHelper.textoParaData(this._inputData.value),
            this._inputQuantidade.value,
            this._inputValor.value);
    }

    _limpaFormulario() {

        this._inputData.value = '';
        this._inputQuantidade.value = 1;
        this._inputValor.value = 0.0;
        this._inputData.focus();
    }
}<commit_msg>ajax com msg<commit_after>class NegociacaoController {

    constructor() {

        let $ = document.querySelector.bind(document);
        this._inputData = $('#data');
        this._inputQuantidade = $('#quantidade');
        this._inputValor = $('#valor');

        this._listaNegociacoes = new Bind(
            new ListaNegociacoes(),
            new NegociacoesView($('#negociacoesView')),
            'adiciona', 'esvazia');

        this._mensagem = new Bind(
            new Mensagem(),
            new MensagemView($('#mensagemView')),
            'texto');

    }

    apaga() {
        this._listaNegociacoes.esvazia();
        this._mensagem.texto = 'Negociações apagadas com sucesso';
    }

    adiciona(event) {

        event.preventDefault();
        this._listaNegociacoes.adiciona(this._criaNegociacao());
        this._limpaFormulario();
        this._mensagem.texto = 'Cadastrado com sucesso';

    }

    importaNegociacoes() {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'negociacoes/semana');
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    JSON.parse(xhr.responseText)
                    .map( o =>
                        new Negociacao(new Date(o.data), o.quantidade, o.valor))
                    .forEach(negociacao => this._listaNegociacoes.adiciona(negociacao));
                    this._mensagem.texto = 'Negociações importadas com sucesso';

                } else {
                    console.log(`Erro: ${xhr.responseText}`);
                    this._mensagem.texto = 'Não foi possível importar as negociações';
                }
            }
        };
        xhr.send();
    }

    _criaNegociacao() {

        return new Negociacao(
            DateHelper.textoParaData(this._inputData.value),
            this._inputQuantidade.value,
            this._inputValor.value);
    }

    _limpaFormulario() {

        this._inputData.value = '';
        this._inputQuantidade.value = 1;
        this._inputValor.value = 0.0;
        this._inputData.focus();
    }
}<|endoftext|>


<commit_before>var React = require('react-native');
var ClassStandbyView = require('./classStandbyView.js');
require('./../../utils/userAgent');
var io =require('socket.io-client/socket.io');
var NavBar = require('./../shared/navbar');
var Button = require('./../shared/button');
var env = require('./../../utils/environment');
var api = require('./../../utils/api');

var server = env.server + ':' + env.port;

var {
  View,
  Text,
  StyleSheet,
  TouchableHighlight,
  Modal,
  Dimensions,
  TextInput,
  TouchableOpacity,
  Navigator,
  ScrollView,
} = React;

class JoinClassView extends React.Component {
  constructor(props){
    super(props);
    var {height, width} = Dimensions.get('window');
    this.state = {
      enrolledClasses: [],
      user: this.props.route.user,
      secretCode: '',
      height: height,
      width: width,
      modalVisible: false
    }
  }

  componentWillMount() {
    var userId = this.state.user.uid;
    var that =  this;
    api.getStudentClasses(userId)
    .then(function(resp){
      if(resp.status === 500) {
        console.error('Error for getting class data')
      } else if(resp.status === 200) {
        var classes = JSON.parse(resp._bodyInit);
        that.setState({
          enrolledClasses: classes,
        })
      }
    })
    .catch(function(err){
      console.error(err);
    })
  }

  selectedClass(cls) {
    //perhaps pass class as part of url to socket
    this.socket = io(server, {jsonp: false});
    this.socket.emit('studentConnect', {user: this.state.user, classId: cls.class.id});

    this.props.navigator.push({
      component: ClassStandbyView,
      class: cls,
      user: this.state.user,
      socket: this.socket,
      sceneConfig: {
        ...Navigator.SceneConfigs.HorizontalSwipeJump,
        gestures: {}
      }
    });
  }

  selectQuickClass() {
    this.setState({
      modalVisible: true
    })
  }

  handleCodeChange(event) {
    this.setState({
      secretCode: event.nativeEvent.text
    });
  }

  handleModalSubmit() {
    var classCode = 'qc' + this.state.secretCode;
    this.setState({
      modalVisible: false
    });
    //assuming code is valid:
    this.socket = io(server, {jsonp: false});
    this.socket.emit('studentQuickClassConnect', {user: this.state.user, classId: classCode});
    this.setState({
      secretCode: ''
    });
    var classObj = {
      id: classCode,
      name: 'Quick Class: ' + classCode
    };
    this.props.navigator.push({
      component: ClassStandbyView,
      class: classObj,
      user: this.state.user,
      socket: this.socket,
      sceneConfig: {
        ...Navigator.SceneConfigs.HorizontalSwipeJump,
        gestures: {}
      }
    });
  }

  handleModalCancel() {
    this.setState({
      secretCode: '',
      modalVisible: false
    });
  }

  eachClasses(classes){
    return classes.map((cls, index) => {
      return (
        <View key={index}>
          <Button onPress={this.selectedClass.bind(this, cls)} text={cls.class.name} />
        </View>
      )
    })
  }

  render() {
    return (
      <View style={{flex:1}}>
        <NavBar navi={this.props.navigator}>Enrollled Classes</NavBar>
        <View style={styles.container}>
          <View>
            <ScrollView>
              <View style={styles.container}>
                <Button onPress={this.selectQuickClass.bind(this)} text={'Join Quick Class'}/>
                {this.eachClasses(this.state.enrolledClasses)}
              </View>
            </ScrollView>
          </View>
        </View>

        <Modal visible={this.state.modalVisible} transparent={true} animated={true}>
          <View style={styles.modal}>
            <View style={{height:this.state.height * 0.6, width:this.state.width * 0.8}}>
              <View style={styles.modalBox}>
                <Text> Enter the secret code from your lecturer: </Text>
                <TextInput
                  autoCapitalize={'none'}
                  autoCorrect={false}
                  maxLength={4}
                  style={styles.userInput}
                  keyboardType='numeric'
                  value={this.state.secretCode}
                  returnKeyType={'done'}
                  onChange={this.handleCodeChange.bind(this)}
                  onSubmitEditing={this.handleModalSubmit.bind(this)}
                />
                <TouchableHighlight onPress={this.handleModalSubmit.bind(this)}>
                  <Text> Enter </Text>
                </TouchableHighlight>
                <TouchableHighlight onPress={this.handleModalCancel.bind(this)}>
                  <Text> Cancel </Text>
                </TouchableHighlight>
              </View>
            </View>
          </View>
        </Modal>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
  },
  modal: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.8)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalBox: {
    flex: 1,
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white'
  },
  userInput: {
    height: 50,
    padding: 4,
    fontSize: 18,
    borderWidth: 1,
    borderRadius: 4,
  },
})
module.exports = JoinClassView;
<commit_msg>change join class view modal size and button amd input box size<commit_after>var React = require('react-native');
var ClassStandbyView = require('./classStandbyView.js');
require('./../../utils/userAgent');
var io =require('socket.io-client/socket.io');
var NavBar = require('./../shared/navbar');
var Button = require('./../shared/button');
var env = require('./../../utils/environment');
var api = require('./../../utils/api');

var server = env.server + ':' + env.port;

var {
  View,
  Text,
  StyleSheet,
  TouchableHighlight,
  Modal,
  Dimensions,
  TextInput,
  TouchableOpacity,
  Navigator,
  ScrollView,
} = React;

class JoinClassView extends React.Component {
  constructor(props){
    super(props);
    var {height, width} = Dimensions.get('window');
    this.state = {
      enrolledClasses: [],
      user: this.props.route.user,
      secretCode: '',
      height: height,
      width: width,
      modalVisible: false
    }
  }

  componentWillMount() {
    var userId = this.state.user.uid;
    var that =  this;
    api.getStudentClasses(userId)
    .then(function(resp){
      if(resp.status === 500) {
        console.error('Error for getting class data')
      } else if(resp.status === 200) {
        var classes = JSON.parse(resp._bodyInit);
        that.setState({
          enrolledClasses: classes,
        })
      }
    })
    .catch(function(err){
      console.error(err);
    })
  }

  selectedClass(cls) {
    //perhaps pass class as part of url to socket
    this.socket = io(server, {jsonp: false});
    this.socket.emit('studentConnect', {user: this.state.user, classId: cls.class.id});

    this.props.navigator.push({
      component: ClassStandbyView,
      class: cls,
      user: this.state.user,
      socket: this.socket,
      sceneConfig: {
        ...Navigator.SceneConfigs.HorizontalSwipeJump,
        gestures: {}
      }
    });
  }

  selectQuickClass() {
    this.setState({
      modalVisible: true
    })
  }

  handleCodeChange(event) {
    this.setState({
      secretCode: event.nativeEvent.text
    });
  }

  handleModalSubmit() {
    var classCode = 'qc' + this.state.secretCode;
    this.setState({
      modalVisible: false
    });
    //assuming code is valid:
    this.socket = io(server, {jsonp: false});
    this.socket.emit('studentQuickClassConnect', {user: this.state.user, classId: classCode});
    this.setState({
      secretCode: ''
    });
    var classObj = {
      id: classCode,
      name: 'Quick Class: ' + classCode
    };
    this.props.navigator.push({
      component: ClassStandbyView,
      class: classObj,
      user: this.state.user,
      socket: this.socket,
      sceneConfig: {
        ...Navigator.SceneConfigs.HorizontalSwipeJump,
        gestures: {}
      }
    });
  }

  handleModalCancel() {
    this.setState({
      secretCode: '',
      modalVisible: false
    });
  }

  eachClasses(classes){
    return classes.map((cls, index) => {
      return (
        <View key={index}>
          <Button onPress={this.selectedClass.bind(this, cls)} text={cls.class.name} />
        </View>
      )
    })
  }

  render() {
    return (
      <View style={{flex:1}}>
        <NavBar navi={this.props.navigator}>Enrollled Classes</NavBar>
        <View style={styles.container}>
          <View>
            <ScrollView>
              <View style={styles.container}>
                <Button onPress={this.selectQuickClass.bind(this)} text={'Join Quick Class'}/>
                {this.eachClasses(this.state.enrolledClasses)}
              </View>
            </ScrollView>
          </View>
        </View>

        <Modal visible={this.state.modalVisible} transparent={true} animated={false}>
          <View style={styles.modal}>
            <View style={{height:this.state.height * 0.7, width:this.state.width * 0.9}}>
              <View style={styles.modalBox}>
                <Text style={styles.modalTextSize}> Enter the secret code </Text>
                <View>
                  <TextInput
                    autoCapitalize={'none'}
                    autoCorrect={false}
                    maxLength={4}
                    style={styles.userInput}
                    keyboardType='numeric'
                    value={this.state.secretCode}
                    returnKeyType={'done'}
                    onChange={this.handleCodeChange.bind(this)}
                    onSubmitEditing={this.handleModalSubmit.bind(this)}
                  />
                </View>
              </View>
              <Button onPress={this.handleModalSubmit.bind(this)} text={'Enter'} />
              <Button onPress={this.handleModalCancel.bind(this)} text={'Cancel'} />
            </View>
          </View>
        </Modal>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
  },
  modal: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.8)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalBox: {
    flex: 1,
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#e3f2fd'
  },
  modalTextSize: {
    fontSize: 25,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  userInput: {
    backgroundColor: '#e1f5fe',
    width: 300,
    height: 45,
    padding: 4,
    fontSize: 18,
    borderWidth: 1,
    borderRadius: 4,
  },
})
module.exports = JoinClassView;
<|endoftext|>


<commit_before>import { axisBottom, axisLeft } from "d3-axis";
import { scaleLinear } from "d3-scale";
import { line, area } from "d3-shape";
import { forEach, map, min, values } from "lodash-es";

import { appendLegend, createSVG } from "../../chartUtils";

const series = [
    {label: "Mean", color: "#a94442"},
    {label: "Median", color: "#428bca"},
    {label: "Quartile", color: "#3C763D"},
    {label: "Decile", color: "#FFF475"}
];

/**
 * @func
 * @param name
 * @param areaX
 * @param y
 * @param a
 * @param b
 */
const getArea = (name, areaX, y, a, b) => ({
    name,
    func: area()
        .x(areaX)
        .y0(d => y(d[a]))
        .y1(d => y(d[b]))
});

/**
 * @func
 * @param data
 * @returns {*}
 */
const getMinQuality = (data) => (
    min(map(data, document => min(values(document))))
);

/**
 * Generates the lines representing mean and median base quality.
 *
 * @func
 * @param data
 * @param key
 * @param x
 * @param y
 */
const lineDrawer = (data, key, x, y) => {
    const column = {
        mean: 0,
        median: 1
    }[key];

    const generator = line()
        .x((d, i) => x(i))
        .y(d => y(d[column]));

    return generator(data);
};

const CreateBasesChart = (element, data, baseWidth) => {

    const svg = createSVG(element, baseWidth);

    const width = baseWidth - svg.margin.left - svg.margin.right;

    // Set up scales and axes.
    const y = scaleLinear()
        .range([svg.height, 0])
        .domain([getMinQuality(data) - 5, 48]);

    const x = scaleLinear()
        .range([0, width])
        .domain([0, data.length]);

    const areaX = (d, i) => x(i);

    // Define the d3 area functions to render the inter-quartile and upper and lower decile plot areas.
    const areas = [
        getArea("upper", areaX, y, 3, 5),
        getArea("lower", areaX, y, 2, 4),
        getArea("quartile", areaX, y, 2, 3)
    ];

    // Append the areas to the chart.
    forEach(areas, area => {
        svg.append("path")
            .attr("d", area.func(data))
            .attr("class", "graph-line")
            .style("stroke", "none")
            .style("fill", area.name === "quartile" ? "#3C763D" : "#FFF475")
            .style("opacity", 0.5);
    });

    // Append the median line to the chart. Color is blue.
    svg.append("path")
        .attr("d", lineDrawer(data, "median", x, y))
        .attr("class", "graph-line")
        .style("stroke", "#428bca");

    // Append the median line to the chart. Color is red.
    svg.append("path")
        .attr("d", lineDrawer(data, "mean", x, y))
        .attr("class", "graph-line")
        .style("stroke", "#a94442");

    // Append the x-axis to the chart.
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", `translate(0, ${svg.height})`)
        .call(axisBottom(x))
        .append("text")
        .attr("x", width / 2)
        .attr("y", 30)
        .attr("fill", "black")
        .attr("class", "axis-label")
        .text("Read Position");

    // Append the y-axis to the chart.
    svg.append("g")
        .attr("class", "y axis")
        .call(axisLeft(y))
        .append("text")
        .text("Quality")
        .attr("dy", "10px")
        .attr("y", 6)
        .attr("fill", "black")
        .attr("class", "axis-label")
        .style("text-anchor", "end")
        .attr("transform", "rotate(-90)");

    appendLegend(svg, width, series);
};

export default CreateBasesChart;
<commit_msg>Fix chart axis label<commit_after>import { axisBottom, axisLeft } from "d3-axis";
import { scaleLinear } from "d3-scale";
import { line, area } from "d3-shape";
import { forEach, map, min, values } from "lodash-es";

import { appendLegend, createSVG } from "../../chartUtils";

const series = [
    {label: "Mean", color: "#a94442"},
    {label: "Median", color: "#428bca"},
    {label: "Quartile", color: "#3C763D"},
    {label: "Decile", color: "#FFF475"}
];

/**
 * @func
 * @param name
 * @param areaX
 * @param y
 * @param a
 * @param b
 */
const getArea = (name, areaX, y, a, b) => ({
    name,
    func: area()
        .x(areaX)
        .y0(d => y(d[a]))
        .y1(d => y(d[b]))
});

/**
 * @func
 * @param data
 * @returns {*}
 */
const getMinQuality = (data) => (
    min(map(data, document => min(values(document))))
);

/**
 * Generates the lines representing mean and median base quality.
 *
 * @func
 * @param data
 * @param key
 * @param x
 * @param y
 */
const lineDrawer = (data, key, x, y) => {
    const column = {
        mean: 0,
        median: 1
    }[key];

    const generator = line()
        .x((d, i) => x(i))
        .y(d => y(d[column]));

    return generator(data);
};

const CreateBasesChart = (element, data, baseWidth) => {

    const svg = createSVG(element, baseWidth);

    const width = baseWidth - svg.margin.left - svg.margin.right;

    // Set up scales and axes.
    const y = scaleLinear()
        .range([svg.height, 0])
        .domain([getMinQuality(data) - 5, 48]);

    const x = scaleLinear()
        .range([0, width])
        .domain([0, data.length]);

    const areaX = (d, i) => x(i);

    // Define the d3 area functions to render the inter-quartile and upper and lower decile plot areas.
    const areas = [
        getArea("upper", areaX, y, 3, 5),
        getArea("lower", areaX, y, 2, 4),
        getArea("quartile", areaX, y, 2, 3)
    ];

    // Append the areas to the chart.
    forEach(areas, area => {
        svg.append("path")
            .attr("d", area.func(data))
            .attr("class", "graph-line")
            .style("stroke", "none")
            .style("fill", area.name === "quartile" ? "#3C763D" : "#FFF475")
            .style("opacity", 0.5);
    });

    // Append the median line to the chart. Color is blue.
    svg.append("path")
        .attr("d", lineDrawer(data, "median", x, y))
        .attr("class", "graph-line")
        .style("stroke", "#428bca");

    // Append the median line to the chart. Color is red.
    svg.append("path")
        .attr("d", lineDrawer(data, "mean", x, y))
        .attr("class", "graph-line")
        .style("stroke", "#a94442");

    // Append the x-axis to the chart.
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", `translate(0, ${svg.height})`)
        .call(axisBottom(x))
        .append("text")
        .attr("x", width / 2)
        .attr("y", 30)
        .attr("dy", "10px")
        .attr("fill", "black")
        .attr("class", "axis-label")
        .text("Read Position");

    // Append the y-axis to the chart.
    svg.append("g")
        .attr("class", "y axis")
        .call(axisLeft(y))
        .append("text")
        .text("Quality")
        .attr("dy", "10px")
        .attr("y", 6)
        .attr("fill", "black")
        .attr("class", "axis-label")
        .style("text-anchor", "end")
        .attr("transform", "rotate(-90)");

    appendLegend(svg, width, series);
};

export default CreateBasesChart;
<|endoftext|>


<commit_before>import { gql } from "@apollo/client"
import {
  DEFAULT_PAGE_PROPS,
  DEFAULT_SEARCH_PROPS,
  DEFAULT_SEARCH_QUERY,
  SEARCH_OBJECT_TYPES,
  setSearchQuery
} from "actions"
import API from "api"
import LinkTo from "components/LinkTo"
import Model from "components/Model"
import {
  mapPageDispatchersToProps,
  PageDispatchersPropType,
  useBoilerplate
} from "components/Page"
import {
  deserializeQueryParams,
  getSearchQuery,
  SearchQueryPropType
} from "components/SearchFilters"
import _isEmpty from "lodash/isEmpty"
import { Report } from "models"
import moment from "moment"
import PropTypes from "prop-types"
import React, { useEffect, useMemo, useState } from "react"
import { Card, Table } from "react-bootstrap"
import { connect } from "react-redux"
import { useParams } from "react-router-dom"
import Settings from "settings"

const GQL_GET_REPORT_LISTS = gql`
  query (
    $reportQuery: ReportSearchQueryInput
    $reportPreviousQuery: ReportSearchQueryInput
  ) {
    currentList: reportList(query: $reportQuery) {
      list {
        uuid
        location {
          uuid
        }
        attendees {
          position {
            uuid
          }
        }
      }
    }
    previousList: reportList(query: $reportPreviousQuery) {
      list {
        uuid
        location {
          uuid
        }
        attendees {
          position {
            uuid
          }
        }
      }
    }
  }
`
// TODO: replace this with a way ask graphql for the content of a set of UUIDs
const GQL_GET_STATIC_DATA = gql`
  query (
    $positionQuery: PositionSearchQueryInput
    $locationQuery: LocationSearchQueryInput
    $taskQuery: TaskSearchQueryInput
  ) {
    positionList(query: $positionQuery) {
      list {
        uuid
        name
      }
    }
    locationList(query: $locationQuery) {
      list {
        uuid
        name
      }
    }
    taskList(query: $taskQuery) {
      list {
        uuid
        shortName
      }
    }
  }
`

const _SEARCH_PROPS = Object.assign({}, DEFAULT_SEARCH_PROPS, {
  onSearchGoToSearchPage: false,
  searchObjectTypes: [SEARCH_OBJECT_TYPES.REPORTS]
})

const DecisivesDashboard = ({ pageDispatchers }) => {
  const { dashboard } = useParams()
  const dashboardSettings = Settings.dashboards.find(o => o.label === dashboard)
  const [dashboardData, setDashboardData] = useState([])
  useEffect(() => {
    async function fetchData() {
      await fetch(dashboardSettings.data)
        .then(response => response.json())
        .then(setDashboardData)
    }
    fetchData()
  }, [dashboardSettings.data])

  return (
    <DecisivesDashboardStatic
      dashboardData={dashboardData}
      pageDispatchers={pageDispatchers}
    />
  )
}

DecisivesDashboard.propTypes = { pageDispatchers: PageDispatchersPropType }

const DecisivesDashboardStatic = ({ dashboardData, pageDispatchers }) => {
  const allActive = {
    pageNum: 0,
    pageSize: 0,
    status: Model.STATUS.ACTIVE
  }
  const { loading, error, data } = API.useApiQuery(GQL_GET_STATIC_DATA, {
    positionQuery: allActive,
    locationQuery: allActive,
    taskQuery: allActive
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    pageDispatchers
  })
  const decisives = useMemo(() => {
    if (!data || !dashboardData) {
      return []
    }
    return dashboardData.map(decisive => {
      return {
        label: decisive.label,
        positions: data.positionList.list.filter(item =>
          decisive.positions.includes(item.uuid)
        ),
        tasks: data.taskList.list.filter(item =>
          decisive.tasks.includes(item.uuid)
        ),
        locations: data.locationList.list.filter(item =>
          decisive.locations.includes(item.uuid)
        )
      }
    })
  }, [dashboardData, data])
  if (done) {
    return result
  }

  return _isEmpty(dashboardData) ? null : (
    <DecisivesDashboardImpl
      decisives={decisives}
      pageDispatchers={pageDispatchers}
    />
  )
}

DecisivesDashboardStatic.propTypes = {
  pageDispatchers: PageDispatchersPropType,
  dashboardData: PropTypes.array
}

const BaseDecisivesDashboardImpl = ({
  decisives,
  searchQuery,
  setSearchQuery,
  pageDispatchers
}) => {
  let queryParams
  if (searchQuery === DEFAULT_SEARCH_QUERY) {
    // when going from a different page to the decisives page, use the default
    // decisives search query
    queryParams = setDecisivesDefaultSearchQuery()
  } else {
    queryParams = getSearchQuery(searchQuery)
  }
  const prevQueryParams = {
    ...queryParams,
    engagementDateEnd: queryParams.engagementDateStart,
    engagementDateStart:
      2 * queryParams.engagementDateStart - queryParams.engagementDateEnd
  }
  const { loading, error, data } = API.useApiQuery(GQL_GET_REPORT_LISTS, {
    reportQuery: {
      pageNum: 0,
      pageSize: 0,
      ...queryParams
    },
    reportPreviousQuery: {
      pageNum: 0,
      pageSize: 0,
      ...prevQueryParams
    }
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    pageProps: DEFAULT_PAGE_PROPS,
    searchProps: _SEARCH_PROPS,
    pageDispatchers
  })
  const [reportStats, prevReportStats] = useMemo(() => {
    if (!data) {
      return []
    }
    const currentReports = data.currentList.list || []
    const currentStats = currentReports.reduce(
      (counter, report) => {
        counter.locationStats[report.location.uuid] =
          ++counter.locationStats[report.location.uuid] || 1
        report.attendees.reduce((counterNested, attendee) => {
          if (attendee.position) {
            counterNested.positionStats[attendee.position.uuid] =
              ++counterNested.positionStats[attendee.position.uuid] || 1
          }
          return counterNested
        }, counter)
        return counter
      },
      { locationStats: {}, positionStats: {} }
    )
    const previousReports = data.previousList.list || []
    const previousStats = previousReports.reduce(
      (counter, report) => {
        counter.locationStats[report.location.uuid] =
          ++counter.locationStats[report.location.uuid] || 1
        report.attendees.reduce((counterNested, attendee) => {
          if (attendee.position) {
            counterNested.positionStats[attendee.position.uuid] =
              ++counterNested.positionStats[attendee.position.uuid] || 1
          }
          return counterNested
        }, counter)
        return counter
      },
      { locationStats: {}, positionStats: {} }
    )
    return [currentStats, previousStats]
  }, [data])
  if (done) {
    return result
  }
  if (!data) {
    return null
  }

  return (
    <>
      <Card>
        <Card.Header>
          <Card.Title as="h3">People</Card.Title>
        </Card.Header>
        <Card.Body>
          {decisives.map(decisive => (
            <StatsTable
              label={decisive.label}
              key={decisive.label}
              data={decisive.positions}
              contentData={reportStats.positionStats}
              prevContentData={prevReportStats.positionStats}
              itemLabel={item => (
                <LinkTo modelType="Position" model={item}>
                  {item.name}
                </LinkTo>
              )}
            />
          ))}
        </Card.Body>
      </Card>
      <Card>
        <Card.Header>
          <Card.Title as="h3">Places</Card.Title>
        </Card.Header>
        <Card.Body>
          {decisives.map(decisive => (
            <StatsTable
              label={decisive.label}
              key={decisive.label}
              data={decisive.locations}
              contentData={reportStats.locationStats}
              prevContentData={prevReportStats.locationStats}
              itemLabel={item => (
                <LinkTo modelType="Location" model={item}>
                  {item.name}
                </LinkTo>
              )}
            />
          ))}
        </Card.Body>
      </Card>
      <Card>
        <Card.Header>
          <Card.Title as="h3">Processes</Card.Title>
        </Card.Header>
        <Card.Body>{/* <StatsTable data={[]} /> */}</Card.Body>
      </Card>
    </>
  )

  function setDecisivesDefaultSearchQuery() {
    const queryParams = {
      state: [Report.STATE.PUBLISHED],
      engagementDateStart: moment().subtract(8, "d").endOf("day").valueOf(),
      engagementDateEnd: moment().subtract(1, "d").endOf("day").valueOf()
    }
    deserializeQueryParams(
      SEARCH_OBJECT_TYPES.REPORTS,
      queryParams,
      deserializeCallback
    )
    return queryParams
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
  }
}

BaseDecisivesDashboardImpl.propTypes = {
  pageDispatchers: PageDispatchersPropType,
  searchQuery: SearchQueryPropType,
  setSearchQuery: PropTypes.func.isRequired,
  decisives: PropTypes.array
}

const mapStateToProps = (state, ownProps) => ({
  searchQuery: state.searchQuery
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  setSearchQuery: searchQuery => dispatch(setSearchQuery(searchQuery))
})

const DecisivesDashboardImpl = connect(
  mapStateToProps,
  mapDispatchToProps
)(BaseDecisivesDashboardImpl)

const StatsTable = ({
  data,
  itemLabel,
  label,
  contentData,
  prevContentData
}) => (

        {label}
        {data.map(item => (
          {itemLabel(item)}
        ))}

        {data.map(item => {
          const previous = prevContentData[item.uuid] || 0
          const current = contentData[item.uuid] || 0
          const color =
            current > 1.3 * previous
              ? "green"
              : current < 0.7 * previous
                ? "red"
                : "white"
          return (

              {current}/{previous}

          )
        })}

)

StatsTable.propTypes = {
  data: PropTypes.array.isRequired,
  itemLabel: PropTypes.func.isRequired,
  label: PropTypes.string.isRequired,
  contentData: PropTypes.object.isRequired,
  prevContentData: PropTypes.object.isRequired
}

export default connect(null, mapPageDispatchersToProps)(DecisivesDashboard)
<commit_msg>Fix old pre-HTML5 attribute<commit_after>import { gql } from "@apollo/client"
import {
  DEFAULT_PAGE_PROPS,
  DEFAULT_SEARCH_PROPS,
  DEFAULT_SEARCH_QUERY,
  SEARCH_OBJECT_TYPES,
  setSearchQuery
} from "actions"
import API from "api"
import LinkTo from "components/LinkTo"
import Model from "components/Model"
import {
  mapPageDispatchersToProps,
  PageDispatchersPropType,
  useBoilerplate
} from "components/Page"
import {
  deserializeQueryParams,
  getSearchQuery,
  SearchQueryPropType
} from "components/SearchFilters"
import _isEmpty from "lodash/isEmpty"
import { Report } from "models"
import moment from "moment"
import PropTypes from "prop-types"
import React, { useEffect, useMemo, useState } from "react"
import { Card, Table } from "react-bootstrap"
import { connect } from "react-redux"
import { useParams } from "react-router-dom"
import Settings from "settings"

const GQL_GET_REPORT_LISTS = gql`
  query (
    $reportQuery: ReportSearchQueryInput
    $reportPreviousQuery: ReportSearchQueryInput
  ) {
    currentList: reportList(query: $reportQuery) {
      list {
        uuid
        location {
          uuid
        }
        attendees {
          position {
            uuid
          }
        }
      }
    }
    previousList: reportList(query: $reportPreviousQuery) {
      list {
        uuid
        location {
          uuid
        }
        attendees {
          position {
            uuid
          }
        }
      }
    }
  }
`
// TODO: replace this with a way ask graphql for the content of a set of UUIDs
const GQL_GET_STATIC_DATA = gql`
  query (
    $positionQuery: PositionSearchQueryInput
    $locationQuery: LocationSearchQueryInput
    $taskQuery: TaskSearchQueryInput
  ) {
    positionList(query: $positionQuery) {
      list {
        uuid
        name
      }
    }
    locationList(query: $locationQuery) {
      list {
        uuid
        name
      }
    }
    taskList(query: $taskQuery) {
      list {
        uuid
        shortName
      }
    }
  }
`

const _SEARCH_PROPS = Object.assign({}, DEFAULT_SEARCH_PROPS, {
  onSearchGoToSearchPage: false,
  searchObjectTypes: [SEARCH_OBJECT_TYPES.REPORTS]
})

const DecisivesDashboard = ({ pageDispatchers }) => {
  const { dashboard } = useParams()
  const dashboardSettings = Settings.dashboards.find(o => o.label === dashboard)
  const [dashboardData, setDashboardData] = useState([])
  useEffect(() => {
    async function fetchData() {
      await fetch(dashboardSettings.data)
        .then(response => response.json())
        .then(setDashboardData)
    }
    fetchData()
  }, [dashboardSettings.data])

  return (
    <DecisivesDashboardStatic
      dashboardData={dashboardData}
      pageDispatchers={pageDispatchers}
    />
  )
}

DecisivesDashboard.propTypes = { pageDispatchers: PageDispatchersPropType }

const DecisivesDashboardStatic = ({ dashboardData, pageDispatchers }) => {
  const allActive = {
    pageNum: 0,
    pageSize: 0,
    status: Model.STATUS.ACTIVE
  }
  const { loading, error, data } = API.useApiQuery(GQL_GET_STATIC_DATA, {
    positionQuery: allActive,
    locationQuery: allActive,
    taskQuery: allActive
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    pageDispatchers
  })
  const decisives = useMemo(() => {
    if (!data || !dashboardData) {
      return []
    }
    return dashboardData.map(decisive => {
      return {
        label: decisive.label,
        positions: data.positionList.list.filter(item =>
          decisive.positions.includes(item.uuid)
        ),
        tasks: data.taskList.list.filter(item =>
          decisive.tasks.includes(item.uuid)
        ),
        locations: data.locationList.list.filter(item =>
          decisive.locations.includes(item.uuid)
        )
      }
    })
  }, [dashboardData, data])
  if (done) {
    return result
  }

  return _isEmpty(dashboardData) ? null : (
    <DecisivesDashboardImpl
      decisives={decisives}
      pageDispatchers={pageDispatchers}
    />
  )
}

DecisivesDashboardStatic.propTypes = {
  pageDispatchers: PageDispatchersPropType,
  dashboardData: PropTypes.array
}

const BaseDecisivesDashboardImpl = ({
  decisives,
  searchQuery,
  setSearchQuery,
  pageDispatchers
}) => {
  let queryParams
  if (searchQuery === DEFAULT_SEARCH_QUERY) {
    // when going from a different page to the decisives page, use the default
    // decisives search query
    queryParams = setDecisivesDefaultSearchQuery()
  } else {
    queryParams = getSearchQuery(searchQuery)
  }
  const prevQueryParams = {
    ...queryParams,
    engagementDateEnd: queryParams.engagementDateStart,
    engagementDateStart:
      2 * queryParams.engagementDateStart - queryParams.engagementDateEnd
  }
  const { loading, error, data } = API.useApiQuery(GQL_GET_REPORT_LISTS, {
    reportQuery: {
      pageNum: 0,
      pageSize: 0,
      ...queryParams
    },
    reportPreviousQuery: {
      pageNum: 0,
      pageSize: 0,
      ...prevQueryParams
    }
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    pageProps: DEFAULT_PAGE_PROPS,
    searchProps: _SEARCH_PROPS,
    pageDispatchers
  })
  const [reportStats, prevReportStats] = useMemo(() => {
    if (!data) {
      return []
    }
    const currentReports = data.currentList.list || []
    const currentStats = currentReports.reduce(
      (counter, report) => {
        counter.locationStats[report.location.uuid] =
          ++counter.locationStats[report.location.uuid] || 1
        report.attendees.reduce((counterNested, attendee) => {
          if (attendee.position) {
            counterNested.positionStats[attendee.position.uuid] =
              ++counterNested.positionStats[attendee.position.uuid] || 1
          }
          return counterNested
        }, counter)
        return counter
      },
      { locationStats: {}, positionStats: {} }
    )
    const previousReports = data.previousList.list || []
    const previousStats = previousReports.reduce(
      (counter, report) => {
        counter.locationStats[report.location.uuid] =
          ++counter.locationStats[report.location.uuid] || 1
        report.attendees.reduce((counterNested, attendee) => {
          if (attendee.position) {
            counterNested.positionStats[attendee.position.uuid] =
              ++counterNested.positionStats[attendee.position.uuid] || 1
          }
          return counterNested
        }, counter)
        return counter
      },
      { locationStats: {}, positionStats: {} }
    )
    return [currentStats, previousStats]
  }, [data])
  if (done) {
    return result
  }
  if (!data) {
    return null
  }

  return (
    <>
      <Card>
        <Card.Header>
          <Card.Title as="h3">People</Card.Title>
        </Card.Header>
        <Card.Body>
          {decisives.map(decisive => (
            <StatsTable
              label={decisive.label}
              key={decisive.label}
              data={decisive.positions}
              contentData={reportStats.positionStats}
              prevContentData={prevReportStats.positionStats}
              itemLabel={item => (
                <LinkTo modelType="Position" model={item}>
                  {item.name}
                </LinkTo>
              )}
            />
          ))}
        </Card.Body>
      </Card>
      <Card>
        <Card.Header>
          <Card.Title as="h3">Places</Card.Title>
        </Card.Header>
        <Card.Body>
          {decisives.map(decisive => (
            <StatsTable
              label={decisive.label}
              key={decisive.label}
              data={decisive.locations}
              contentData={reportStats.locationStats}
              prevContentData={prevReportStats.locationStats}
              itemLabel={item => (
                <LinkTo modelType="Location" model={item}>
                  {item.name}
                </LinkTo>
              )}
            />
          ))}
        </Card.Body>
      </Card>
      <Card>
        <Card.Header>
          <Card.Title as="h3">Processes</Card.Title>
        </Card.Header>
        <Card.Body>{/* <StatsTable data={[]} /> */}</Card.Body>
      </Card>
    </>
  )

  function setDecisivesDefaultSearchQuery() {
    const queryParams = {
      state: [Report.STATE.PUBLISHED],
      engagementDateStart: moment().subtract(8, "d").endOf("day").valueOf(),
      engagementDateEnd: moment().subtract(1, "d").endOf("day").valueOf()
    }
    deserializeQueryParams(
      SEARCH_OBJECT_TYPES.REPORTS,
      queryParams,
      deserializeCallback
    )
    return queryParams
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
  }
}

BaseDecisivesDashboardImpl.propTypes = {
  pageDispatchers: PageDispatchersPropType,
  searchQuery: SearchQueryPropType,
  setSearchQuery: PropTypes.func.isRequired,
  decisives: PropTypes.array
}

const mapStateToProps = (state, ownProps) => ({
  searchQuery: state.searchQuery
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  setSearchQuery: searchQuery => dispatch(setSearchQuery(searchQuery))
})

const DecisivesDashboardImpl = connect(
  mapStateToProps,
  mapDispatchToProps
)(BaseDecisivesDashboardImpl)

const StatsTable = ({
  data,
  itemLabel,
  label,
  contentData,
  prevContentData
}) => (

        {label}
        {data.map(item => (
          {itemLabel(item)}
        ))}

        {data.map(item => {
          const previous = prevContentData[item.uuid] || 0
          const current = contentData[item.uuid] || 0
          const color =
            current > 1.3 * previous
              ? "green"
              : current < 0.7 * previous
                ? "red"
                : "white"
          return (

              {current}/{previous}

          )
        })}

)

StatsTable.propTypes = {
  data: PropTypes.array.isRequired,
  itemLabel: PropTypes.func.isRequired,
  label: PropTypes.string.isRequired,
  contentData: PropTypes.object.isRequired,
  prevContentData: PropTypes.object.isRequired
}

export default connect(null, mapPageDispatchersToProps)(DecisivesDashboard)
<|endoftext|>


<commit_before>import EditPosition from '../pages/editPosition.page'
import { expect } from 'chai'

const ADVISOR_ORG = 'EF 2.2'
const PRINCIPAL_ORG = 'MoI'

describe('Edit position page', () => {

  beforeEach('Open the edit position page', () => {
    EditPosition.open()
  })

  describe('When changing the position type from principal to advisor', () => {
    it('Should reset the organization field', () => {
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typeAdvisorButton.click()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.equal('')
    })
  })

  describe('When changing the position type from principal to advisor and saving', () => {
    it('Should update the position type to advisor', () => {
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typeAdvisorButton.click()
        EditPosition.organization.setValue(ADVISOR_ORG)
        EditPosition.organizationAutocomplete.waitForExist(5000)
        EditPosition.organizationAutocomplete.click()
        EditPosition.submitForm()
        EditPosition.waitForAlertSuccessToLoad()
        const alertMessage = EditPosition.alertSuccess.getText()
        expect(alertMessage).to.equal('Saved Position')
        EditPosition.open()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.equal(ADVISOR_ORG)
    })
  })

  describe('When changing the position type from advisor to principal and saving', () => {
    it('Should update the position type to principal', () => {
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typePrincipalButton.click()
        EditPosition.organization.setValue(PRINCIPAL_ORG)
        EditPosition.organizationAutocomplete.waitForExist(5000)
        EditPosition.organizationAutocomplete.click()
        EditPosition.submitForm()
        EditPosition.waitForAlertSuccessToLoad()
        const alertMessage = EditPosition.alertSuccess.getText()
        expect(alertMessage).to.equal('Saved Position')
        EditPosition.open()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.equal(PRINCIPAL_ORG)
    })
  })

})
<commit_msg>NCI-Agency/anet#329: Wait for buttons to become visible<commit_after>import EditPosition from '../pages/editPosition.page'
import { expect } from 'chai'

const ADVISOR_ORG = 'EF 2.2'
const PRINCIPAL_ORG = 'MoI'

describe('Edit position page', () => {

  beforeEach('Open the edit position page', () => {
    EditPosition.open()
  })

  describe('When changing the position type from principal to advisor', () => {
    it('Should reset the organization field', () => {
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        EditPosition.typePrincipalButton.waitForVisible()
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typeAdvisorButton.click()
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        EditPosition.typePrincipalButton.waitForVisible()
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.equal('')
    })
  })

  describe('When changing the position type from principal to advisor and saving', () => {
    it('Should update the position type to advisor', () => {
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typeAdvisorButton.click()
        EditPosition.organization.setValue(ADVISOR_ORG)
        EditPosition.organizationAutocomplete.waitForExist()
        EditPosition.organizationAutocomplete.click()
        EditPosition.submitForm()
        EditPosition.waitForAlertSuccessToLoad()
        const alertMessage = EditPosition.alertSuccess.getText()
        expect(alertMessage).to.equal('Saved Position')
        EditPosition.open()
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        EditPosition.typePrincipalButton.waitForVisible()
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.equal(ADVISOR_ORG)
    })
  })

  describe('When changing the position type from advisor to principal and saving', () => {
    it('Should update the position type to principal', () => {
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.include('active')
        EditPosition.typePrincipalButton.waitForVisible()
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.not.include('active')
        expect(EditPosition.organization.getValue()).to.not.equal('')
        EditPosition.typePrincipalButton.click()
        EditPosition.organization.setValue(PRINCIPAL_ORG)
        EditPosition.organizationAutocomplete.waitForExist()
        EditPosition.organizationAutocomplete.click()
        EditPosition.submitForm()
        EditPosition.waitForAlertSuccessToLoad()
        const alertMessage = EditPosition.alertSuccess.getText()
        expect(alertMessage).to.equal('Saved Position')
        EditPosition.open()
        EditPosition.typeAdvisorButton.waitForVisible()
        expect(EditPosition.typeAdvisorButton.getAttribute('class')).to.not.include('active')
        EditPosition.typePrincipalButton.waitForVisible()
        expect(EditPosition.typePrincipalButton.getAttribute('class')).to.include('active')
        expect(EditPosition.organization.getValue()).to.equal(PRINCIPAL_ORG)
    })
  })

})
<|endoftext|>