Fix crosshair test

Fix crosshair test

--- bokehjs/test/models/tools/inspectors/crosshair_tool.coffee
+++ bokehjs/test/models/tools/inspectors/crosshair_tool.coffee
@@ -22,10 +22,6 @@
       @p = new Plot({x_range: @x_range, y_range: @y_range, toolbar: toolbar})

     it "should add two new spans to the plot_canvas synthetic_renderers", ->
-      plot_canvas = @p.plot_canvas
-      # Plot canvas has no renderers
-      expect(plot_canvas.renderers.length).to.be.equal 0
-
       crosshair = new CrosshairTool({plot: @p})
       spans = [crosshair.spans.width, crosshair.spans.height]
       # Plot canvas should now have the two cross hair span renderers


Scroller; check document.body exists.

Scroller; check document.body exists.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -219,7 +219,7 @@
         window.scrollBy(0, amount)
       return

-    activatedElement ||= firstScrollableElement()
+    activatedElement ||= document.body and firstScrollableElement()
     return unless activatedElement

     # Avoid the expensive scroll calculation if it will not be used.  This reduces costs during smooth,
@@ -230,7 +230,7 @@
       CoreScroller.scroll element, direction, elementAmount

   scrollTo: (direction, pos) ->
-    activatedElement ||= firstScrollableElement()
+    activatedElement ||= document.body and firstScrollableElement()
     return unless activatedElement

     element = findScrollableElement activatedElement, direction, pos, 1


Autocomplete fixes, still have some issues ~

--- client/Framework/components/autocomplete/autocompletecontroller.coffee
+++ client/Framework/components/autocomplete/autocompletecontroller.coffee
@@ -206,7 +206,7 @@
     {itemDataPath,customCompare,isCaseSensitive} = @getOptions()
     suggested = JsPath.getAt data, itemDataPath
     for selectedData in @getSelectedItemData()
-      if compare?
+      if customCompare?
         alreadySelected = customCompare data, selectedData
         return yes if alreadySelected
       else
@@ -273,14 +273,14 @@
       itemValue = item.getOptions().userInput
       data = JsPath itemDataPath, itemValue

-    return no if @isItemAlreadySelected data
+    return no  if @isItemAlreadySelected data

     path = @getCollectionPath()

     itemName  = "#{name}-#{@selectedItemCounter++}"
     if form
-      collection = form.getCustomData path
-      collection = [] unless collection?
+      collection   = form.getCustomData path
+      collection or= []
       form.addCustomData path, collection
       id = itemValue.getId?()
       collection.push(


Admin: Integrations: Post selected events.

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -111,35 +111,33 @@

   createEventCheckboxes: ->

-    selectedEvents = {}
-
-    for event in @getData().selectedEvents
-      selectedEvents[event.name] = event
-
-    mainWrapper = new KDCustomHTMLView cssClass: 'event-cbes'
+    { selectedEvents } = @getData()
+    mainWrapper        = new KDCustomHTMLView cssClass: 'event-cbes'

     for item in @data.settings?.events

-      wrapper = new KDCustomHTMLView cssClass: 'event-cb'
-
-      label   = new KDLabelView
-        title : item.description
-
-      wrapper.addSubView new KDInputView
+      { name } = item
+      wrapper  = new KDCustomHTMLView cssClass: 'event-cb'
+      label    = new KDLabelView title: item.description
+      checkbox = new KDInputView
         type         : 'checkbox'
-        name         : item.name
+        name         : name
         label        : label
-        defaultValue : if selectedEvents[item.name] then yes else ''
-
+        defaultValue : selectedEvents.indexOf(name) > -1
+
+      wrapper.addSubView checkbox
       wrapper.addSubView label
       mainWrapper.addSubView wrapper

+      @eventCheckboxes[name] = checkbox
+
     @settingsForm.fields.events.addSubView mainWrapper

   handleFormCallback: (formData) ->

     data = @getData()
+    data.selectedEvents = []
     { name, label, channels } = formData
     options       =
       id          : data.id
@@ -150,6 +148,9 @@

     if name isnt data.title
       options.settings = customName : name
+
+    for name, checkbox of @eventCheckboxes when checkbox.getValue()
+      data.selectedEvents.push name

     kd.singletons.socialapi.integrations.update options, (err) =>
       return kd.warn err  if err


Client: use webhookmiddleware url instead of integration url

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -199,7 +199,7 @@

       { url, regenerate } = @settingsForm.inputs

-      url.setValue "#{globals.config.integration.url}/#{name}/#{res.token}"
+      url.setValue "#{globals.config.webhookMiddleware.url}/#{name}/#{res.token}"

       regenerate.updatePartial 'Webhook url has been updated!'
       regenerate.setClass 'label'


adding newlines for banned text

adding newlines for banned text

--- client/landing/site.landing/coffee/core/maincontrollerloggedout.coffee
+++ client/landing/site.landing/coffee/core/maincontrollerloggedout.coffee
@@ -88,7 +88,7 @@
       success     : -> location.replace "/#{redirectTo}#{query}"
       error       : ({responseText}) =>

-        if /suspended/i.test responseText
+        if /suspension/i.test responseText
           handleBanned responseText
         else if /TwoFactor/i.test responseText
           @emit 'TwoFactorEnabled'
@@ -101,7 +101,7 @@

   handleBanned = (responseText) ->
     new KDModalView
-      title        : "You've been banned!"
+      title        : "Account banned due to policy violation(s)."
       content      : responseText
       overlay      : yes
       cancelable   : no


remove error message on redirect url, changed time formatting

--- scripts/soundcloud-info.coffee
+++ scripts/soundcloud-info.coffee
@@ -31,8 +31,6 @@
       if res.statusCode is 302
         data = JSON.parse(body)
         showInfo msg, data.location
-      else
-        msg.send "Soundcloud: error: #{url} returned #{res.statusCode}"

 showInfo = (msg, url) ->
   msg.http(url)
@@ -54,8 +52,6 @@
   m = (h - hours) * 60
   mins = Math.floor m
   secs = Math.round Math.floor((m - mins) * 60)
-  secs = if secs >= 10 then secs else "0#{secs}"
   if hours > 0
-    mins = if mins >= 10 then mins else "0#{mins}"
-    return "#{hours}:#{mins}:#{secs}"
-  return "#{mins}:#{secs}"
+    return "#{hours}h#{mins}m#{secs}s"
+  return "#{mins}m#{secs}s"


set up feature china

set up feature china

--- server_setup.coffee
+++ server_setup.coffee
@@ -169,7 +169,7 @@
       features.playViewsOnly = true
       features.codePlay = true # for one-off changes. If they're shared across different scenarios, refactor

-    if /cn\.codecombat\.com/.test(req.get('host')) or /koudashijie/.test(req.get('host')) or req.session.featureMode is 'china'
+    if /aojiarui/.test(req.get('host')) or req.session.featureMode is 'china'
       features.china = true
       features.freeOnly = true
       features.noAds = true


MainNav: show dashboard link only for group admins

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -59,9 +59,10 @@
       attributes : href : '/Account'
       cssClass   : 'bottom separator'
       partial    : 'Account settings'
-      click      : (event)->
+      click      : (event)=>
         KD.utils.stopDOMEvent event
         KD.getSingleton('router').handleRoute '/Account'
+        @hide()

     @avatarPopupContent.addSubView new KDCustomHTMLView
@@ -69,28 +70,39 @@
       attributes : href : '/Environments'
       cssClass   : 'bottom'
       partial    : 'Environments'
-      click      : (event)->
+      click      : (event)=>
         KD.utils.stopDOMEvent event
         KD.getSingleton('router').handleRoute '/Environments'
-
-    @avatarPopupContent.addSubView new KDCustomHTMLView
+        @hide()
+
+
+    @avatarPopupContent.addSubView dashboard = new KDCustomHTMLView
       tagName    : 'a'
       attributes : href : '/Dashboard'
-      cssClass   : 'bottom'
+      cssClass   : 'bottom hidden'
       partial    : 'Dashboard'
-      click      : (event)->
-        KD.utils.stopDOMEvent event
-        KD.getSingleton('router').handleRoute '/Dashboard'
-
+
+    # FIXME:
+    KD.utils.wait 2000, =>
+      group = KD.getSingleton("groupsController").getCurrentGroup()
+      group.canEditGroup? (err, success)=>
+        if success
+          dashboard.show()
+          dashboard.on 'click', (event)=>
+            KD.utils.stopDOMEvent event
+            KD.getSingleton('router').handleRoute '/Dashboard'
+            @hide()

     @avatarPopupContent.addSubView new KDCustomHTMLView
       tagName    : 'a'
       attributes : href : '/Logout'
       cssClass   : 'bottom'
       partial    : 'Logout'
-      click      : (event)->
+      click      : (event)=>
         KD.utils.stopDOMEvent event
         KD.getSingleton('router').handleRoute '/Logout'
+        @hide()
+

   populatePendingGroups:->


MachineSettingsDomainsView: Completed assing in new UI.

--- client/app/lib/providers/machinesettingsdomainsview.coffee
+++ client/app/lib/providers/machinesettingsdomainsview.coffee
@@ -3,6 +3,7 @@
 KDView                    = kd.View
 globals                   = require 'globals'
 DomainItem                = require 'app/domains/domainitem'
+KDModalView               = kd.ModalView
 KDCustomHTMLView          = kd.CustomHTMLView
 MachineSettingsCommonView = require './machinesettingscommonview'

@@ -21,6 +22,7 @@

     @listController.getListView()
       .on 'DeleteDomainRequested', @bound 'removeDomain'
+      .on 'DomainStateChanged',    @bound 'handleStateChange'

   createAddInput: ->
@@ -41,6 +43,7 @@

    kd.singletons.computeController.fetchDomains (err, domains = []) =>
       kd.warn err  if err
+
       @listController.lazyLoader.hide()
       @listController.replaceAllItems domains

@@ -70,3 +73,87 @@
         # @warning.show()

+  handleStateChange: (domainItem, state) ->
+
+    domainItem.setLoadingMode yes
+
+    # @warning.hide()
+
+    @askForPermission domainItem, state, (approved) =>
+      if approved
+        @changeDomainState domainItem, state
+      else
+        @revertToggle domainItem, state
+        domainItem.setLoadingMode no
+
+
+  changeDomainState: (domainItem, state) ->
+
+    { computeController } = kd.singletons
+    { stateToggle }       = domainItem
+    { domain }            = domainItem.getData()
+    machineId             = @machine._id
+    action                = if state then 'setDomain' else 'unsetDomain'
+
+    computeController.getKloud()[action] { domainName: domain, machineId }
+      .then ->
+        computeController.domains = []
+        domainItem.data.machineId = null  unless state
+
+      .catch (err) =>
+        kd.warn "Failed to change domain state:", err
+        # @warning.setTooltip title: err.message
+        # @warning.show()
+
+        @revertToggle domainItem, state
+        domainItem.setLoadingMode no
+
+      .finally =>
+        domainItem.setLoadingMode no
+
+
+  revertToggle: (domainItem, state) ->
+
+    { stateToggle } = domainItem
+    if state then stateToggle.setOff no else stateToggle.setOn no
+
+
+  askForPermission: (domainItem, state, callback) ->
+
+    { domain, machineId } = domainItem.getData()
+
+    return callback yes  if not state or not machineId
+
+    { computeController } = kd.singletons
+
+    modal = new KDModalView
+      cssClass      : 'domain-assign-modal'
+      title         : 'Reassign domain ?'
+      content       : """
+

+

+            The domain that you are trying to add: #{domain} is already
+            assigned to another VM that you own. If you continue, this domain
+            will be reassigned to the current VM and disassociated with all others.
+

+

+
Continue?

+

+      """
+      overlay       : yes
+      cancel        : ->
+        modal.destroy()
+        callback no
+      buttons       :
+        OK          :
+          title     : 'Yes'
+          style     : 'solid red medium'
+          loader    :
+            color   : 'darkred'
+          callback  : ->
+            modal.destroy()
+            callback yes
+        cancel      :
+          title     : 'Cancel'
+          style     : 'solid light-gray medium'
+          callback  : -> modal.cancel()


VMSettings:General: Hide "Keep VM always on" and "Assigned URL" parts for managed machines

--- client/app/lib/providers/machinesettingsgeneralview.coffee
+++ client/app/lib/providers/machinesettingsgeneralview.coffee
@@ -155,7 +155,7 @@
           label         : 'Keep VM always on'
           defaultValue  : @machine.alwaysOn
           itemClass     : KodingSwitch
-          cssClass      : 'statustoggle'
+          cssClass      : if @machine.isManaged() then 'statustoggle hidden' else 'statustoggle'
           disabled      : @machine.isPermanent()
           callback      : @bound 'handleAlwaysOnStateChanged'
         nickname        :
@@ -177,7 +177,7 @@
           partial       : @machine.ipAddress or 'N/A'
         accessUri       :
           label         : 'Assigned URL'
-          cssClass      : 'assigned-url'
+          cssClass      : if @machine.isManaged() then 'assigned-url hidden' else 'assigned-url'
           itemClass     : CustomLinkView
           title         : @machine.domain
           href          : accessUri


Make vmController.run to work like kiteController.run but to work specially for vm related requests.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -8,30 +8,44 @@
     @resetVMData()
     (KD.getSingleton 'mainController').once 'AppIsReady', => @fetchVMs()

-  run:(vm, command, callback)->
-    KD.requireMembership
-      callback : =>
-        @askForApprove command, (approved)=>
-          if approved
-            cb = unless command is 'vm.info' then @_cbWrapper vm, callback \
-                 else callback
-            @kc.run
-              kiteName : 'os'
-              method   : command
-              vmName   : vm
-            , cb
-          else unless command is 'vm.info' then @info vm
-      onFailMsg : "Login required to use VMs"  unless command is 'vm.info'
-      onFail    : =>
-        unless command is 'vm.info' then callback yes
-        else callback null, state: 'STOPPED'
-      silence   : yes
+  run:(options={}, callback)->
+
+    if "string" is typeof options
+      command = options
+      options =
+        withArgs : command
+
+    @fetchDefaultVmName (defaultVmName)=>
+      log "Found defaultVm as:", defaultVmName
+      vmName = if options.vmName then options.vmName else defaultVmName
+      log "Requested VM is:", vmName
+      unless vmName
+        return callback message: 'There is no VM for this account.'
+      options.correlationName = vmName
+      @kc.run options, callback

   _runWraper:(command, vm, callback)->
     [callback, vm] = [vm, callback]  unless 'string' is typeof vm
     @fetchDefaultVmName (defaultVm)=>
       vm or= defaultVm
-      @run vm, command, callback  if vm
+      return  unless vm
+      KD.requireMembership
+        callback : =>
+          @askForApprove command, (approved)=>
+            if approved
+              cb = unless command is 'vm.info' then @_cbWrapper vm, callback \
+                   else callback
+              @run
+                kiteName : 'os'
+                method   : command
+                vmName   : vm
+              , cb
+            else unless command is 'vm.info' then @info vm
+        onFailMsg : "Login required to use VMs"  unless command is 'vm.info'
+        onFail    : =>
+          unless command is 'vm.info' then callback yes
+          else callback null, state: 'STOPPED'
+        silence   : yes

   start:(vm, callback)->
     @_runWraper 'vm.start', vm, callback
@@ -67,7 +81,7 @@
               return callback null  unless state
               deleteVM vm, callback

-  info:(vm, callback)->
+  info:(vm, callback=noop)->
     [callback, vm] = [vm, callback]  unless 'string' is typeof vm
     @fetchDefaultVmName (defaultVm)=>
       vm or= defaultVm


Account Settings: Toggle on / off sub option of private message

--- client/account/lib/views/accountemailnotifications.coffee
+++ client/account/lib/views/accountemailnotifications.coffee
@@ -163,5 +163,8 @@
       then 'unsetClass'
       else 'setClass'

-      @fields[item].formView[method] 'off'
+      field = @fields[item]

+      field.formView[method]      'off'
+      field.subSettings[method]   'off'  if item is 'privateMessage'
+


invitation: accepted invitations show modified date

--- client/admin/lib/views/invitations/inviteditemview.coffee
+++ client/admin/lib/views/invitations/inviteditemview.coffee
@@ -71,8 +71,8 @@
       cssClass   : 'avatarview'
       attributes :
         src      : "//gravatar.com/avatar/#{hash}?s=#{size}&d=#{defaultAvatarUri}"
-
-    @timeAgoView  = new KDTimeAgoView { click: @bound 'showSettings' }, createdAt or modifiedAt
+
+    @timeAgoView  = new KDTimeAgoView { click: @bound 'showSettings' }, modifiedAt or createdAt

     if statusType is 'pending'
       @settingsIcon = new KDCustomHTMLView


CredentialStatusView: take aws credential into account for now and ignore rest

--- client/admin/lib/views/stacks/credentialstatusview.coffee
+++ client/admin/lib/views/stacks/credentialstatusview.coffee
@@ -18,6 +18,7 @@

     { @credentials } = (@getOption 'stackTemplate') or {}
     @credentials   or= {}
+    @credentialsData = []

     # Waiting state view
     @waitingView = new KDView
@@ -43,12 +44,9 @@

     creds = Object.keys @credentials

-    if creds.length > 0
-      # TODO you know it. ~GG
-      credential = @credentials[creds.first].first
-
+    if creds.length > 0 and credential = @credentials['aws']?.first
       remote.api.JCredential.one credential, (err, credential) =>
-        if err
+        if err or not credential
         then @setNotVerified 'Credentials not valid'
         else @setCredential credential
     else


fixed processor pin error

fixed processor pin error

--- client/src/app/game/board/editBoardProcessorDialog.coffee
+++ client/src/app/game/board/editBoardProcessorDialog.coffee
@@ -21,7 +21,7 @@
   # Only valid if determineLinkage() returns 'simple'
   getSimpleDestination = (name, direction, inValues, outValues) ->
     if direction in ['in', 'inout'] then return inValues[name]
-    for destination, expression in outValues
+    for destination, expression of outValues
       if expression is "pins.#{name}" then return destination
     throw new Error("Cannot find simple destination for pin '#{name}'")


don't repost errors to /errors path

don't repost errors to /errors path

--- app/assets/javascripts/initializers/error_handler.js.coffee
+++ app/assets/javascripts/initializers/error_handler.js.coffee
@@ -8,10 +8,7 @@
     logError = (msg) ->
       if window.teaspoonTesting == true
         console.log("ERROR: " + msg)
-      else
-        ETahi.RESTless.post errorPath,
-          message: msg
-
+
     container.register('logError:main', logError , instantiate: false)
     application.inject('route', 'logError', 'logError:main')


Changed up selector and added explanatory comment

--- picoCTF-web/web/coffee/problems.coffee
+++ picoCTF-web/web/coffee/problems.coffee
@@ -91,7 +91,10 @@
       when 0
         apiNotify(data)
       when 1
-        addScoreToTitle("h1#title")
+      	# We want the score to be level with the title, but the title
+	# is defined in a template. This solution is therefore a bit
+	# of a hack.
+        addScoreToTitle("#title")
         apiCall "GET", "/api/problems/feedback/reviewed", {}
         .done (reviewData) ->
           $("#problem-list-holder").html renderProblemList({


[Marketing Modals] fix failing test

[Marketing Modals] fix failing test

--- desktop/components/marketing_signup_modal/index.coffee
+++ desktop/components/marketing_signup_modal/index.coffee
@@ -67,7 +67,7 @@
     @inner.on 'close', => @modal.close()

     loggedOut = not sd.CURRENT_USER?
-    delayDuration = if modalData.duration then modalData.duration else 2000
+    delayDuration = if modalData?.duration then modalData?.duration else 2000

     setTimeout @open, delayDuration if loggedOut and modalData?


Test creating two invites at once

Test creating two invites at once

--- services/web/test/acceptance/coffee/ProjectInviteTests.coffee
+++ services/web/test/acceptance/coffee/ProjectInviteTests.coffee
@@ -249,6 +249,33 @@
 					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
 				], done

+			it 'should allow the project owner to many invites at once', (done) ->
+				@inviteOne = null
+				@inviteTwo = null
+				Async.series [
+					(cb) => expectProjectAccess @sendingUser, @projectId, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
+					# create first invite
+					(cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
+						return cb(err) if err
+						@inviteOne = invite
+						cb()
+					(cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
+					# and a second
+					(cb) => createInvite @sendingUser, @projectId, @email, (err, invite) =>
+						return cb(err) if err
+						@inviteTwo = invite
+						cb()
+					# should have two
+					(cb) => expectInviteListCount @sendingUser, @projectId, 2, cb
+					# revoke first
+					(cb) => revokeInvite @sendingUser, @projectId, @inviteOne._id, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 1, cb
+					# revoke second
+					(cb) => revokeInvite @sendingUser, @projectId, @inviteTwo._id, cb
+					(cb) => expectInviteListCount @sendingUser, @projectId, 0, cb
+				], done
+
 	describe 'clicking the invite link', ->

 		beforeEach (done) ->


refactor: Use minim to add warning about unrecognized format

--- src/parse.coffee
+++ src/parse.coffee
@@ -6,24 +6,22 @@
 apiElementsToJson = require('./api-elements-to-json')

-createAnnotation = (message, type) ->
-  {
-    element: 'annotation'
-    meta: {classes: [type]}
-    content: message
-  }
+createWarning = (message) ->
+  annotation = new fury.minim.elements.Annotation(message)
+  annotation.classes.push('warning')
+  return annotation

 parse = (source, callback) ->
-  annotations = []
+  warning = null
   mediaType = deckardcain.identify(source)

   unless mediaType
     mediaType = 'text/vnd.apiblueprint'
-    annotations.push(createAnnotation('''\
+    warning = createWarning('''\
       Could not recognize API description format. \
       Falling back to API Blueprint by default.\
-    ''', 'warning'))
+    ''')

   args = {source, mediaType, generateSourceMap: true}
   fury.parse(args, (err, apiElements) ->
@@ -34,8 +32,8 @@
       err = new Error(err.message)

     if apiElements
+      apiElements.unshift(warning) if warning
       apiElements = apiElementsToJson(apiElements)
-      apiElements.content = apiElements.content.concat(annotations)
     else
       apiElements = null


Renamed classes to be more specific

Renamed classes to be more specific

--- core/app/backbone/views/evidence/evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/evidence_vote_view.coffee
@@ -24,8 +24,8 @@
       'spec-evidence-vote-down'
     ].join(' ')

-    R.div className: 'evidence-vote',
-      R.span className:"evidence-vote-relevance spec-evidence-relevance",
+    R.div className: 'comment-vote-container',
+      R.span className:"comment-vote-amount spec-evidence-relevance",
         format_as_short_number(@model().relevance())
       if Factlink.Global.signed_in
         [


We don't need separate songs collection.

All method can be implemented on store .

--- app/client/js/songs/songs.coffee
+++ app/client/js/songs/songs.coffee
@@ -1,19 +0,0 @@
-goog.provide 'app.songs.Songs'
-
-goog.require 'app.songs.Songs'
-
-class app.songs.Songs
-
-  ###*
-    @constructor
-  ###
-  constructor: ->
-    @items = []
-
-  ###*
-    @type {Array.<app.songs.Song>}
-  ###
-  items: null
-
-  clearAll: ->
-    @items.length = 0


bugfix - avoid double counting compiles

bugfix - avoid double counting compiles

--- app/coffee/CompileManager.coffee
+++ app/coffee/CompileManager.coffee
@@ -41,7 +41,7 @@
 					image:     request.imageName
 				}, (error, output, stats) ->
 					return callback(error) if error?
-					Metrics.inc("compiles")
+					Metrics.inc("compiles-succeeded")
 					for metric_key, metric_value of stats or {}
 						Metrics.count(metric_key, metric_value)
 					loadavg = os.loadavg?()


fix whitespace

fix whitespace

--- app/coffee/ResourceWriter.coffee
+++ app/coffee/ResourceWriter.coffee
@@ -53,7 +53,7 @@

 	storeSyncState: (state, basePath, callback) ->
 		stateFile = Path.join(basePath, @SYNC_STATE_FILE)
-		if not state? # remove the  file if no state passed in
+		if not state? # remove the file if no state passed in
 			logger.log state:state, basePath:basePath, "clearing sync state"
 			fs.unlink stateFile, (err) ->
 				if err? and err.code isnt 'ENOENT'


fix issue #3332

fix issue #3332

--- app/coffee/modules/search.coffee
+++ app/coffee/modules/search.coffee
@@ -64,11 +64,11 @@
         promise.then null, @.onInitialDataError.bind(@)

         # Search input watcher
-        @scope.searchTerm = ""
+        @scope.searchTerm = null
         loadSearchData = debounceLeading(100, (t) => @.loadSearchData(t))

         bindOnce @scope, "projectId", (projectId) =>
-            if !@scope.searchResults
+            if !@scope.searchResults && @scope.searchTerm
                 @.loadSearchData()

         @scope.$watch "searchTerm", (term) =>


fix for subject groups that only have 2 preview sets

--- app/classify.cjsx
+++ app/classify.cjsx
@@ -67,7 +67,7 @@

   onSubjectSelect: (e, subject) ->
     previews = subject.location.previews
-    randomInt = Math.round(Math.random() * (2 - 0)) + 0
+    randomInt = if subject.location.previews.length is 2 then Math.round(Math.random()) else Math.round(Math.random() * (2 - 0)) + 0
     @setState({
       video: subject.location.standard
       previews: previews[randomInt]


add test for generator sources

add test for generator sources


restored OutlineElement's "filled" option

--- src/art/engine/elements/shape_children/outline_element.coffee
+++ src/art/engine/elements/shape_children/outline_element.coffee
@@ -13,6 +13,7 @@
     lineWidth:  default: 1,         validate: (v) -> typeof v is "number"
     lineCap:    default: "butt",    validate: (v) -> v in validLineCaps
     lineJoin:   default: "miter",   validate: (v) -> v in validLineJoins
+    filled:     default: false
     miterLimit:
       default: 10,
       validate: (v) -> !v || typeof v is "number"
@@ -28,6 +29,8 @@
     cacheable: -> @getHasChildren()

   fillShape: (target, elementToTargetMatrix, options) ->
+    if @_filled
+      @_parent.fillShape target, elementToTargetMatrix, options
     @_parent.strokeShape target, elementToTargetMatrix, options

   _prepareDrawOptions: (drawOptions, compositeMode, opacity)->


Updated method headers

Updated method headers

to make them more understandable. see https://github.com/github/hubot/pull/344#discussion_r3017158

--- src/robot.coffee
+++ src/robot.coffee
@@ -333,20 +333,18 @@
   #            when event happens.
   #
   # Returns nothing.
-  on: (args...) ->
-    @events.on args...
+  on: (event, args...) ->
+    @events.on event, args...

   # Public: A wrapper around the EventEmitter API to make usage
   # semanticly better.
   #
   # event   - The event name.
-  # [arg1]  - Event argument one (optional).
-  # [arg2]  - Event argument two (optional).
-  # [....]  - Event argument n (optional).
-  #
-  # Returns nothing.
-  emit: (args...) ->
-    @events.emit args...
+  # args...  - Arguments emitted by the event
+  #
+  # Returns nothing.
+  emit: (event, args...) ->
+    @events.emit event, args...

   # Public: Kick off the event loop for the adapter
   #


Fail harder on errors

Fail harder on errors

Currently if the client emits an error we log it but just hang around.
The app is in an undefined state at that point so it's hard to say
what exactly will happen, but it's probably not ideal.

At least this way a process monitor can tell that it died and restart
it, ship the logs somewhere, or something.

--- src/slack.coffee
+++ src/slack.coffee
@@ -37,6 +37,8 @@

   error: (error) =>
     @robot.logger.error "Received error #{error.toString()}"
+    @robot.logger.error error.stack
+    process.exit 1

   loggedIn: (self, team) =>
     @robot.logger.info "Logged in as #{self.name} of #{team.name}, but not yet connected"


No function getBody()

No function getBody()

--- src/slack.coffee
+++ src/slack.coffee
@@ -117,7 +117,7 @@
     user = @client.dataStore.getUserById msg.user if msg.user
     user.room ?= msg.channel if channel and user

-    rawText = msg.getBody()
+    rawText = msg.text
     text = @removeFormatting rawText

     if msg.hidden or (not rawText and not msg.attachments) or msg.subtype is 'bot_message' or not msg.user or not channel
@@ -219,7 +219,7 @@
           match = match

       @robot.logger.debug "Sending to #{envelope.room}: #{msg}"
-
+
       if msg.length <= SlackBot.MAX_MESSAGE_LENGTH
         @client.sendMessage msg, envelope.room


In development mode, don't make sure Chrome dev tools still give us errors (RE window.onerror)

--- js/inner.coffee
+++ js/inner.coffee
@@ -565,6 +565,14 @@
   str.replace(/(.+)\/$/, "$1")

 $(document).ready () ->
+  if window.renderContext.env == 'development'
+    hoptoad_on_error = window.onerror
+    window.onerror = (message, file, line) =>
+      hoptoad_on_error message, file, line
+      VM.setErrorMessage message + "\nfile: " + file + "\nline: " + line
+      return false
+
+
   Sammy('#app', () ->
     @get('/tests/inner', (cx) -> VM.loadJasmineTests(cx))


ActivityRightBlock: Use koding as default group

--- client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
+++ client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
@@ -61,7 +61,8 @@

     super options, data

-    group = KD.singletons.groupsController.getCurrentGroup().slug
+    # FIXME ~ EA
+    group = 'koding' # KD.singletons.groupsController.getCurrentGroup().slug

     @showAllLink = new KDCustomHTMLView
       tagName : "a"


Added execsPending and execsComplete events for ie enabling/disabling buttons based on pending execs

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -36,12 +36,16 @@
       cmd: cmd
     complete: ->
       scraperwiki.exec.pending -= 1
+      if scraperwiki.exec.pending is 0
+        $(document).trigger('execsComplete')

   if success?
     options.success = success
   if error?
     options.error = error
   scraperwiki.exec.pending += 1
+  if scraperwiki.exec.pending is 1
+    $(document).trigger('execsPending')
   $.ajax options

 scraperwiki.exec.pending = 0


dependencies on other objects must be called in dinitialize

--- static/coffee/renderers/guide/axis.coffee
+++ static/coffee/renderers/guide/axis.coffee
@@ -345,7 +345,6 @@
     super(attrs, options)
     @register_property('bounds', @_bounds, false)
     @add_dependencies('bounds', this, ['guidespec'])
-    @add_dependencies('bounds', @get_obj('plot'), ['x_range', 'y_range'])

     @register_property('rule_coords', @_rule_coords, false)
     @add_dependencies('rule_coords', this, ['bounds', 'dimension', 'location'])
@@ -360,6 +359,9 @@
     @add_dependencies('side', this, ['normals'])

     @register_property('padding_request', @_padding_request, false)
+
+  dinitialize: (attrs, options)->
+    @add_dependencies('bounds', @get_obj('plot'), ['x_range', 'y_range'])

   _bounds: () ->
     i = @get('guidespec').dimension


refactored. removed useless methods

refactored. removed useless methods

--- tasks/lib/file_finder.coffee
+++ tasks/lib/file_finder.coffee
@@ -16,18 +16,10 @@
       extension:  options.extension
       dest:       helper.pathToFolder(filepath)

-    grunt.log.writeln "Files: ", filepath, this.requiredFiles( params )
-    grunt.log.writeln "Dirs : ", filepath, this.requiredDirs(  params )
+    grunt.log.writeln "Files: ", filepath, this.requiredFiles( 'file', params )
+    grunt.log.writeln "Dirs : ", filepath, this.requiredFiles( 'dirs', params )

-
-  exports.requiredFiles = (options) ->
-    this.requiredThing('file', options)
-
-  exports.requiredDirs = (options) ->
-    this.requiredThing('folder', options)
-
-  exports.requiredThing = (key, options) ->
-    options = {}  unless options
+  exports.requiredFiles = (key, options) ->
     keyword = if key == "file" then options.keyword else options.dirKeyword
     this.generateList(keyword, key, options)

@@ -42,6 +34,4 @@
   exports.extension = (key, extension) ->
     if key == "file" then extension else "*"+extension

-
-
   exports


Handled error when creating LinkListCollection and no begin or end defined for a link

--- src/main/webapp/lib/collections/LinkListCollection.coffee
+++ src/main/webapp/lib/collections/LinkListCollection.coffee
@@ -273,10 +273,10 @@
       _.map(link.roads().road(), (r) -> r.on('change', -> link.set_crud_update()))
     bNode = link.begin_node()
     eNode = link.end_node()
-    bNode.on('remove', => @removeNodeReference(link, 'begin'))
-    eNode.on('remove', => @removeNodeReference(link, 'end'))
+    bNode.on('remove', => @removeNodeReference(link, 'begin')) if bNode?
+    eNode.on('remove', => @removeNodeReference(link, 'end'))  if eNode?
     # bNode.position().on('change',(=> @reDrawLink(link)), @)
-    #    eNode.position().on('change',(=> @reDrawLink(link)), @)
+    # eNode.position().on('change',(=> @reDrawLink(link)), @)

   #view the demands for the link passed in
   viewDemands: (cid) ->


removed no longer needed dupe-testing functionality

--- core/app/backbone/views/autocompleted_add_to_channel_view.coffee
+++ core/app/backbone/views/autocompleted_add_to_channel_view.coffee
@@ -83,15 +83,13 @@
         return
     @createNewChannel()

-  isDupe: (title) -> @collection.where(title: title).length > 0
+  createNewChannel: ->
+    title = $.trim @$("input.typeahead").val()

-  createNewChannel: ->
-    title = @$("input.typeahead").val()
-    title = $.trim(title)
-    dupe = false
-    if (title.length < 1) or (@isDupe(title))
+    if (title.length < 1)
       @completelyDisappear()
       return
+
     to_create_user_channels = @_auto_completes_view.collection.filter((item) ->
       item.get("title") is title and item.get("user_channel")
     )


Fixed showing user popover

Fixed showing user popover

--- core/app/backbone/views/facts/fact_vote_table_view.coffee
+++ core/app/backbone/views/facts/fact_vote_table_view.coffee
@@ -7,7 +7,7 @@
     user: @model.user().toJSON()

   onRender: ->
-    UserPopoverContentView.makeTooltip @, @model
+    UserPopoverContentView.makeTooltip @, @model.user()

 class window.FactVoteTableView extends Backbone.Marionette.CompositeView


Tests: InvitationListController: Fix broken tests

--- client/admin/lib/test/invitationslistcontroller.test.coffee
+++ client/admin/lib/test/invitationslistcontroller.test.coffee
@@ -12,6 +12,7 @@
 item              = new InvitedItemView {}, mockInvitation
 item.revokeButton = new KDButtonView
 item.resendButton = new KDButtonView
+fetcherMethod     = kd.noop

 describe 'InvitationsListController', ->
@@ -22,7 +23,7 @@

     it 'should instantiate with default options', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()

       { noItemFoundText, statusType, lazyLoadThreshold, viewOptions, fetcherMethod } = listController.getOptions()
@@ -39,7 +40,7 @@

     it 'should handle Resend event', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()
       spy            = expect.spyOn listController, 'resend'

@@ -49,7 +50,7 @@

     it 'should handle Revoke (RemoveItem) event', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()
       spy            = expect.spyOn listController, 'removeItem'

@@ -62,7 +63,7 @@

     it 'should remove item completely if no error', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()

       removeSpy      = expect.spyOn(mockInvitation, 'remove').andCall (callback) -> callback null
@@ -76,7 +77,7 @@

     it 'should hide lazy loader of button if err', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()

       removeSpy      = expect.spyOn(mockInvitation, 'remove').andCall (callback) -> callback new Error 'error!'
@@ -91,7 +92,7 @@

     it 'should call sendInvitationByCode with given code', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()

       spy            = expect.spyOn remote.api.JInvitation, 'sendInvitationByCode'
@@ -102,7 +103,7 @@

     it 'should hide loader of resend button', ->

-      listController = new InvitationsListController
+      listController = new InvitationsListController { fetcherMethod }
       listView       = listController.getListView()

       remoteSpy      = expect.spyOn(remote.api.JInvitation, 'sendInvitationByCode').andCall (code, callback) -> callback null


Reporter v1.1 - much simpler

Reporter v1.1 - much simpler

--- tasks/report.coffee
+++ tasks/report.coffee
@@ -118,15 +118,6 @@

 prettyPrint = () ->

-  files = {}
-  results = {}
-  tests = {}
-
-  report =
-    files: files
-    results: results
-    tests: tests
-
   grunt.log.subhead "Test results:"
   grunt.log.writeln ''

@@ -147,14 +138,9 @@
     grunt.log.writeln '   content    "' + result.content + '"'
     grunt.log.writeln ''

-    # Reporter
-
-    files[result.file] = _result.files[result.file]
-    tests[result.test] = _result.tests[result.test]
-    results[idResult] = result
-
-  grunt.file.write 'tasks/reportingTool/data/report.json', JSON.stringify(report, null, '\t')
-  grunt.log.verbose.writeln JSON.stringify(report, null, '\t')
+  # Write the report
+  grunt.file.write 'tasks/reportingTool/data/report.json', JSON.stringify(_result, null, '\t')
+  grunt.log.verbose.writeln JSON.stringify(_result, null, '\t')

 module.exports =
   set: set


Remove unneeded commented out line of code

--- app/assets/javascripts/admin/views/home/add_integration.js.coffee
+++ app/assets/javascripts/admin/views/home/add_integration.js.coffee
@@ -8,7 +8,6 @@
     @listTemplate = JST['admin/templates/parameters/list_fields']

   events:
-    # 'click button#save': 'save'
     'click button#cancel': 'cancel'

   render: ->
@@ -47,7 +46,6 @@
       on: false,
       width: 90
     })
-

     @prepareClickHandlers()


change lastActivity based color scheme to workingHours based scheme, fetch data on date range change, honor user chosen workingHour range and hide users not within the range #651

--- app/assets/javascripts/admin/views/task/task_overview_view.coffee
+++ app/assets/javascripts/admin/views/task/task_overview_view.coffee
@@ -7,6 +7,7 @@
 routes : routes
 daterangepicker : DateRangePicker
 rangeslider : RangeSlider
+libs/utils : Utils
 ###

 class TaskOverviewView extends Backbone.Marionette.ItemView
@@ -56,17 +57,55 @@
     "rangeSliderLabel2" : "#rangeSliderLabel2"
     "rangeSliderLabel3" : "#rangeSliderLabel3"

+  DEFAULT_TIME_PERIOD_UNIT : "month"
+  DEFAULT_TIME_PERIOD_TIME : 3
+  MS_PER_HOUR : 3600000
+
+  chosenMinHours : 0
+  chosenMaxHours : 24 * 356 * 100
+

   initialize : ->

-    @fetchPromise = @model.fetch()
+    defaultStartDate = moment().startOf("day").subtract(@DEFAULT_TIME_PERIOD_TIME, @DEFAULT_TIME_PERIOD_UNIT).valueOf()
+    defaultEndDate = moment().endOf("day").valueOf()
+    @fetchData(defaultStartDate, defaultEndDate)
+
+    @listenTo(@model, "change", @renderRangeSlider)
+
+
+  fetchData : (start, end) ->
+
+    @minMaxHours = {}
+    @fetchPromise = @model.fetch(
+      data:
+        start: start
+        end: end
+    )
+
+
+  getMinMaxHours : ->
+
+    if _.isEmpty(@minMaxHours)
+      minTime = Math.min(_.pluck(@model.attributes.userInfos, "workingTime")...)
+      maxTime = Math.max(_.pluck(@model.attributes.userInfos, "workingTime")...)
+
+      # Convert ms to h
+      @minMaxHours =
+        min: Math.floor(minTime / @MS_PER_HOUR)
+        max: Math.ceil(maxTime / @MS_PER_HOUR)
+      if @minMaxHours.min == @minMaxHours.max
+        @minMaxHours.max += 1
+
+
+    return @minMaxHours

   onRender : ->

     @ui.taskTypesCheckbox.prop("checked", true)
     @initializeDateRangePicker()
-    @initializeRangeSlider()
+    @renderRangeSlider()
     @paintGraph()

@@ -75,33 +114,47 @@
     @ui.dateRangeInput.daterangepicker(
       locale:
         format: 'L'
-      startDate: moment().subtract(3, 'months').format("L")
+      startDate: moment().subtract(@DEFAULT_TIME_PERIOD_TIME, @DEFAULT_TIME_PERIOD_UNIT).format("L")
       endDate: moment().format("L")
       opens: "left"
     (start, end, label) =>
+      @fetchData(start.valueOf(), end.valueOf())
       @paintGraph()
     )
     return

-  initializeRangeSlider : ->
-
-    RangeSlider.create(@ui.rangeSliderInput[0],
-      start: [0, 80]
-      connect: true
-      step: 1
-      range:
-        min: 0
-        max: 100 #TODO: Find min/max in data
-    )
-
-    @ui.rangeSliderInput[0].noUiSlider.on('update', (values, handle) =>
-      @ui.rangeSliderLabel1[0].innerHTML = "#{Math.round(+values[0])}h";
-      @ui.rangeSliderLabel2[0].innerHTML = "#{Math.round((+values[0] + +values[1]) / 2)}h";
-      @ui.rangeSliderLabel3[0].innerHTML = "#{Math.round(+values[1])}h";
-      @paintGraphDebounced()
-    )
-    return
+  renderRangeSlider : ->
+
+    sliderEl = @ui.rangeSliderInput[0]
+
+    @fetchPromise.done( =>
+      minMaxHours = @getMinMaxHours()
+
+      # Destroy existing instance to reconfigure
+      if sliderEl.noUiSlider
+        sliderEl.noUiSlider.destroy()
+
+      RangeSlider.create(sliderEl,
+        start: [
+          Math.max(minMaxHours.min, @chosenMinHours)
+          Math.min(minMaxHours.max, @chosenMaxHours)
+        ]
+        connect: true
+        step: 1
+        margin: 1
+        range: minMaxHours
+      )
+
+      sliderEl.noUiSlider.on('update', (values, handle) =>
+        @chosenMinHours = Math.round(+values[0])
+        @chosenMaxHours = Math.round(+values[1])
+        @ui.rangeSliderLabel1[0].innerHTML = "#{@chosenMinHours}h";
+        @ui.rangeSliderLabel2[0].innerHTML = "#{Utils.roundTo((+values[0] + +values[1]) / 2, 1)}h";
+        @ui.rangeSliderLabel3[0].innerHTML = "#{@chosenMaxHours}h";
+        @paintGraphDebounced()
+      )
+    )

   paintGraphDebounced : ->
@@ -150,11 +203,18 @@
     $(@ui.projectsCheckbox).prop("checked")

+  doDrawUser : (user) ->
+
+    return @chosenMinHours <= user.workingHours <= @chosenMaxHours
+
+
   getSVG : ->

     @fetchPromise.then( =>

       { userInfos, taskTypes, projects } = @model.attributes
+      # move workingTime to user object and convert to hours
+      userInfos.map( (userInfo) => userInfo.user.workingHours = Utils.roundTo(userInfo.workingTime / @MS_PER_HOUR, 2) )
       # extract users and add full names
       @users = _.pluck(userInfos, "user")
       @users.map( (user) -> user.name = user.firstName + " " + user.lastName )
@@ -187,11 +247,13 @@
   buildNodes : (taskTypes, projects) ->

     svgUsers = @users.map( (user) =>
-      userName = user.firstName + " " + user.lastName
-      lastActivityDays = moment().diff(user.lastActivity, 'days')
-      color = @colorJet(Math.min(50, lastActivityDays) * (128 / 50) + 128)
-
-      @quoted(userName) + " [id="+ @quoted(user.id) + ", shape=box, fillcolor=" + @quoted(color) + "]"
+      if @doDrawUser(user)
+        userName = user.firstName + " " + user.lastName
+        workingHours = user.workingHours
+        minMaxHours = @getMinMaxHours()
+        color = @colorJet(128 - (workingHours - minMaxHours.min) * (128 / minMaxHours.max - minMaxHours.min) + 128)
+
+        @quoted(userName) + " [id="+ @quoted(user.id) + ", shape=box, fillcolor=" + @quoted(color) + "]"
     ).join(";")

     svgTaskTypes = ""
@@ -217,21 +279,21 @@

       svgTaskTypeEdges = userInfos.map( (userInfo) =>
         { user, taskTypes } = userInfo
-        taskTypes.map( (taskType) => @edge(user.name, taskType.summary))
+        taskTypes.map( (taskType) => @edge(user.name, taskType.summary)) if @doDrawUser(user)
       ).join(";")

       svgFutureTaskTypesEdges  = "edge [ color=blue ];"
       svgFutureTaskTypesEdges += userInfos.map( (userInfo) =>
         { user, futureTaskType } = userInfo
         if(futureTaskType)
-          @edge(user.name, futureTaskType.summary)
+          @edge(user.name, futureTaskType.summary) if @doDrawUser(user)
       ).join(";")

     if @doDrawProjects()
       svgProjectEdges = userInfos.map( (userInfo) =>
         { user, projects } = userInfo
-        projects.map( (project) => @edge(user.name, project.name))
+        projects.map( (project) => @edge(user.name, project.name)) if @doDrawUser(user)
       ).join(";")

@@ -268,9 +330,7 @@

   createUserTooltip : (user) ->

-    lastActivityDays = moment().diff(user.lastActivity, 'days')
-
-    ["Last Activity: #{lastActivityDays} days ago",
+    ["Working time: #{user.workingHours}h",
      "Experiences:",
       _.map(user.experiences, (domain, value) -> domain + " : " + value ).join("
")
     ].join("
")


Fixed issue #1913 and #1914: Given template parameters for rendering ticket create view are not displayed.

--- app/assets/javascripts/app/controllers/agent_ticket_create.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_create.coffee
@@ -220,8 +220,7 @@

   renderQueue: (template = {}) =>
     localeRender = =>
-      @render(options: template)
-      @render()
+      @render(template)
     App.QueueManager.add(@queueKey, localeRender)
     return if !@formMeta
     App.QueueManager.run(@queueKey)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -14,7 +14,7 @@
   # Add events and methods here
   self.bind "update", ->
     for property, data of I.activeTweens
-      I[property] = Easing[]
+      I[property] = Easing[data.]

   tween: (duration, properties) ->
     for property, target of properties


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -1,5 +1,5 @@
 test "Bounds should give correct offset position", ->
-  obj =
+  obj = GameObject
     x: 5
     y: 10
     width: 50


Factories are autoregistered, so no more need for empty ones.

--- test/factory.coffee
+++ test/factory.coffee
@@ -112,14 +112,5 @@
   moves: ['Perish Song', 'Endeavor']
   ability: 'Drizzle'

-Factory.register 'Abomasnow'
-Factory.register 'Regirock'
-Factory.register 'Shaymin (land)'
-Factory.register 'Jolteon'
-Factory.register 'Clamperl'
 Factory.register 'Dugtrio',
   moves: ['Magnitude', 'Dig']
-Factory.register 'Giratina (origin)'
-Factory.register 'Ferrothorn'
-Factory.register 'Tornadus (incarnate)'
-Factory.register 'Moltres'


don't throw an exception when there's an annoucnement and 2 chagim tables--this happens when Pesach begins in middle of the week and it's not an error condition because it all fits on the page

--- source/javascripts/index/schedule.coffee
+++ source/javascripts/index/schedule.coffee
@@ -62,7 +62,6 @@
       when 1
         if has_announcement then [7] else [8]
       when 2
-        throw "There's no room for announcements when there are 2 visible chagim table" if has_announcement
         switch
           when $("#chagim-tables > div.shabbat .zachor").not(".hidden").size() > 0 then [5,7]
           else [6,6]


Restricted some more analytics.

Restricted some more analytics.

--- app/views/play/level_view.coffee
+++ app/views/play/level_view.coffee
@@ -89,7 +89,7 @@
       setTimeout f, 100
     else
       @load()
-      application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID
+      application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID, ['Google Analytics']

   setUpHourOfCode: ->
     me.set 'hourOfCode', true
@@ -291,7 +291,7 @@
       @loadEndTime = new Date()
       loadDuration = @loadEndTime - @loadStartTime
       console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s"
-      application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration
+      application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration, ['Google Analytics']
       application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID

   onSurfaceSetUpNewWorld: ->


update unit test

update unit test

--- services/real-time/test/unit/coffee/DocumentUpdaterManagerTests.coffee
+++ services/real-time/test/unit/coffee/DocumentUpdaterManagerTests.coffee
@@ -93,7 +93,7 @@
 				@DocumentUpdaterManager.flushProjectToMongoAndDelete @project_id, @callback

 			it 'should delete the project from the document updater', ->
-				url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}"
+				url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}?background=true"
 				@request.del.calledWith(url).should.equal true

 			it "should call the callback with no error", ->


updated atom docblockr settings

updated atom docblockr settings

--- .atom/config.cson
+++ .atom/config.cson
@@ -22,6 +22,12 @@
       "one-dark-ui"
       "chester-atom-syntax"
     ]
+  docblockr:
+    align_tags: "no"
+    extra_tags: [
+      ""
+    ]
+    lower_case_primitives: true
   editor:
     fontFamily: "Fira Code"
     showIndentGuide: true


use keyboard-localization in atom

use keyboard-localization in atom

--- .atom/config.cson
+++ .atom/config.cson
@@ -26,6 +26,8 @@
   "autocomplete-plus": {}
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
-'.source.python':
+  "keyboard-localization":
+    useKeyboardLayout: "fr_FR"
+".python.source":
   whitespace:
     removeTrailingWhitespace: true


atom: ctrl space for autocomplete

atom: ctrl space for autocomplete

--- .atom/keymap.cson
+++ .atom/keymap.cson
@@ -29,3 +29,8 @@

 'atom-text-editor':
   'ctrl-shift-delete': 'editor:delete-to-end-of-line'
+  'ctrl-shift-f': 'atom-beautify:beautify-editor'
+
+'atom-text-editor.autocomplete-active':
+  'tab': 'autocomplete-plus:cancel'
+  'ctrl-space': 'autocomplete-plus:confirm'


Fix the issue with autoprefixer.

Fix the issue with autoprefixer.

--- src/watch.coffee
+++ src/watch.coffee
@@ -395,14 +395,25 @@
         false
       else
         true
-    plugins = (getPlugins packages, config).filter (plugin) ->
-      if config.plugins.on?.length and plugin.brunchPluginName in config.plugins.on
-        plugin.defaultEnv = '*'
+
+    unfiltered = getPlugins packages, config
+    currentEnvs = config.env
+    alwaysEnabled = config.plugins.on or []
+
+    plugins = unfiltered.filter (plugin) ->
+      plugin.defaultEnv = '*' if plugin.brunchPluginName in alwaysEnabled
       plugin.optimize ?= plugin.minify if typeof plugin.minify is 'function'
-      isOptimizer = typeof plugin.optimize is 'function'
-      return false if not config.optimize and isOptimizer
-      plugin.defaultEnv ?= '*'
-      plugin.defaultEnv is '*' or plugin.defaultEnv in config.env
+
+      # Optimizers are only enabled in production.
+      env = plugin.defaultEnv ?= if typeof plugin.optimize is 'function'
+        'production'
+      else
+        '*'
+
+      # Does it match the environment?
+      env is '*' or env in currentEnvs
+
+    debug "Loaded plugins: #{plugins.map((plugin) -> plugin.brunchPluginName).join(', ')}"

     # Get compilation methods.
     compilers  = plugins.filter propIsFunction 'compile'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -7,6 +7,6 @@
   ok obj.meter

 test "should set max<Attribute> if one doesn't exist", ->
-  obj = GameObj
+  obj = GameObject()

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -21,6 +21,6 @@
 test "should set both <attribute> and max<attribute> if they aren't present in the including object", ->
   obj = GameObject()

-  obj
+  obj.include

 module()


:new: Add specs for EditorLinter::onDidMessage*

--- spec/editor-linter-spec.coffee
+++ spec/editor-linter-spec.coffee
@@ -43,6 +43,27 @@
       editorLinter.removeMessage(message)
       expect(messageSet.has(message)).toBe(false)

+  describe '::onDidMessage*', ->
+    it 'notifies us of the changes to messages', ->
+      message = getMessage('Hey!', __dirname + '/fixtures/file.txt', [[0, 1], [0, 2]])
+      messageAdd = jasmine.createSpy('messageAdd')
+      messageChange = jasmine.createSpy('messageChange')
+      messageRemove = jasmine.createSpy('messageRemove')
+      editorLinter.onDidMessageAdd(messageAdd)
+      editorLinter.onDidMessageChange(messageChange)
+      editorLinter.onDidMessageRemove(messageRemove)
+      editorLinter.addMessage(message)
+      expect(messageAdd).toHaveBeenCalled()
+      expect(messageAdd).toHaveBeenCalledWith(message)
+      expect(messageChange).toHaveBeenCalled()
+      expect(messageChange.mostRecentCall.args[0].type).toBe('add')
+      expect(messageChange.mostRecentCall.args[0].message).toBe(message)
+      editorLinter.removeMessage(message)
+      expect(messageRemove).toHaveBeenCalled()
+      expect(messageRemove).toHaveBeenCalledWith(message)
+      expect(messageChange.mostRecentCall.args[0].type).toBe('remove')
+      expect(messageChange.mostRecentCall.args[0].message).toBe(message)
+
   describe '::onShouldLint', ->
     it 'ignores instant save requests', ->
       timesTriggered = 0


Update the spec.

Update the spec.

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -314,7 +314,7 @@

             button = fatalError.querySelector('.btn')
             expect(button.textContent).toContain 'Create issue on the notifications package'
-            expect(button.getAttribute('href')).toContain 'bit.ly/cats'
+            expect(button.getAttribute('href')).toContain 'is.gd/cats'

             expect(issueTitle).toContain '$ATOM_HOME'
             expect(issueTitle).not.toContain process.env.ATOM_HOME
@@ -583,7 +583,7 @@

           button = fatalError.querySelector('.btn')
           expect(button.textContent).toContain 'Create issue on atom/atom'
-          expect(button.getAttribute('href')).toContain 'bit.ly/cats'
+          expect(button.getAttribute('href')).toContain 'is.gd/cats'

           expect(issueBody).toContain 'ReferenceError: a is not defined'
           expect(issueBody).toContain '**Thrown From**: Atom Core'
@@ -622,7 +622,7 @@
           fatalNotification = fatalError.querySelector('.fatal-notification')
           expect(button.textContent).toContain 'Create issue'
           expect(fatalNotification.textContent).toContain 'You can help by creating an issue'
-          expect(button.getAttribute('href')).toContain 'bit.ly/cats'
+          expect(button.getAttribute('href')).toContain 'is.gd/cats'

       describe "when the error has not been reported", ->
         beforeEach ->
@@ -651,7 +651,7 @@
               button = fatalError.querySelector('.btn')
               encodedMessage = encodeURI(truncatedMessage)
               expect(button.textContent).toContain 'Create issue'
-              expect(button.getAttribute('href')).toContain 'bit.ly/cats'
+              expect(button.getAttribute('href')).toContain 'is.gd/cats'

       describe "when the package is out of date", ->
         beforeEach ->
@@ -875,10 +875,8 @@
 # issuesResponse
 generateFakeAjaxResponses = (options) ->
   $.ajax.andCallFake (url, settings) ->
-    if url.indexOf('bitly.com') > -1
-      response = options?.shortenerResponse ? {
-        data: url: 'http://bit.ly/cats'
-      }
+    if url.indexOf('is.gd') > -1
+      response = options?.shortenerResponse ? 'http://is.gd/cats'
       settings.success(response)
     else if url.indexOf('atom.io/api/packages') > -1
       response = options?.packageResponse ? {


fix sending of understaffed-mails

fix sending of understaffed-mails

--- server/methods/mail/sendMail/sendUnderstaffed.coffee
+++ server/methods/mail/sendMail/sendUnderstaffed.coffee
@@ -27,7 +27,7 @@
 			users = users.fetch().filter (u) -> Roles.userIsInRole u._id, Permissions.participant, shift.tagId
 			type = 'participant'

-		for user in users.filter((u) -> user.profile.shortTermCalls == true)
+		for user in users.filter((u) -> u.profile.shortTermCalls == true)
 			thisMoment = moment(shift.date, 'YYYYDDDD')
 			thisMoment.locale(user.profile.language)
 			date = thisMoment.format('dddd, DD.MM.YYYY')


tasks: update realtime preview without using the network

--- salvus/page/tasks.coffee
+++ salvus/page/tasks.coffee
@@ -924,6 +924,7 @@
             desc           = cm.getValue()
             task.last_desc = desc  # update current description before syncing.
             task.desc      = desc
+            @display_desc(task) # update the realtime preview
             task.last_edited = (new Date()) - 0
             @db.update
                 set   : {desc    : task.desc, last_edited : task.last_edited}


:green_heart: Another attempt to fix tests

--- spec/table-element-spec.coffee
+++ spec/table-element-spec.coffee
@@ -170,7 +170,7 @@
       bodyContent = tableShadowRoot.querySelector('.table-edit-rows')

       expect(bodyContent.offsetHeight).toBeCloseTo(2000)
-      expect(bodyContent.offsetWidth).toBeCloseTo(tableElement.clientWidth, -1)
+      expect(bodyContent.offsetWidth).toBeCloseTo(tableElement.clientWidth, -2)

     describe 'with the absolute widths setting enabled', ->
       beforeEach ->


[➠] Optimized `shady`.

[➠] Optimized `shady`.

--- vendor/assets/javascripts/jquery/plugins/shadify/shady.js.coffee
+++ vendor/assets/javascripts/jquery/plugins/shadify/shady.js.coffee
@@ -55,7 +55,7 @@
       minDistance: 20
       maxDistance: 400
       autopilot: false
-      draw: true
+      draw: false
       bounds: do FSS.Vector3.create
       step: FSS.Vector3.create Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0)

@@ -185,6 +185,7 @@
             @renderer.context.strokeStyle = light.ambientHex

             do @renderer.context.stroke
+
             do @renderer.context.beginPath

             @renderer.context.arc lx, ly, 4, 0, Math.PIM2
@@ -323,7 +324,7 @@
     browser = ua.browser
     family  = browser.family

-    if Modernizr.webgl and (family is 'Chrome' or family is 'Safari')
+    if Modernizr.webgl
       Shady.RENDERER.WEBGL
     else if Modernizr.canvas
       Shady.RENDERER.CANVAS


allows to define css classes for cancel button

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -3,6 +3,7 @@
     fade: false
     title: null
     cancel: "Cancel"
+    cancel_class: "btn cancel"
     proceed: "OK"
     proceed_class: "btn proceed btn-primary"

@@ -20,7 +21,7 @@
   $("#confirmation_dialog").addClass("fade") if element.data("confirm-fade") || $.fn.twitter_bootstrap_confirmbox.defaults.fade
   $("#confirmation_dialog .modal-body").html(message)
   $("#confirmation_dialog .modal-header h3").html(element.data("confirm-title") || $.fn.twitter_bootstrap_confirmbox.defaults.title || window.top.location.origin)
-  $("#confirmation_dialog .modal-footer .cancel").html(element.data("confirm-cancel") || $.fn.twitter_bootstrap_confirmbox.defaults.cancel)
+  $("#confirmation_dialog .modal-footer .cancel").html(element.data("confirm-cancel") || $.fn.twitter_bootstrap_confirmbox.defaults.cancel).attr("class", $.fn.twitter_bootstrap_confirmbox.defaults.cancel_class).addClass(element.data("confirm-cancel-class"))
   $("#confirmation_dialog .modal-footer .proceed").html(element.data("confirm-proceed") || $.fn.twitter_bootstrap_confirmbox.defaults.proceed).attr("class", $.fn.twitter_bootstrap_confirmbox.defaults.proceed_class).addClass(element.data("confirm-proceed-class"))
   $("#confirmation_dialog").modal("show").on("hidden", ->
     $(this).remove()


added the case for undefined

added the case for undefined

--- source/latte.coffee
+++ source/latte.coffee
@@ -39,6 +39,8 @@
           call(rightParam, @, toEval, allParams)
         else
           throw 'broken'
+      else if testelem is undefined
+        call(rightParam, @, toEval, allParams)
       else
         throw 'unsupported arg'


fixed lint errors

fixed lint errors

--- bin/fbsh.coffee
+++ bin/fbsh.coffee
@@ -193,7 +193,10 @@
 printEvents = (list) ->
 	# TODO simple event list
 	list.forEach (e) ->
-		console.log '%s %s: %s', shortName(e.person), relTime(e.date), e.text || e.description
+		console.log '%s %s: %s',
+			shortName(e.person),
+			relTime(e.date),
+			e.text || e.description

 shortName = (user) ->
 	if !user || !user.name then return ''
@@ -205,6 +208,6 @@
 	if d.getDate() == now.getDate() then return 'today'
 	if d.getDate() == now.getDate() - 1 then return 'yesterday'
 	dif = now.getDate() - d.getDate()
-	return dif + ' days ago';
+	return dif + ' days ago'

 do main


updated mocks to use public

updated mocks to use public

--- src/admin/javascripts/mock-e2e.js.coffee
+++ src/admin/javascripts/mock-e2e.js.coffee
@@ -659,7 +659,7 @@
            first_name: "Joe"
            position: 1
            status: "queueing"
-           due_time: moment('2015-04-17 10:00')
+           due_time: moment('2015-04-30 10:00')
            _links:
              self:
                href: "http://www.bookingbug.com/api/v1/queuers/1"
@@ -670,8 +670,11 @@
                href: "http://www.bookingbug.com/api/v1/123/members/123456{?embed}"
                templated: true
              space:
-              href: "http://www.bookingbug.com/api/v1/123/spaces/300"
-              templated: true
+               href: "http://www.bookingbug.com/api/v1/123/spaces/300"
+               templated: true
+               company:
+                 href: "http://www.bookingbug.com/api/v1/public/companies/123"
+                 templated: true
            }
            {
              service_name: "Extreme yoga consultation"
@@ -680,10 +683,10 @@
              first_name: "Jane"
              position: 2
              status: "queueing"
-             due_time: moment('2015-04-17 10:30')
+             due_time: moment('2015-04-30 10:30')
              _links:
                self:
-                 href: "http://www.bookingbug.com/api/v1/queuers/240"
+                 href: "http://www.bookingbug.com/api/v1/public/queuers/240"
                service:
                  href: "http://www.bookingbug.com/api/v1/123/services/30063"
                  templated: true
@@ -691,8 +694,11 @@
                  href: "http://www.bookingbug.com/api/v1/123/members/223456{?embed}"
                  templated: true
                space:
-                href: "http://www.bookingbug.com/api/v1/123/spaces/301"
-                templated: true
+                 href: "http://www.bookingbug.com/api/v1/123/spaces/301"
+                 templated: true
+               company:
+                 href: "http://www.bookingbug.com/api/v1/public/companies/123"
+                 templated: true
            }
            {
              service_name: "Chess gymnastics consultation"
@@ -701,10 +707,10 @@
              first_name: "Shanikwa"
              position: 3
              status: "queueing"
-             due_time: moment('2015-04-17 11:00')
+             due_time: moment('2015-04-30 11:00')
              _links:
                self:
-                 href: "http://www.bookingbug.com/api/v1/queuers/176"
+                 href: "http://www.bookingbug.com/api/v1/public/queuers/176"
                service:
                  href: "http://www.bookingbug.com/api/v1/123/services/30063"
                  templated: true
@@ -712,12 +718,15 @@
                  href: "http://www.bookingbug.com/api/v1/123/members/323456{?embed}"
                  templated: true
                space:
-                href: "http://www.bookingbug.com/api/v1/123/spaces/302"
-                templated: true
+                 href: "http://www.bookingbug.com/api/v1/123/spaces/302"
+                 templated: true
+               company:
+                 href: "http://www.bookingbug.com/api/v1/public/companies/123"
+                 templated: true
            }
          ]
    $httpBackend.whenGET('http://www.bookingbug.com/api/v1/admin/queuers').respond(queuers)
-   $httpBackend.whenGET('http://www.bookingbug.com/api/v1/queuers/1').respond(queuers._embedded.queuers[0])
+   $httpBackend.whenGET('http://www.bookingbug.com/api/v1/public/queuers/1').respond(queuers._embedded.queuers[0])

    queuer_schema =
      form: [


Pick up .ist files in acceptance tests

Pick up .ist files in acceptance tests

--- services/clsi/test/acceptance/coffee/helpers/Client.coffee
+++ services/clsi/test/acceptance/coffee/helpers/Client.coffee
@@ -46,7 +46,7 @@
 					return "#{entity}/#{subEntity}"
 			else if stat.isFile() and entity != "output.pdf"
 				extension = entity.split(".").pop()
-				if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex"].indexOf(extension) > -1
+				if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex", "ist", "md", "Rmd"].indexOf(extension) > -1
 					resources.push
 						path: entity
 						content: fs.readFileSync("#{baseDirectory}/#{directory}/#{entity}").toString()


re-queried the dom for hidden items

re-queried the dom for hidden items

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -206,8 +206,11 @@
                         # trigger the afterDecompress callback
                         o.opts.onAfterDecompress(_this) if $.isFunction(o.opts.onAfterDecompress)

+                        # query again the dom and store it again in a variable
+                        hiddenItems = holder.find 'ul > li'
+
                         # remove the holder if there is no more item left.
-                        if holder.find('ul > li').length is 0
+                        if hiddenItems.length is 0
                             holder.remove()

                             # delete the reference since the holder element have been remove already


fix bug with bad file/all view

fix bug with bad file/all view

--- backend/db.coffee
+++ backend/db.coffee
@@ -353,6 +353,24 @@
             callback null, body.last_seq

     # TODO find a better module for this.
+    pickViewToCopy: (client, model, callback) ->
+        urlPath = "cozy/_design/#{model}"
+        log.debug "Getting design doc #{model} from remote"
+        client.get urlPath, (err, res, designdoc) ->
+            if err
+                callback err
+            else if designdoc.error
+                callback new Error designdoc.error
+            else if designdoc?.views?['files-all']
+                callback null, 'files-all'
+            else if designdoc?.views?.all
+                callback null, 'all'
+            else
+                # TODO : may be create it ourself
+                callback new Error 'install files app on cozy'
+
+
+    # TODO find a better module for this.
     copyViewFromRemote: (model, callback) ->
         remoteConfig = config.getConfig()
         deviceName = config.getDeviceName()
@@ -360,19 +378,24 @@
         client = request.newClient remoteConfig.url
         client.setBasicAuth deviceName, remoteConfig.devicePassword

-        urlPath = "cozy/_design/#{model}/_view/all/"
-        log.debug "Getting latest #{model} documents from remote"
-        client.get urlPath, (err, res, body) ->
+        @pickViewToCopy client, model, (err, viewName) ->
             return callback err if err
-            return callback null unless body.rows?.length
-            async.eachSeries body.rows, (doc, cb) ->
-                doc = doc.value
-                db.put doc, new_edits: false, (err, file) ->
-                    return callback err if err
-                    cb()
-            , ->
-                log.debug "#{body.rows.length} docs retrieved for #{model}."
-                callback()
+
+            urlPath = "cozy/_design/#{model}/_view/#{viewName}/"
+            log.debug "Getting latest #{model} documents from remote"
+            client.get urlPath, (err, res, body) ->
+                return callback err if err
+                return callback null unless body.rows?.length
+                async.eachSeries body.rows, (doc, cb) ->
+                    doc = doc.value
+                    db.put doc, new_edits: false, (err) ->
+                        if err
+                            log.error 'failed to copy one doc'
+                            log.error err
+                        cb null # keep copying other docs
+                , (err) ->
+                    log.debug "#{body.rows.length} docs retrieved for #{model}."
+                    callback err

     # TODO find a better module for this.
     replicateToRemote: (callback) ->


style fix

style fix

--- server/middlewares/authentication.coffee
+++ server/middlewares/authentication.coffee
@@ -49,7 +49,7 @@
             next makeError 401, 'error otp invalid code'
         else
             index = codes.indexOf(parseInt req.body.authcode)
-            if index == -1 # invalid code
+            if index is -1 # invalid code
                 next makeError 401, 'error otp invalid code'
             else
                 disableRecoveryCode user, codes, index, (err) ->


destroy back buttons instead of toggle visibility

cleans up the layer list. side effects?

--- vnc-example01.framer/modules/ViewNavigationController.coffee
+++ vnc-example01.framer/modules/ViewNavigationController.coffee
@@ -38,7 +38,7 @@
 		@history = []
 		@animationOptions = @options.animationOptions or ANIMATION_OPTIONS
 		@initialViewName  = @options.initialViewName  or INITIAL_VIEW_NAME
-		@backButton       = @options.backButton ?= true
+		@backButton       = @options.backButton       ?= true
 		@backButtonFrame  = @options.backButtonFrame  or BACK_BUTTON_FRAME
 		@debugMode        = @options.debugMode        or DEBUG_MODE

@@ -124,7 +124,7 @@

 	removeBackButton: (view) ->
 		Utils.delay 0, =>
-			view.subLayersByName(BACKBUTTON_VIEW_NAME)[0].visible = false
+			view.subLayersByName(BACKBUTTON_VIEW_NAME)[0].destroy()

 	back: () ->
 		@transition(@_getLastHistoryItem(), direction = DIR.LEFT, switchInstant = false, preventHistory = true)


Fix syntax error in apiKeys.coffee

Fix syntax error in apiKeys.coffee

--- evesrp/static/js/apiKeys.coffee
+++ evesrp/static/js/apiKeys.coffee
@@ -18,7 +18,7 @@
         # TODO: WTH am I redoing a search I just did (for .copy-btn)?
         for btn in $copyButtons.find '.copy-btn'
             (jQuery btn).tooltip 'destroy'
-        $oldRows.remove ()
+        $oldRows.remove()

         if data.api_keys.length != 0
             $newRows = jQuery (apiKeyTemplate data)


Match the new Github.repo.read signature (it includes the sha)

--- scripts/gh-book/binary-file.coffee
+++ scripts/gh-book/binary-file.coffee
@@ -11,7 +11,10 @@
     initialize: (options) ->
       @mediaType = options.mediaType if options.mediaType

-    parse: (bytes) ->
+    parse: (json) ->
+
+      @sha = json.sha
+      bytes = json.content

       # The result of a Github PUT is an object instead of the new state of the model.
       # Basically ignore it.


Add campaign statistics method

Add campaign statistics method

--- scripts/mailchimp-subscribe.coffee
+++ scripts/mailchimp-subscribe.coffee
@@ -11,6 +11,7 @@
 # Commands:
 #   hubot subscribe <email> - Add email to list
 #   hubot unsubscribe <email> - Remove email from list
+#   hubot mailchimp - Get statistics from latest mailing
 #
 # Author:
 #   max, lmarburger, m-baumgartner, sporkmonger
@@ -25,6 +26,8 @@
     subscribeToList message
   robot.respond /\bunsubscribe (.+@.+)/i, (message) ->
     unsubscribeFromList message
+  robot.respond /\bmailchimp/i, (message) ->
+    latestCampaign

 subscribeToList = (message) ->
   emailAddress = message.match[1]
@@ -71,3 +74,29 @@
       message.send "Uh oh, something went wrong: #{error.message}"
     else
       message.send "You successfully unsubscribed #{emailAddress}."
+
+latestCampaign = (message) ->
+
+  try
+    api = new MailChimpAPI(apiKey,
+      version: "1.3"
+      secure: false
+    )
+  catch error
+    console.log error.message
+    return
+
+  api.campaigns { start: 0, limit: 1 }, (error, data) ->
+    if error
+      message.send "Uh oh, something went wrong: #{error.message}"
+    else
+      # Get the first campaign in the list
+      cid = data['data'][0]['id']
+      campaign_name = data['data'][0]['title']
+
+      api.campaignStats { cid : cid }, (error, data) ->
+        if error
+          message.send "Uh oh, something went wrong: #{error.message}"
+        else
+          stats = data['data']
+          message.send "Last campaign \"#{campaign_name}\" was sent to #{stats['emails_sent']} subscribers (#{stats['unique_opens']} opened, #{stats['unique_clicks']} clicked, #{stats['unsubscribes']} unsubscribed)"


Supply needed router names for subcomponent nav

--- src/components/reference-book/index.cjsx
+++ src/components/reference-book/index.cjsx
@@ -48,7 +48,8 @@

   renderNavbarControls: ->
     return null unless CourseStore.isTeacher(@state.courseId)
-    <TeacherContentToggle onChange={@setTeacherContent} />
+
+    <TeacherContentToggle isShowing={@state.isShowingTeacherContent} onChange={@setTeacherContent} />

   renderBook: ->
     {courseId, ecosystemId} = @state
@@ -56,6 +57,8 @@
     <ReferenceBook
         navbarControls={@renderNavbarControls()}
         section={@state.section}
+        pageNavRouterLinkTarget='viewReferenceBookSection'
+        menuRouterLinkTarget='viewReferenceBookSection'
         className={classnames('is-teacher': @state.isShowingTeacherContent)}
         dataProps={@getCourseDataProps(courseId) if courseId}
         ecosystemId={ecosystemId}


Modify comment.

Modify comment.

--- src/5.test.1.coffee
+++ src/5.test.1.coffee
@@ -5,7 +5,8 @@
 ###

 class npmWishlist.Test
-    # `allCount` and `endedCount` are redundant, but needed (for caching) ==========[
+    # `allCount` and `endedCount` refer to all descendant tests including itself.
+    # Both are redundant, but needed (for performance) ==========[
     constructor: (@description = "") ->
         @_children = []
         @fun = =>
@@ -104,6 +105,7 @@
         r
     getAncestorsAndSelf: ->
         [@].concat(@getAncestors())
+    # all descendant tests including itself
     getAll: ->
         r = []
         r.push(@)


Fix framebuffer tests.

Fix framebuffer tests.

--- test/framebuffer_test.coffee
+++ test/framebuffer_test.coffee
@@ -7,7 +7,7 @@
 chai.use sinonChai

 FrameBuffer = require '../lib/framebuffer.coffee'
-message = require '../lib/message'
+wire = require '../lib/wire'

 createFrame = (frameId, payload) ->
   frame = new Buffer(4 + 4 + payload.length)
@@ -20,34 +20,34 @@

   it 'should parse a single, full frame', ->
     frameBuffer = new FrameBuffer()
-    data = createFrame message.FRAME_TYPE_RESPONSE, 'OK'
+    data = createFrame wire.FRAME_TYPE_RESPONSE, 'OK'
     frames = frameBuffer.consume data

     [frameId, payload] = frames.pop()
-    frameId.should.eq message.FRAME_TYPE_RESPONSE
+    frameId.should.eq wire.FRAME_TYPE_RESPONSE
     payload.toString().should.eq 'OK'

   it 'should parse two full frames', ->
     frameBuffer = new FrameBuffer()

-    firstFrame = createFrame message.FRAME_TYPE_RESPONSE, 'OK'
-    secondFrame = createFrame message.FRAME_TYPE_ERROR,
+    firstFrame = createFrame wire.FRAME_TYPE_RESPONSE, 'OK'
+    secondFrame = createFrame wire.FRAME_TYPE_ERROR,
       JSON.stringify {shortname: 'localhost'}

     frames = frameBuffer.consume Buffer.concat [firstFrame, secondFrame]
     frames.length.should.eq 2

     [frameId, data] = frames.shift()
-    frameId.should.eq message.FRAME_TYPE_RESPONSE
+    frameId.should.eq wire.FRAME_TYPE_RESPONSE
     data.toString().should.eq 'OK'

     [frameId, data] = frames.shift()
-    frameId.should.eq message.FRAME_TYPE_ERROR
+    frameId.should.eq wire.FRAME_TYPE_ERROR
     data.toString().should.eq JSON.stringify {shortname: 'localhost'}

   it 'should parse frame delivered in partials', ->
     frameBuffer = new FrameBuffer()
-    data = createFrame message.FRAME_TYPE_RESPONSE, 'OK'
+    data = createFrame wire.FRAME_TYPE_RESPONSE, 'OK'

     # First frame is 10 bytes long. Don't expect to get anything back.
     frames = frameBuffer.consume data[0...3]
@@ -63,8 +63,8 @@

   it 'should parse multiple frames delivered in partials', ->
     frameBuffer = new FrameBuffer()
-    first = createFrame message.FRAME_TYPE_RESPONSE, 'OK'
-    second = createFrame message.FRAME_TYPE_RESPONSE, '{}'
+    first = createFrame wire.FRAME_TYPE_RESPONSE, 'OK'
+    second = createFrame wire.FRAME_TYPE_RESPONSE, '{}'
     data = Buffer.concat [first, second]

     # First frame is 10 bytes long. Don't expect to get anything back.
@@ -85,7 +85,7 @@

   it 'empty internal buffer when all frames are consumed', ->
     frameBuffer = new FrameBuffer()
-    data = createFrame message.FRAME_TYPE_RESPONSE, 'OK'
+    data = createFrame wire.FRAME_TYPE_RESPONSE, 'OK'

     frame = frameBuffer.consume data
     expect(frameBuffer._buffer).to.be.null


Cleaned up some of the code

Cleaned up some of the code

--- src/backside/reg_finder.coffee
+++ src/backside/reg_finder.coffee
@@ -1,15 +1,14 @@
 `

-var Onedrive_keys = ['\\Software\\Microsoft\\Windows\\CurrentVersion\\Run']
-var key_array_length = Onedrive_keys.length;
+var Onedrive_key = ['\\Software\\Microsoft\\OneDrive']
+var key_array_length = Onedrive_key.length;
 for (var i= 0; i < key_array_length; i++)  {
     var Winreg = require('winreg')
     ,   regKey = new Winreg({
-          hive: Winreg.HKCU,                                          // HKEY_CURRENT_USER
-          key:  '\\Software\\Microsoft\\OneDrive' // key containing autostart programs
+          hive: Winreg.HKCU,                                // HKEY_CURRENT_USER
+          key:  '\\Software\\Microsoft\\OneDrive' // key containing Onedrive
         })

-    // list autostart programs
     regKey.values(function (err, items) {
       if (err)
         console.log('ERROR: '+err);


#246 - Get languages working

#246 - Get languages working

--- source/scripts/views/tab/language.coffee
+++ source/scripts/views/tab/language.coffee
@@ -1,4 +1,4 @@
-
+Setting = require '../../models/setting'
 Tab = require '../settings_tab'

 language = new Tab
@@ -7,10 +7,17 @@

   selector: '.language'

-  events: {}
+  events:
+    'click a': 'setLanguage'

   methods: []

   load: ->
+    el = $ "[data-value=#{ Setting.language }]"
+    el.addClass 'selected'
+
+  setLanguage: (event) ->
+    Setting.language = $(this).data 'value'
+    document.location.reload()

 module.exports = language


Guard for atom windows w/ no webContents

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -87,7 +87,7 @@
       hash: encodeURIComponent(JSON.stringify(loadSettings))

   getLoadSettings: ->
-    if @browserWindow.webContents.loaded
+    if @browserWindow.webContents?.loaded
       hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
       JSON.parse(decodeURIComponent(hash))


tests passing

tests passing

--- spec/javascripts/lunr_search_spec.coffee
+++ spec/javascripts/lunr_search_spec.coffee
@@ -31,7 +31,7 @@
     @search.search("first")
     post = @search.$entries.children()
     content = post.html().replace( new RegExp( "\>[\n\t ]+\<" , "g" ) , "><" ).trim()
-    (expect content).toBe('''
<time datetime="April/19/2013" pubdate="">April/19/2013</time>first post
tagged: tag_one,tag_two | category: first_category

''')
+    (expect content).toBe('''
<time datetime="April/19/2013" pubdate="">April/19/2013</time>first post
tagged: tag_one,tag_two | category: first_category

''')

   it "displays Nothing found message when there is no result", ->
     @search.search("hgkflgfkgfgo")


Typo fix.

Typo fix.

--- lib/chat.coffee
+++ lib/chat.coffee
@@ -1154,7 +1154,7 @@
   # @private
   # @nodoc
   finish : () ->
-    if @closeCB and !@fineshed
+    if @closeCB and !@finished
       @finished = true
       @closeCB()


Fix bug causing constant refreshing of custom commands

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -185,7 +185,7 @@
         deviceCustomCommands[k] = v._timestamp for k,v of device._customCommands

         for cmd in customCommands.getDeviceCustomCommandNames(currentRequest.session.deviceId)
-          if not (deviceCustomCommands[cmd]?._timestamp < lastBootstrap)
+          if not deviceCustomCommands[cmd]? or deviceCustomCommands[cmd] < lastBootstrap
             # Increment timestamp by one millisecond to ensure it runs after any refresh task
             _tasks.push({device: currentRequest.session.deviceId, name: 'customCommand', command: "#{cmd} init", timestamp: new Date(now.getTime() + 1)})
           delete deviceCustomCommands[cmd]


Syntax error tests look for error on stdout now.

--- test/test_commandline.coffee
+++ test/test_commandline.coffee
@@ -369,7 +369,8 @@
                     return undefined

                 'output': (error, stdout, stderr) ->
-                    assert.isNotEmpty(stderr)
+                    console.log(error, stdout, stderr)
+                    assert.isNotEmpty(stdout)

         'with option q not set':
             'and no errors occured':
@@ -388,6 +389,6 @@
                     return undefined

                 'output': (error, stdout, stderr) ->
-                    assert.isNotEmpty(stderr)
+                    assert.isNotEmpty(stdout)

 }).export(module)


don't deep clone into shallow defaults

don't deep clone into shallow defaults

--- src/coffee/dom/generics.coffee
+++ src/coffee/dom/generics.coffee
@@ -105,7 +105,7 @@
           styles: {}
           events: {}

-        OJ.extend defaults, options, true
+        OJ.extend defaults, options, false
         ret = OJ.element tag, defaults.props, defaults.styles, defaults.events, defaults.text


Fix typos and issues related to unobserving array mutations.

--- src/adapters.coffee
+++ src/adapters.coffee
@@ -45,8 +45,9 @@
         map.pointers[ref].push keypath

   unobserveMutations: (obj, ref, keypath) ->
-    if keyapths = @weakReference(obj).pointers?[ref]
-      keypaths.splice keypaths.indexOf(keypath), 1
+    if Array.isArray obj and obj[@id]?
+      if keypaths = @weakReference(obj).pointers?[ref]
+        keypaths.splice keypaths.indexOf(keypath), 1

   subscribe: (obj, keypath, callback) ->
     callbacks = @weakReference(obj).callbacks
@@ -71,7 +72,7 @@
   unsubscribe: (obj, keypath, callback) ->
     callbacks = @weakmap[obj[@id]].callbacks[keypath]
     callbacks.splice callbacks.indexOf(callback), 1
-    @observeMutations obj[keypath], obj[@id], keypath
+    @unobserveMutations obj[keypath], obj[@id], keypath

   read: (obj, keypath) ->
     obj[keypath]


Correct typo in console warning message

Correct typo in console warning message

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -71,7 +71,7 @@

   loadPackage: (id, options) ->
     if @isPackageDisabled(id)
-      return console.warn("Tried to load disabled packaged '#{id}'")
+      return console.warn("Tried to load disabled package '#{id}'")

     if path = @resolvePackagePath(id)
       return pack if pack = @getLoadedPackage(id)


Fix exception when pane items with no view are removed from the pane

--- src/app/pane.coffee
+++ src/app/pane.coffee
@@ -124,7 +124,7 @@
       viewClass = item.getViewClass()
       otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass
       unless otherItemsForView.length
-        @viewsByClassName[viewClass.name].remove()
+        @viewsByClassName[viewClass.name]?.remove()
         delete @viewsByClassName[viewClass.name]

   viewForItem: (item) ->


Adjusted multi-array controller init

Adjusted multi-array controller init

--- packages/emberella/lib/controllers/multi_array_controller.coffee
+++ packages/emberella/lib/controllers/multi_array_controller.coffee
@@ -23,8 +23,9 @@
 ###
 Emberella.MultiArrayController = Ember.ArrayController.extend
   init: ->
+    ret = @_super()
     @_subArraysDidChange()
-    @_super()
+    ret

   ###
     An array of strings describing the names of array controllers to include


Add JAccount to known types, kudos goes to Chris \o/

--- server/app/bongo/models/tag.coffee
+++ server/app/bongo/models/tag.coffee
@@ -62,7 +62,7 @@
         targetType  : JAccount
         as          : 'follower'
       content       :
-        targetType  : [JCodeSnip, JApp, JStatusUpdate]
+        targetType  : [JCodeSnip, JApp, JStatusUpdate, JAccount]
         as          : 'post'
       # content       :
       #   targetType  : [JCodeSnip, JAccount]


Fixup tests to play nice with the node-qunit runner.

 - There were some weird interactions where using the test context object as the namespace kept previous test's objects in the namespace, so getRelatedModel would return the previous test's model classes, and the instanceof checks would fail. By forcing a new namespace for each test the issue is solved. I believe the browser based QUnit uses a fresh object for each run, so the tests passed in the browser without these modifications.

--- tests/batman/model/associations/has_many_test.coffee
+++ tests/batman/model/associations/has_many_test.coffee
@@ -3,9 +3,10 @@

 QUnit.module "Batman.Model hasMany Associations"
   setup: ->
-    namespace = this
+    Batman.currentApp = null
+    namespace = {}

-    class @Store extends Batman.Model
+    namespace.Store = class @Store extends Batman.Model
       @encode 'id', 'name'
       @hasMany 'products', namespace: namespace

@@ -14,7 +15,7 @@
         name: "Store One"
         id: 1

-    class @Product extends Batman.Model
+    namespace.Product = class @Product extends Batman.Model
       @encode 'id', 'name', 'store_id'
       @belongsTo 'store', namespace: namespace
       @hasMany 'productVariants', namespace: namespace
@@ -42,7 +43,7 @@
           product_id:3
         }]

-    class @ProductVariant extends Batman.Model
+    namespace.ProductVariant = class @ProductVariant extends Batman.Model
       @encode 'price'
       @belongsTo 'product', namespace: namespace

@@ -121,10 +122,10 @@
   @Store.find 1, (err, store) =>
     source = '

'
     context = Batman(store: store)
-    helpers.render source, context, (node) =>
-      equal node.children().get(0).innerHTML, 'Product One'
-      equal node.children().get(1).innerHTML, 'Product Two'
-      equal node.children().get(2).innerHTML, 'Product Three'
+    helpers.render source, context, (node, view) =>
+      equal node.children().get(0)?.innerHTML, 'Product One'
+      equal node.children().get(1)?.innerHTML, 'Product Two'
+      equal node.children().get(2)?.innerHTML, 'Product Three'

       addedProduct = new @Product(name: 'Product Four', store_id: store.id)
       addedProduct.save (err, savedProduct) ->


Make _getGatewayCallbackURL an instance method

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -459,7 +459,7 @@
 	_getProfileURL: =>
 		HOST_URL + '/api/checkout/pub/profiles/'

-	_getGatewayCallbackURL = ->
+	_getGatewayCallbackURL: =>
 		HOST_URL + '/checkout/gatewayCallback/{0}/{1}/{2}'


Lower resolution torus by default

Lower resolution torus by default

This solves warnings on my machine where torus is used.

It's also more sane. Previous numbers were crazy high for most cases.

--- lib/assets/javascripts/jax/builtin/meshes/torus.js.coffee
+++ lib/assets/javascripts/jax/builtin/meshes/torus.js.coffee
@@ -18,8 +18,8 @@
   constructor: (options) ->
     @innerRadius = 0.6
     @outerRadius = 1.8
-    @sides = 64
-    @rings = 128
+    @sides = 16
+    @rings = 32
     super options

   init: (vertices, colors, texes, normals) ->


Improved server logging

Improved server logging

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -101,7 +101,6 @@
   for ver in versions
     p = ver.split '='
     currAddons[p[0]] = p[1]
-  console.log currAddons
   #check against server versions
   latestAddons =ServerAddons.find().fetch()
   for addon in latestAddons
@@ -323,11 +322,11 @@
       switch splitMsg[0]
         when "init"
           if splitMsg[1] isnt serverPassword
-            console.log " -> auth fail"
+            log.info "[SERVER] #{serverObj.ip} failed auth"
             ws.send 'authFail'
             return
           if splitMsg[4] isnt serverVersion
-            console.log " -> out of date ("+splitMsg[4]+") updating to "+serverVersion
+            log.info "[SERVER] #{serverObj.ip} Old version (#{splitMsg[4]}) updating (#{serverVersion})"
             ws.send 'outOfDate|'+getBundleDownloadURL("s"+serverVersion+".zip")
             return
           serverObj.maxLobbies = parseInt(splitMsg[2])
@@ -338,6 +337,7 @@
           serverObj.portRangeEnd = parseInt prange[1]
           if installStr is "|"
             console.log "server init "+serverObj.ip
+            log.info "[SERVER] #{serverObj.ip} Initialized"
             if !serverObj._id?
               ourID = servers.insert serverObj
               serverObj._id = ourID


JStackTemplate: simplfy the schema since we don't need most of these

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -53,12 +53,7 @@

     schema            :

-      rules           : [ Object ]
-      domains         : [ Object ]
       machines        : [ Object ]
-      extras          : [ Object ]
-
-      connections     : [ Object ]

       title           :
         type          : String
@@ -121,11 +116,7 @@
         title       : data.title
         config      : data.config      ? {}
         description : data.description ? ''
-        rules       : data.rules       ? []
-        domains     : data.domains     ? []
         machines    : data.machines    ? []
-        extras      : data.extras      ? []
-        connections : data.connections ? []
         accessLevel : data.accessLevel ? 'private'
         template    : generateTemplateObject data.template, data.templateDetails
         credentials : data.credentials ? []
@@ -223,8 +214,6 @@
      "KODING_BASE_PACKAGES" : "mc nodejs python sl screen",
      "DEBIAN_FRONTEND" : "noninteractive"
   },
-  rules: [],
-  domains: [],
   machines: [
     {
       "label" : "koding-vm-0",
@@ -237,7 +226,6 @@
       "source_ami" : "ami-a6926dce"
     }
   ],
-  connections: []
 }, function(err, template) {
   return console.log(err, template);
 });
@@ -259,21 +247,6 @@
         "KODING_BASE_PACKAGES" : "mc nodejs python sl",
         "DEBIAN_FRONTEND" : "noninteractive"
     },
-    "rules" : [],
-    "domains" : [
-        {
-            "domain" : "${username}.kd.io"
-        },
-        {
-            "domain" : "aws.${username}.kd.io"
-        },
-        {
-            "domain" : "rs.${username}.kd.io"
-        },
-        {
-            "domain" : "do.${username}.kd.io"
-        }
-    ],
     "machines" : [
         {
             "label" : "VM1 from Koding",
@@ -310,25 +283,6 @@
             "size" : "512mb"
         }
     ],
-    "extras" : [],
-    "connections" : [
-        {
-            "domains" : 0,
-            "machines" : 0
-        },
-        {
-            "domains" : 1,
-            "machines" : 0
-        },
-        {
-            "domains" : 2,
-            "machines" : 1
-        },
-        {
-            "domains" : 3,
-            "machines" : 2
-        }
-    ],
     "group" : "koding",
     "originId" : ObjectId("5196fcb0bc9bdb0000000011")
 }


JStackTemplate: validate template on ::create, ::update requests [completes #108551338]

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -123,6 +123,15 @@
     }

+  validateTemplate = (template, group) ->
+
+    plan = group.getAt 'config.plan'
+    return  unless plan # No plan, no pain.
+
+    ComputeProvider = require './computeprovider'
+    return ComputeProvider.validateTemplateContent template, plan
+
+
   @create = permit 'create stack template',

     success: revive
@@ -137,6 +146,9 @@

       unless data?.title
         return callback new KodingError 'Title required.'
+
+      if validationError = validateTemplate data.template, group
+        return callback validationError

       stackTemplate = new JStackTemplate
         originId    : delegate.getId()
@@ -296,6 +308,10 @@
       { template, templateDetails, rawContent } = data

       if template?
+
+        if validationError = validateTemplate template, group
+          return callback validationError
+
         data.template = generateTemplateObject \
           template, rawContent, templateDetails


write version number coming from koding to the manifest

--- kites/Deployer.kite/index.coffee
+++ kites/Deployer.kite/index.coffee
@@ -21,8 +21,8 @@

 class Deployment
   @pathToContainers = "/var/lib/lxc"
-  constructor: (@zipUrl, @name, @lxc) ->
-    @kitePath = path.join @pathToContainers, @name, "overlay", "opt", "kites"
+  constructor: (@kiteName, @version, @zipUrl) ->
+    @kitePath = path.join Deployment.pathToContainers, "#{@kiteName}-#{@version}", "overlay", "opt", "kites"

   createLxc: (callback) ->
     cmd = spawn "create-lxc", [@kiteName]
@@ -36,7 +36,7 @@
       callback()

   downloadAndExtractKite: (callback) ->
-    {kitePath, zipUrl, kiteName} = this
+    {kitePath, zipUrl, version, kiteName} = this
     fs.mkdirsSync(kitePath) ## TODO mkdir creates 0777, exists check

     filepath = path.join kitePath, "#{kiteName}.zip"
@@ -59,6 +59,10 @@

           cmd.on 'close', (code) ->
             console.log 'unzip process exited with code ', code
+            manifestFilePath = path.join(kitePath, kiteName, "manifest.json")
+            content = JSON.parse fs.readFileSync(manifestFilePath)
+            content.version = version
+            fs.writeFileSync manifestFilePath, JSON.stringify(content)
             callback()

   runKite: () ->
@@ -85,15 +89,15 @@

 console.log "deployer:", manifest.name

-manifest.name = "Deployer_#{os.hostname()}_#{uuid.v4()}"
+manifest.name = "Deployer_#{os.hostname()}_#{uuid}"
 kite.worker manifest,

   deploy: (options, callback) ->
-    {id, kiteName, version, zipUrl} = options
-    deployment = new Deployment(id, kiteName, zipUrl)
+    {kiteName, version, zipUrl} = options
+    deployment = new Deployment(kiteName, version, zipUrl)
     deployment.create-lxc () ->
       deployment.downloadAndExtractKite deployment.runKite.bind deployment

-    return callback null, "Hello, I'm #{name}! This is Deployer"
+    return callback null, "Hello, This is #{manifest.name}"