us-donations vis captions changed

us-donations vis captions changed

--- app/views.coffee
+++ app/views.coffee
@@ -346,7 +346,8 @@
         .width(800)
         .height(300)
         .marginLeft(200)
-        .title("Total US donations (blue, nominal US$) as percentage of US GDP (red, current US$)")
+        #.title("Total US donations (blue, nominal US$) as percentage of US GDP (red, current US$)")
+        .title("Total US foreign aid donations as percentage of US GDP")
         .valueTickFormat(d3.format(",.2%"))

@@ -384,9 +385,10 @@
           for y, o of data.gdp
             year = utils.date.yearToDate(y)

-            datum1.push
-              date : year
-              outbound : +(donated[y]) / o.value
+            if (donated[y]?)
+              datum1.push
+                date : year
+                outbound : +(donated[y]) / o.value

             datum2.push
               date : year
@@ -398,11 +400,17 @@
               outbound : +donated[y]

-          d3.select("#tseries1").datum(datum1).call(tschart1)
+          d3.select("#tseries1")
+            .datum(datum1).call(tschart1)
+            .append("div")
+              .attr("class", "credits")
+              .attr("style", "font-size:10px; color:#ccc; text-align:center;")
+              .text("Based on data from AidData.org and World Bank")
+
           d3.select("#tseries2").datum(datum2).call(tschart2)
-          d3.select("#tseries2").datum(datum3).call(tschart3)
-
-
-
-
-
+          d3.select("#tseries3").datum(datum3).call(tschart3)
+
+
+
+
+


collision: adding back the touchmove callback

--- app/_src/components/blog/estimation-survey/collision.coffee
+++ app/_src/components/blog/estimation-survey/collision.coffee
@@ -103,7 +103,7 @@
 			return

 		@svg.on("mousemove", () -> onInteraction(this) )
-		# @svg.on("touchmove", () -> onInteraction(this) )
+		@svg.on("touchmove", () -> onInteraction(this) )

 		collide = (node) ->
 			r = node.r + 0


Small error handling improvment.

Small error handling improvment.

--- app/assets/javascripts/app/controllers/tag_widget.js.coffee
+++ app/assets/javascripts/app/controllers/tag_widget.js.coffee
@@ -69,5 +69,6 @@
     container = document.getElementById( @attribute_id + '_tagsinput' )
     if reorder
       $('#' + @attribute_id + '_tagsinput').height( 20 )
+    return if !container
     height = container.scrollHeight
     $('#' + @attribute_id + '_tagsinput').height( height - 10 )


Improved logging.

Improved logging.

--- app/assets/javascripts/app/lib/app_post/websocket.js.coffee
+++ app/assets/javascripts/app/lib/app_post/websocket.js.coffee
@@ -34,7 +34,7 @@
 class _webSocketSingleton extends App.Controller
   @include App.LogInclude

-  queue: []
+  queue:                    []
   supported:                true
   lastSpoolMessage:         undefined
   sentSpoolFinished:        true
@@ -43,6 +43,7 @@
   connectionWasEstablished: false
   tryToConnect:             false
   backend:                  'websocket'
+  backend_port:             ''
   client_id:                undefined
   error:                    false

@@ -185,14 +186,16 @@
     if @backend is 'websocket'
       @ws = new window.WebSocket( protocol + window.location.hostname + '/ws' )
     else if @backend is 'websocketPort'
-      websocket_port = App.Config.get('websocket_port') || '6042'
-      @ws            = new window.WebSocket( protocol + window.location.hostname + ':' + websocket_port + '/' )
+      @backend_port = App.Config.get('websocket_port') || '6042'
+      @ws           = new window.WebSocket( protocol + window.location.hostname + ':' + @backend_port + '/' )
     else
       @_ajaxInit()

     # Set event handlers.
     @ws.onopen = =>
-      @log 'notice', "new websocket (#{@channel()}) connection open"
+      if @backend_port
+        port = ":#{@backend_port}"
+      @log 'notice', "new websocket (#{@channel()}#{port}) connection open"

       @connectionEstablished    = true
       @connectionWasEstablished = true
@@ -201,7 +204,7 @@
       App.Delay.clear('websocket-no-connection-try-reconnect-message', 'ws')
       if @error
         @error.hide()
-        @error = false
+        @error        = false
         @tryToConnect = false

       @auth()


revert error handler comment-out

revert error handler comment-out

--- app/assets/javascripts/initializers/error_handler.js.coffee
+++ app/assets/javascripts/initializers/error_handler.js.coffee
@@ -22,12 +22,12 @@
           applicationController.set('error', message)

     # The global error handler
-    # Ember.onerror = (error) ->
-    #   logError("\n" + error.message + "\n" + error.stack + "\n")
-    #   if ETahi.environment == 'development'
-    #     throw error
-    #   else
-    #     displayErrorMessage(error)
+    Ember.onerror = (error) ->
+      logError("\n" + error.message + "\n" + error.stack + "\n")
+      if ETahi.environment == 'development'
+        throw error
+      else
+        displayErrorMessage(error)

     $(document).ajaxError (event, jqXHR, ajaxSettings, thrownError) ->
       {type, url} = ajaxSettings


gallery filename extension bugfix;

gallery filename extension bugfix;

--- app/assets/javascripts/lolita/file-upload/gallery.js.coffee
+++ app/assets/javascripts/lolita/file-upload/gallery.js.coffee
@@ -88,7 +88,7 @@
     $(SLIDER_SELECTOR)

   _isImagePath: (filePath) ->
-    filePath.match(/\.(jpg|jpeg|gif|png|ico)$/)
+    filePath.match(/\.(jpg|jpeg|gif|png|ico)$/i)

   _getFileExtension: (filePath) ->
     filePath.substr(filePath.lastIndexOf('.') + 1)


#162 Throttle push

#162 Throttle push

--- app/assets/javascripts/oxalis/model/binary/pushqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pushqueue.coffee
@@ -1,12 +1,13 @@
 ### define
-../../../libs/array_buffer_socket : ArrayBufferSocket
-../../../libs/unit8array_builder : Uint8ArrayBuilder
+libs/array_buffer_socket : ArrayBufferSocket
+libs/unit8array_builder : Uint8ArrayBuilder
 ###

 class PushQueue

-  BATCH_LIMIT : 6
+  BATCH_LIMIT : 1
   BATCH_SIZE : 3
+  THROTTLE_TIME : 2000

   constructor : (@dataSetName, @cube, @dataLayerName, @tracingId, version, @sendData = true) ->
@@ -18,6 +19,8 @@
       cubeSize : 1 << @cube.BUCKET_SIZE_P
       annotationId : tracingId
       version : version
+
+    @push = _.throttle @pushImpl, @THROTTLE_TIME

   insert : (bucket) ->
@@ -54,7 +57,7 @@
       console.log(e)

-  push : ->
+  pushImpl : =>

     unless @sendData
       return


Lost diff again

Lost diff again

--- app/assets/javascripts/oxalis/view/abstract_tab_view.coffee
+++ app/assets/javascripts/oxalis/view/abstract_tab_view.coffee
@@ -17,7 +17,7 @@

       <% }) %>

-

+

       <% TABS.forEach(function(tab) { %>
         <div class="tab-pane" id="<%= tab.id %>">

       <% }) %>


only setup recurly once for subscription dashboard and apply async to make sure updates on pricing are applied

--- services/web/public/coffee/main/subscription-dashboard.coffee
+++ services/web/public/coffee/main/subscription-dashboard.coffee
@@ -2,7 +2,10 @@
 	"base"
 ], (App)->
 	SUBSCRIPTION_URL = "/user/subscription/update"
-	recurly.configure window.recurlyApiKey
+
+	setupReturly = _.once ->
+		recurly?.configure window.recurlyApiKey
+

 	App.controller "CurrenyDropdownController", ($scope, MultiCurrencyPricing, $q)->

@@ -14,7 +17,7 @@

 	App.controller "ChangePlanFormController", ($scope, $modal, MultiCurrencyPricing)->
-
+		setupReturly()

 		taxRate = window.taxRate

@@ -41,7 +44,9 @@
 			pricing = recurly.Pricing()
 			pricing.plan(planCode, { quantity: 1 }).done (price)->
 				totalPriceExTax = parseFloat(price.next.total)
-				$scope.prices[planCode] = $scope.currencySymbol + (totalPriceExTax + (totalPriceExTax * taxRate))
+				$scope.$evalAsync () ->
+					$scope.prices[planCode] = $scope.currencySymbol + (totalPriceExTax + (totalPriceExTax * taxRate))
+
 			price = ""


Update modal width.

Update modal width.

--- app/assets/javascripts/admin_reserve_view_manager.js.coffee
+++ app/assets/javascripts/admin_reserve_view_manager.js.coffee
@@ -94,7 +94,7 @@
       @modal.off("shown")
     )
     @modal.modal().css({
-      width: 'auto',
+      width: '600px',
       'margin-left': ->
         return -($(this).width() / 2);
       }


prevent upper resize if mark is too small

--- app/assets/javascripts/components/image-subject-viewer.cjsx
+++ app/assets/javascripts/components/image-subject-viewer.cjsx
@@ -214,12 +214,17 @@
     x = Math.round x
     y = Math.round y

+    currentMark = @state.selectedMark
+
     # enforce bounds
     if y < 0
       y = 0
       return

-    currentMark = @state.selectedMark
+    if currentMark.yLower - y < 50
+      currentMark.yUpper = -50 + currentMark.yLower
+      @setState selectedMark: currentMark
+      return

     dy = currentMark.yUpper - y
     yUpper_p = y
@@ -237,14 +242,11 @@
     console.log 'handleLowerResize()'
     {x,y} = @getEventOffset e

-    # console.log 'y: ', y
-
     x = Math.round x
     y = Math.round y

     currentMark = @state.selectedMark

-    # console.log 'mark height: ', currentMark.markHeight

     # enforce bounds
     if y > @state.imageHeight
@@ -252,10 +254,7 @@
       return

     if y - currentMark.yUpper < 50
-      console.log 'WARNING: mark too small'
       currentMark.yLower = 50 + currentMark.yUpper
-
-      console.log 'currentMark.yLower ', currentMark.yLower
       @setState selectedMark: currentMark
       return


ui tools: don't append spaces to csv list.

--- modules/gui/tools/widgets.coffee
+++ modules/gui/tools/widgets.coffee
@@ -23,7 +23,7 @@
                 if typeof values is 'string'
                     values = [values]

-                ele._val values.join ', '
+                ele._val values.join ','

     ##
     ##


Re-enable the minimap package

Re-enable the minimap package

--- atom/config.cson
+++ atom/config.cson
@@ -21,7 +21,6 @@
   'projectHome': '/Volumes/Data/Users/Lee/Source'
   'disabledPackages': [
     'autosave'
-    'minimap'
   ]
   'ignoredNames': [
     '.git'


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -10,6 +10,8 @@
   "autocomplete-plus":
     enableAutoActivation: false
     minimumWordLength: 1
+  "bracket-matcher":
+    highlightMatchingLineNumber: true
   core:
     customFileTypes:
       "source.ruby": [


Always use Atom as the default atom:// handler

--- atom/config.cson
+++ atom/config.cson
@@ -77,6 +77,7 @@
       "monokai"
     ]
     titleBar: "custom"
+    uriHandlerRegistration: "always"
   docblockr:
     align_tags: "shallow"
     extend_double_slash: false


Fix for emmet+django html compatibility?

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -34,3 +34,6 @@

 'atom-text-editor:not([mini])':
     'cmd-;': 'turbo-javascript:end-line'
+
+'atom-text-editor[data-grammar="text html django"]:not([mini])':
+    'tab': 'emmet:expand-abbreviation-with-tab'


Incorporate with existing error handling

--- app/assets/javascripts/initializers/error_handler.js.coffee
+++ app/assets/javascripts/initializers/error_handler.js.coffee
@@ -25,6 +25,7 @@
     # The global error handler
     Ember.onerror = (error) ->
       logError("\n" + error.message + "\n" + error.stack + "\n")
+      Bugsnag.notifyException(error)
       if ETahi.environment == 'development'
         throw error
       else


file icon into dropable link to file

file icon into dropable link to file

--- app/assets/javascripts/lolita/file-upload/gallery.js.coffee
+++ app/assets/javascripts/lolita/file-upload/gallery.js.coffee
@@ -161,6 +161,6 @@
     ""

   _insertableFileTag:  (filePath) ->
-    "" + @_getFileBasename(filePath) + ""
+    "" + @_getFileBasename(filePath) + ""

 window.LolitaFileUploadGallery = LolitaFileUploadGallery


Reloaded admin modal if any introduction record modified or created.

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -394,7 +394,8 @@
     buttons.Update.show()

   initIntroductionTab: ->
-    @modalTabs.forms["Introduction"].addSubView new IntroductionAdmin()
+    parentView = @modalTabs.forms["Introduction"]
+    parentView.addSubView new IntroductionAdmin { parentView }

 class IntroductionAdmin extends JView
@@ -430,6 +431,11 @@
     @on "IntroductionItemDeleted", (snippet) =>
       @snippets.splice @snippets.indexOf(snippet), 1
       @notFoundText.show() if @snippets.length is 0
+
+  reload: ->
+    parentView = @getOptions().parentView
+    parentView.addSubView new IntroductionAdmin { parentView }
+    @destroy()

   fetchData: ->
     KD.remote.api.JIntroSnippet.fetchAll (err, snippets) =>
@@ -451,6 +457,8 @@
       delegate: @
     }
     , data
+
+    @form.on "IntroductionFormNeedsReload", => @reload()

   viewAppended: ->
     super
@@ -578,13 +586,20 @@
       Title           :
         label         : "Title"
         type          : "text"
-        placeholder   : "Title of the introduction"
+        placeholder   : "Title of introduction group"
         defaultValue  : data.title
       ExpiryDate      :
         label         : "Expiry Date"
         type          : "text"
-        placeholder   : "Best before"
+        placeholder   : "Best before (YYYY-MM-DD)"
         defaultValue  : data.expiryDate
+        tooltip       :
+          title       : "Date format should be YYYY-MM-DD. e.g. 2013-05-03"
+          placement   : "right"
+          direction   : "center"
+          offset      :
+            top       : 2
+            left      : 0
       Overlay         :
         label         : "Overlay"
         type          : "select"
@@ -657,43 +672,22 @@
   insertParent: ->
     KD.remote.api.JIntroSnippet.create @createPostData(), =>
       @buttons["Save"].hideLoader()
-      log "new created"
+      @emit "IntroductionFormNeedsReload"

   insertChild: ->
     @parentData.addChild @createPostData(), =>
       @buttons["Save"].hideLoader()
-      log "child created"
+      @emit "IntroductionFormNeedsReload"

   updateParent: ->
     @parentData.update @createPostData(), =>
-      log "parent updated"
+      @emit "IntroductionFormNeedsReload"

   updateChild: ->
     data = @createPostData()
     data.oldIntroId = @parentData.introId
     @parentData.introductionItem.getData().updateChild data, =>
-      log "child updated"
-
-    # {
-    #   introTitle : postData.IntroTitle
-    #   introId    : postData.IntroId
-    #   snippet    : postData.Snippet
-    #   oldIntroId : @getData().IntroId
-    # }
-    # @getData().snippet.updateChild @createPostData(), =>
-      # introId    = "EDITED"
-      # snippet    = "alert"
-      # introTitle = "TITLE"
-      # @getDelegate().getData().updateChild {
-      #   oldIntroId : @getData().introId
-      #   introId
-      #   introTitle
-      #   snippet
-      # }, => @setData {
-      #     introTitle
-      #     introId
-      #     snippet
-      #   }
+      @emit "IntroductionFormNeedsReload"

   createPostData: ->
     {inputs}     = @


AccountSettings: disable referral system section for now

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -19,7 +19,7 @@
       items : [
         { slug : 'SSH',         title : "SSH keys",           listHeader: "Your SSH Keys",          listType: "keys" }
         # { slug : 'Keys',        title : "Koding Keys",        listHeader: "Your Koding Keys",       listType: "kodingKeys" }
-        { slug : 'Referral',    title : "Referral System",    listHeader: "Your Referral Options",  listType: "referralSystem" }
+        # { slug : 'Referral',    title : "Referral System",    listHeader: "Your Referral Options",  listType: "referralSystem" }
         # { slug : 'Credentials', title : "Credentials",        listHeader: "Your Credentials",       listType: "credentials" }
       ]
     danger  :


client: fix oauth login/register

client: fix oauth login/register

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -186,12 +186,13 @@
       isUserLoggedIn = KD.isLoggedIn()
       params = {isUserLoggedIn, provider}

-      KD.remote.api.JUser.authenticateWithOauth params, (err, resp)=>
+      (KD.getSingleton 'mainController').handleOauthAuth params, (err, resp)=>
         if err
           showError err
         else
           {account, replacementToken, isNewUser, userInfo} = resp
           if isNewUser
+            KD.getSingleton('router').handleRoute '/Register'
             @animateToForm "register"
             for own field, value of userInfo
               setValue field, value


Updating config.cson

Updating config.cson

--- config.cson
+++ config.cson
@@ -1,41 +1,5 @@
 "*":
-  "":
-    basic:
-      html:
-        text:
-          editor:
-            softWrap: true
-    source:
-      yaml:
-        editor:
-          softWrap: true
   core:
-    customFileTypes:
-      "source.ini": [
-        ".buckconfig"
-        ".flowconfig"
-      ]
-      "source.json": [
-        ".arcconfig"
-        "BUCK.autodeps"
-      ]
-      "source.lisp": [
-        ".emacs"
-        ".spacemacs"
-      ]
-      "source.python": [
-        "BUCK"
-      ]
-      "text.html.basic": [
-        "ejs"
-        "htm"
-        "html"
-        "kit"
-        "shtml"
-        "tmpl"
-        "tpl"
-        "xhtml"
-      ]
     disabledPackages: [
       "linter-markdown"
       "language-gfm"
@@ -49,8 +13,6 @@
     fontSize: 13
     showIndentGuide: true
     softWrap: true
-  "exception-reporting":
-    userId: "71ef2a62-823d-b379-0f88-bc92c164eedb"
   "file-watcher":
     promptWhenFileHasChangedOnDisk: false
   "language-archlinux": {}


Drop the forced tabstop-sizes in Ruby, because ugh

--- config.cson
+++ config.cson
@@ -120,9 +120,6 @@
 ".python.source":
 	editor:
 		softTabs: false
-".ruby.source":
-	editor:
-		tabLength: 2
 ".rust.source":
 	editor:
 		softTabs: false


increase timeouts for slow-starting kernels

--- src/smc-project/jupyter/test/supported-kernels.coffee
+++ src/smc-project/jupyter/test/supported-kernels.coffee
@@ -132,7 +132,7 @@

 describe 'test the julia kernel --', ->
-    @timeout(20000)
+    @timeout(90000)
     kernel = undefined
     it 'evaluate 4/3', (done) ->
         kernel = common.kernel('julia')
@@ -151,7 +151,7 @@

 describe 'test the system-wide R kernel --', ->
-    @timeout(60000)
+    @timeout(90000)
     kernel = undefined
     it 'evaluate sd(c(1,2,5))', (done) ->
         kernel = common.kernel('ir')
@@ -169,7 +169,7 @@
         kernel.close()

 describe 'test the sage R kernel --', ->
-    @timeout(60000)
+    @timeout(90000)
     kernel = undefined
     it 'evaluate sd(c(1,2,5))', (done) ->
         kernel = common.kernel('ir-sage')
@@ -187,7 +187,7 @@
         kernel.close()

 describe 'test the scala kernel --', ->
-    @timeout(30000)
+    @timeout(90000)
     kernel = undefined
     it 'matchTest', (done) ->
         kernel = common.kernel('scala211')


account: add isAuthorized method for checking third party authentication

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -198,6 +198,8 @@
           (signature Function)
         fetchOAuthInfo:
           (signature Function)
+        isAuthorized:
+          (signature String, Function)
         fetchFromUser:
           (signature String, Function)
         likeMember:
@@ -1369,6 +1371,18 @@
     else
       callback new KodingError 'Access denied'

+  isAuthorized: secure (client, name, callback)->
+    @fetchOAuthInfo client, (err, response)->
+
+      return callback err  if err
+
+      return callback null, no  unless response?[name]
+
+      return callback null, yes  if name isnt 'github'
+
+      return callback null, response[name].scope is 'repo'
+
+
   likeMember: permit 'like members',
     success: (client, nickname, callback)->
       JAccount.one { 'profile.nickname' : nickname }, (err, account)=>


SocialWorker: fix combinedAppStorage.appId.data field

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -950,7 +950,7 @@
     addStorage = (storage, options, callback) ->
       { appId, data } = options
       query = { $set : {} }
-      query.$set["storage.#{appId}.data"] = { data : data ? {} }
+      query.$set["storage.#{appId}.data"] = data ? {}
       storage.update query, (err) ->
         return callback err, storage


Express: profile static page old html removed

--- workers/social/lib/social/render/profile.coffee
+++ workers/social/lib/social/render/profile.coffee
@@ -40,208 +40,227 @@
     bgImg = "//i.embed.ly/1/display/crop?grow=false&width=90&height=90&key=94991069fb354d4e8fdb825e52d4134a&url=#{encodeURIComponent avatar}"

   prepareHTML = (scripts)->
+
     """
-
-    <!DOCTYPE html>
-
+    <!doctype html>
+

-      <title>#{encoder.XSSEncode title}</title>
+      <title>Koding</title>
       #{getStyles()}
-      #{getGraphMeta image: bgImg, title: title}

-

-

-
-
-
-

-

-

-

-

-

-

-

-

-

-                0
-                0
-                0
-

-

-

-                  Home
-

-

-                  Activity
-

-

-                  Topics
-

-

-                  Members
-

-

-                  Develop
-

-

-                  Apps
-

-

-

-

-

-                  0Invite Friends
-

-

-

-

-

-

-

-

-

-

-

-

-

-

-
-

-

-

-
-

-

-

-
-
-
-                      <button type="button" class="kdbutton kdwhitebtn profilefollowbtn w-loader"><canvas width="18" height="18"></canvas><canvas style="display: none;" width="18" height="18"></canvas>
-
-                        Follow
-                      </button>
-                      @#{encoder.XSSEncode nickname}
-

-
-

-
-

-

-
#{encoder.XSSEncode firstName} #{encoder.XSSEncode lastName}

-

#{if locationTags then encoder.XSSEncode locationTags[0] else 'Earth'}

-

-                          #{encoder.XSSEncode nickname}.kd.io
-                          member for #{if amountOfDays < 2 then 'a' else amountOfDays} day#{if amountOfDays > 1 then 's' else ''}.
-

-

-

-                            #{counts.followers} Followers
-

-

-                            #{counts.following} Following
-

-

-                            #{counts.likes} Likes
-

-

-                            Contact#{encoder.XSSEncode firstName}
-

-

-
-

-
#{encoder.XSSEncode about}

-

-

-

-

-                  #{if not isLoggedIn then addHomeLinkBar() else ''}
-

-

-

-

-

-                                      FILTER
-

-

-

-                                          Everything
-

-

-                                          Status Updates
-

-

-                                          Code Snippets
-

-

-

-

-

-                                      SORT
-

-

-

-                                          Most popular
-

-

-                                          Latest activity
-

-

-                                          Most activity
-

-

-

-

-

-                                      NEED HELP? Learn Personal feed
-

-

-

-

-

-

-

-

-                                      everything
-

-

-                                      statuses
-

-

-                                      codesnips
-

-

-

-
-

-                                          Everything
-

-
-

-

-

-

-                                              Loading...<canvas width="16" height="16"></canvas><canvas style="display: none;" width="16" height="16"></canvas>
-

-

-

-

-

-

-

-

-

-
-

-

-

+
+
       #{KONFIG.getConfigScriptTag {entryPoint: { slug : profile.nickname, type: "profile" }, roles:['guest'], permissions:[]}}
       #{scripts}
+

     """

+    # """
+
+    # <!DOCTYPE html>
+    #
+    #
+    #   <title>#{encoder.XSSEncode title}</title>
+    #   #{getStyles()}
+    #   #{getGraphMeta image: bgImg, title: title}
+    #
+    #
+
+    #

+    #

+    #
+    #
+    #
+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #             0
+    #             0
+    #             0
+    #

+    #

+    #

+    #               Home
+    #

+    #

+    #               Activity
+    #

+    #

+    #               Topics
+    #

+    #

+    #               Members
+    #

+    #

+    #               Develop
+    #

+    #

+    #               Apps
+    #

+    #

+    #

+    #

+    #

+    #               0Invite Friends
+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+
+    #

+    #

+    #

+    #
+    #

+    #

+    #

+    #
+    #
+    #
+    #                   <button type="button" class="kdbutton kdwhitebtn profilefollowbtn w-loader"><canvas width="18" height="18"></canvas><canvas style="display: none;" width="18" height="18"></canvas>
+    #
+    #                     Follow
+    #                   </button>
+    #                   @#{encoder.XSSEncode nickname}
+    #

+
+    #

+
+    #

+    #

+    #
#{encoder.XSSEncode firstName} #{encoder.XSSEncode lastName}

+    #

#{if locationTags then encoder.XSSEncode locationTags[0] else 'Earth'}

+    #

+    #                       #{encoder.XSSEncode nickname}.kd.io
+    #                       member for #{if amountOfDays < 2 then 'a' else amountOfDays} day#{if amountOfDays > 1 then 's' else ''}.
+    #

+    #

+    #

+    #                         #{counts.followers} Followers
+    #

+    #

+    #                         #{counts.following} Following
+    #

+    #

+    #                         #{counts.likes} Likes
+    #

+    #

+    #                         Contact#{encoder.XSSEncode firstName}
+    #

+    #

+
+    #

+    #
#{encoder.XSSEncode about}

+    #

+    #

+    #

+    #

+    #               #{if not isLoggedIn then addHomeLinkBar() else ''}
+    #

+    #

+    #

+    #

+    #

+    #                                   FILTER
+    #

+    #

+    #

+    #                                       Everything
+    #

+    #

+    #                                       Status Updates
+    #

+    #

+    #                                       Code Snippets
+    #

+    #

+    #

+    #

+    #

+    #                                   SORT
+    #

+    #

+    #

+    #                                       Most popular
+    #

+    #

+    #                                       Latest activity
+    #

+    #

+    #                                       Most activity
+    #

+    #

+    #

+    #

+    #

+    #                                   NEED HELP? Learn Personal feed
+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #                                   everything
+    #

+    #

+    #                                   statuses
+    #

+    #

+    #                                   codesnips
+    #

+    #

+    #

+    #
+    #

+    #                                       Everything
+    #

+    #
+    #

+    #

+    #

+    #

+    #                                           Loading...<canvas width="16" height="16"></canvas><canvas style="display: none;" width="16" height="16"></canvas>
+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+    #

+
+    #

+    #

+    #

+    #   #{KONFIG.getConfigScriptTag {entryPoint: { slug : profile.nickname, type: "profile" }, roles:['guest'], permissions:[]}}
+    #   #{scripts}
+    #
+    #
+    # """
+
   fetchScripts {bongoModels, client}, (err, scripts)->
     callback null, prepareHTML scripts


:bowling::eggplant: Updated in browser at strd6.github.io/editor

--- main.coffee
+++ main.coffee
@@ -20,7 +20,8 @@

   switch type
     when 144 # Note on
-      console.log note
+      state.toSet = note
+      state.moveNext = 2
     when 128 # Note off
       ;

@@ -65,6 +66,7 @@

 state =
   activeLine: 0
+  moveNext: 0

 updateViz = ->
   viz.draw(canvas)
@@ -84,6 +86,9 @@
   # TODO: This should be done in terms of context.currentTime
   track.update(osc.frequency, osc.gain, trackTime, dt, state)

+  state.activeLine += state.moveNext
+  state.moveNext = 0
+
 invariants = ->
   state.activeLine = state.activeLine % 16
   if state.activeLine < 0
@@ -99,12 +104,19 @@
   switch
     when keyCode is 8
       state.toSet = 255
+      state.moveNext = 1
+    when keyCode is 32
+      state.moveNext = 1
     when keyCode is 38
       state.activeLine -= 1
     when keyCode is 40
       state.activeLine += 1
+    when keyCode is 46
+      state.toSet = null
+      state.moveNext = 1
     when 48 <= keyCode <= 57
       state.toSet = keyCode - 48 + 4 * 12
+      state.moveNext = 1
 , false

 document.addEventListener "mousedown", (e) ->


Roundtrip with gist.

Roundtrip with gist.

--- main.coffee
+++ main.coffee
@@ -1,9 +1,12 @@
+# developer data (gitignored)
+dev = $blab.resource "dev"

 $("#widget-menu").menu select: (event, ui) ->
     console.log "ui type?", ui.item[0].innerHTML
     return

 $("#tabs").tabs()
+

 marked.setOptions
     renderer: new (marked.Renderer)
@@ -152,7 +155,7 @@
     figure: PlotXY
     table: Table
     markdown: Markdown
-
+    slider: Slider

 # user code

@@ -161,15 +164,17 @@

     console.log "######## pre-code ########"

-    # refresh sink
-    for sl of $blab.slider
-        $blab.slider[sl].update()
-        console.log $blab.slider[sl].stringify()
+    # update sources
+    for c of $blab.component
+        for i of $blab.component[c]
+            item = $blab.component[c][i]
+            if item.spec.isSource is "true"
+                item.update()

     # local copy of vars
     for sym of $blab.component.sheet
         $blab.component.sheet[sym].toLocal()
-
+
     console.log "######## user-code ########"

     fn = (A, x) ->
@@ -181,43 +186,84 @@

     console.log "######## post-code ########"

+    # local vars -> sheets
+    for sym of $blab.component.sheet
+        $blab.component.sheet[sym].fromLocal(eval(sym))
+
+    # update sinks
+    for c of $blab.component
+        for i of $blab.component[c]
+            item = $blab.component[c][i]
+            item.update() if item.spec.isSink is "true"
+
+    # update spec
     $blab.spec = {}
     for c of $blab.component
         $blab.spec[c] = {}
         for i of $blab.component[c]
-            $blab.spec[c][i] = $blab.component[c][i].spec
-            if c == "sheet"
-                $blab.component.sheet[i].fromLocal(eval(i))
-            else
-                $blab.component[c][i].update()
+            item = $blab.component[c][i]
+            $blab.spec[c][i] = item.spec

     console.log ">>>", JSON.stringify($blab.spec)
-
-
-loadgist = (gistid, filename) ->
-
-    $.ajax(
-        url: "https://api.github.com/gists/#{gistid}"
-        type: 'GET'
-        dataType: 'jsonp'
-
-    ).success((gistdata) ->
-        specs = JSON.parse(gistdata.data.files[filename].content)
-        build = (w) ->
-            $blab["component"][w] = {}
-            $blab["component"][w][spec.id] = new toolbox[w] spec for spec in specs[w]
-
+
+
+class App
+
+    token: dev.token
+    gistId: dev.gistId
+
+    constructor: ->
+
+        github = new Github
+            token: @token
+            auth: "oauth"
+
+        @gist = github.getGist(@gistId)
+
+        console.log "gist", @gist
+
+        @readGist()
+
+    readGist: ->
+        @gist.read((err, gist)=> @buildSpec(err,gist))
+
+    buildSpec: (err,gist) ->
+
+        specs = JSON.parse(gist.files["app.json"].content)
+
+        build = (item) ->
+            $blab["component"][item] = {}
+            for sym of specs[item]
+                $blab["component"][item][sym] = new toolbox[item] specs[item][sym]
+
         $blab["component"] = {}
-
         for spec of specs
             build(spec)

-        compute()
-
-    ).error (e) ->
-        # ajax error
-        return
-
-loadgist("f673df3f600fdeb17608", "app.json")
-
-
+        @updateSpec()
+        @saveGist($blab.spec)
+
+    saveGist: (json) ->
+
+        data =
+            "description": "the description for this gist"
+            "files":
+                "app2.json":
+                    "content": JSON.stringify($blab.spec)
+
+        @gist.update(data, (err, gist) -> console.log "save?", err, gist)
+
+    updateSpec: ->
+
+        $blab.spec = {}
+        for c of $blab.component
+            $blab.spec[c] = {}
+            for i of $blab.component[c]
+                item = $blab.component[c][i]
+                $blab.spec[c][i] = item.spec
+
+        console.log ">>>", JSON.stringify($blab.spec)
+
+new App
+
+


:win: Menus: replace three period with ellipsis

to be test on runtime

--- menus/win32.cson
+++ menus/win32.cson
@@ -4,9 +4,9 @@
     submenu: [
       { label: 'New &Window', command: 'application:new-window' }
       { label: '&New File', command: 'application:new-file' }
-      { label: '&Open File...', command: 'application:open-file' }
-      { label: 'Open Folder...', command: 'application:open-folder' }
-      { label: 'Add Project Folder...', command: 'application:add-project-folder' }
+      { label: '&Open File…', command: 'application:open-file' }
+      { label: 'Open Folder…', command: 'application:open-folder' }
+      { label: 'Add Project Folder…', command: 'application:add-project-folder' }
       { label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
       { type: 'separator' }
       { label: 'Se&ttings', command: 'application:show-settings' }
@@ -17,7 +17,7 @@
       { label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
       { type: 'separator' }
       { label: '&Save', command: 'core:save' }
-      { label: 'Save &As...', command: 'core:save-as' }
+      { label: 'Save &As…', command: 'core:save-as' }
       { label: 'Save A&ll', command: 'window:save-all' }
       { type: 'separator' }
       { label: '&Close Tab', command: 'core:close' }
@@ -119,7 +119,7 @@
       {
         label: 'Developer'
         submenu: [
-          { label: 'Open In &Dev Mode...', command: 'application:open-dev' }
+          { label: 'Open In &Dev Mode…', command: 'application:open-dev' }
           { label: 'Run &Atom Specs', command: 'application:run-all-specs' }
           { label: 'Run Package &Specs', command: 'window:run-package-specs' }
           { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }


Added tooltips to week summary graph in daily engagement

--- app/assets/javascripts/admin/daily_engagement.coffee
+++ app/assets/javascripts/admin/daily_engagement.coffee
@@ -90,7 +90,7 @@
     svgSurveys.append('g').attr('class', 'y axis').call(yAxis).append('text').attr('transform', 'rotate(-90)').attr('y', 6).attr('dy', '.71em').style('text-anchor', 'end').text 'Surveys'
     svgSurveys.append('path').datum(data.surveys).attr('class', 'surveys-line').attr 'd', line
     surveyPointElem = svgSurveys.append('g').attr('class', 'line-point')
-    surveyPoints = surveyPointElem.selectAll('circle').data( data.surveys ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.0).style('fill', 'white').style('stroke', 'red')
+    surveyPoints = surveyPointElem.selectAll('circle').data( data.surveys ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 2.0).style('fill', 'white').style('stroke', 'red')
     surveyPoints.on('mouseover', (d) -> document.getElementById('surveys'+d.index).style.display = 'block' )
     surveyPoints.on('mouseout', (d) -> document.getElementById('surveys'+d.index).style.display = 'none' )
     surveyTips = surveyPointElem.selectAll('text').data(data.surveys).enter().append('text').attr('dx', (d) -> x(d.date) - 50 ).attr('dy', (d) -> y(d.count) - 10).attr('display', 'none').attr('id', (d) -> 'surveys'+d.index ).html((d) -> ("<tspan style='font-weight:bold'>" + d.count + " new surveys</tspan> on " + d.date.toDateString()))
@@ -110,9 +110,21 @@
     svgWeek.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call xAxis
     svgWeek.append('g').attr('class', 'y axis').call(yAxis).append('text').attr('transform', 'rotate(-90)').attr('y', 6).attr('dy', '.71em').style('text-anchor', 'end').text 'Weekly Counts'
     svgWeek.append('path').datum(weeklyPosts).attr('class', 'posts-line').attr 'd', line
-    svgWeek.append('g').attr('class', 'line-point').selectAll('circle').data( weeklyPosts ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'steelblue')
+    weekPostElem = svgWeek.append('g').attr('class', 'line-point')
+    weekPostPoints = weekPostElem.selectAll('circle').data( weeklyPosts ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'steelblue')
+    weekPostPoints.on('mouseover', (d) -> document.getElementById('weekposts'+d.index).style.display = 'block' )
+    weekPostPoints.on('mouseout', (d) -> document.getElementById('weekposts'+d.index).style.display = 'none' )
+    weekPostTips = weekPostElem.selectAll('text').data(weeklyPosts).enter().append('text').attr('dx', (d) -> x(d.date) - 50 ).attr('dy', (d) -> y(d.count) - 10).attr('display', 'none').attr('id', (d) -> 'weekposts'+d.index ).html((d) -> ("<tspan style='font-weight:bold'>" + d.count + " new posts</tspan> on " + d.date.toDateString()))
     svgWeek.append('path').datum(weeklyUsers).attr('class', 'users-line').attr 'd', line
-    svgWeek.append('g').attr('class', 'line-point').selectAll('circle').data( weeklyUsers ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'green')
+    weekUserElem = svgWeek.append('g').attr('class', 'line-point')
+    weekUserPoints = weekUserElem.selectAll('circle').data( weeklyUsers ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'green')
+    weekUserPoints.on('mouseover', (d) -> document.getElementById('weekusers'+d.index).style.display = 'block' )
+    weekUserPoints.on('mouseout', (d) -> document.getElementById('weekusers'+d.index).style.display = 'none' )
+    weekUserTips = weekUserElem.selectAll('text').data(weeklyUsers).enter().append('text').attr('dx', (d) -> x(d.date) - 50 ).attr('dy', (d) -> y(d.count) - 10).attr('display', 'none').attr('id', (d) -> 'weekusers'+d.index ).html((d) -> ("<tspan style='font-weight:bold'>" + d.count + " new users</tspan> on " + d.date.toDateString()))
     svgWeek.append('path').datum(weeklySurveys).attr('class', 'surveys-line').attr 'd', line
-    svgWeek.append('g').attr('class', 'line-point').selectAll('circle').data( weeklySurveys ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'red')
+    weekSurveyElem = svgWeek.append('g').attr('class', 'line-point')
+    weekSurveyPoints = weekSurveyElem.selectAll('circle').data( weeklySurveys ).enter().append('circle').attr('cx', (d) -> x(d.date) ).attr('cy', (d) -> y(d.count) ).attr('r', 3.5).style('fill', 'white').style('stroke', 'red')
+    weekSurveyPoints.on('mouseover', (d) -> document.getElementById('weeksurveys'+d.index).style.display = 'block' )
+    weekSurveyPoints.on('mouseout', (d) -> document.getElementById('weeksurveys'+d.index).style.display = 'none' )
+    weekSurveyTips = weekSurveyElem.selectAll('text').data(weeklySurveys).enter().append('text').attr('dx', (d) -> x(d.date) - 50 ).attr('dy', (d) -> y(d.count) - 10).attr('display', 'none').attr('id', (d) -> 'weeksurveys'+d.index ).html((d) -> ("<tspan style='font-weight:bold'>" + d.count + " new surveys</tspan> on " + d.date.toDateString()))
     return


Can't spell

Can't spell

--- app/assets/javascripts/components/articles.js.coffee
+++ app/assets/javascripts/components/articles.js.coffee
@@ -12,7 +12,7 @@
 			className: 'articles'
 			React.DOM.h3
 				className: 'title'
-				'Send us a blog atricle that you would like to have anlized by a expert mood musician from ListenLoop.'
+				'Send us a blog article that you would like to have analyzed by a expert mood musician from ListenLoop.'
 			React.createElement ArticleForm, handleNewArticle: @addArticle
 			React.DOM.table
 				className: 'table table-bordered'


remove animation on tabs interaction

remove animation on tabs interaction

--- app/assets/javascripts/lib/components/tabs.js.coffee
+++ app/assets/javascripts/lib/components/tabs.js.coffee
@@ -38,12 +38,17 @@
           # Get padding (jquery box sizing bug - http://bugs.jquery.com/ticket/10413)
           padding = (parseInt(newTab.css('padding'), 10) * 2)

-          dropdown.css('height', (newTab.children().outerHeight() + padding))
+          # dropdown.css('height', (newTab.children().outerHeight() + padding))
+
+          # setTimeout ->
+          #   contentArea.find(tabSelected).addClass('active')
+          #   dropdown.css('opacity', '1')
+          # , 300

           setTimeout ->
             contentArea.find(tabSelected).addClass('active')
             dropdown.css('opacity', '1')
-          , 300
+          , 1

         # Stop the page jump
         false
@@ -53,4 +58,4 @@
       @dropdown = @parent.find('.js-tabs-container')
       bindEvents(@parent, @dropdown)

-    +


OHIE-131: Comprehensively test the metadata endpoint

--- test/integration/metadataAPITests.coffee
+++ test/integration/metadataAPITests.coffee
@@ -69,166 +69,652 @@
 describe "API Integration Tests", ->

   describe "Metadata REST Api Testing", ->
-
+
     before (done) ->
-      auth.setupTestUsers (err) ->
-        server.start apiPort: 8080, ->
+      server.start apiPort: 8080, ->
+        auth.setupTestUsers (err) ->
           authDetails = auth.getAuthDetails()
           done()

     after (done) ->
-      auth.cleanupTestUsers (err) ->
-        User.remove ->
+        server.stop ->
+          auth.cleanupTestUsers (err) ->
+            done()
+
+
+    # GET TESTS
+    describe "*getMetadata", ->
+
+      describe "Channels", ->
+
+        beforeEach (done) ->
+          (new Channel sampleMetadata.Channels[0]).save (err, channel) ->
+            return done err if err
+            done()
+
+        afterEach (done) ->
+          Channel.remove ->
+            done()
+
+        it  "should fetch channels and return status 200", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(200)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                res.body[0].Channels.length.should.equal 1
+                res.body[0].Channels[0].should.have.property "urlPattern", "test/sample"
+                done()
+
+      describe "Clients", ->
+
+        beforeEach (done) ->
+          (new Client sampleMetadata.Clients[0]).save (err, client) ->
+            return done err if err
+            done()
+
+        afterEach (done) ->
+          Client.remove ->
+            done()
+
+        it  "should fetch clients and return status 200", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(200)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                res.body[0].Clients.length.should.equal 1
+                res.body[0].Clients[0].should.have.property "name", "OpenMRS Ishmael instance"
+                done()
+
+      describe "Mediators", ->
+
+        beforeEach (done) ->
+          (new Mediator sampleMetadata.Mediators[0]).save (err, mediator) ->
+            return done err if err
+            done()
+
+        afterEach (done) ->
+          Mediator.remove ->
+            done()
+
+        it  "should fetch mediators and return status 200", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(200)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                res.body[0].Mediators.length.should.equal 1
+                res.body[0].Mediators[0].should.have.property "name", "Save Encounter Mediator"
+                done()
+
+      describe "Users", ->
+
+        beforeEach (done) ->
+          (new User sampleMetadata.Users[0]).save (err, user) ->
+            return done err if err
+            done()
+
+        afterEach (done) ->
+          User.remove ->
+            auth.setupTestUsers (err) ->
+              authDetails = auth.getAuthDetails()
+              done()
+
+        it  "should fetch users and return status 200", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(200)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                res.body[0].Users.length.should.equal 4 # Due to 3 auth test users
+                done()
+
+      describe "ContactGroups", ->
+
+        beforeEach (done) ->
+          (new ContactGroup sampleMetadata.ContactGroups[0]).save (err, cg) ->
+            return done err if err
+            done()
+
+        afterEach (done) ->
+          ContactGroup.remove ->
+            done()
+
+        it  "should fetch contact groups and return status 200", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(200)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                res.body[0].ContactGroups.length.should.equal 1
+                res.body[0].ContactGroups[0].should.have.property "group", "Group 1"
+                done()
+
+      describe "Other Get Metadata", ->
+
+        it  "should not allow a non admin user to get metadata", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata")
+            .set("auth-username", testUtils.nonRootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .expect(403)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                done()
+
+        it  "should return 404 if not found", (done) ->
+          request("https://localhost:8080")
+            .get("/metadata/bleh")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(sampleMetadata)
+            .expect(404)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                done()
+
+
+
+    # IMPORT TESTS
+    describe "*importMetadata", ->
+
+      describe "Channels", ->
+        testMetadata = {}
+
+        beforeEach (done) ->
+          testMetadata =
+            Channels: JSON.parse JSON.stringify sampleMetadata.Channels
+          done()
+
+        afterEach (done) ->
+          Channel.remove ->
+            done()
+
+        it  "should insert a channel and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              res.body.rows[0].should.have.property "status", "Inserted"
+              Channel.findOne { name: "TestChannel1" }, (err, channel) ->
+                return done err if err
+                channel.should.have.property "urlPattern", "test/sample"
+                channel.allow.should.have.length 3
+                done()
+
+        it  "should update a channel and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, resp) ->
+              return done err if err
+              testMetadata.Channels[0].urlPattern = "sample/test"
+              request("https://localhost:8080")
+                .post("/metadata")
+                .set("auth-username", testUtils.rootUser.email)
+                .set("auth-ts", authDetails.authTS)
+                .set("auth-salt", authDetails.authSalt)
+                .set("auth-token", authDetails.authToken)
+                .send(testMetadata)
+                .expect(201)
+                .end (err, res) ->
+                  return done err if err
+
+                  res.body.rows[0].should.have.property "status", "Updated"
+                  Channel.findOne { name: "TestChannel1" }, (err, channel) ->
+                    return done err if err
+                    channel.should.have.property "urlPattern", "sample/test"
+                    channel.allow.should.have.length 3
+                    done()
+
+        it  "should fail to insert a Channel and return 201", (done) ->
+          testMetadata.Channels = [{"fakeChannel"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+              res.body.rows[0].should.have.property "status", "Error"
+              done()
+
+      describe "Clients", ->
+        testMetadata = {}
+
+        beforeEach (done) ->
+          testMetadata =
+            Clients: JSON.parse JSON.stringify sampleMetadata.Clients
+          done()
+
+        afterEach (done) ->
+          Client.remove ->
+            done()
+
+        it  "should insert a client and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              res.body.rows[0].should.have.property "status", "Inserted"
+              Client.findOne { clientID: "YUIAIIIICIIAIA" }, (err, client) ->
+                return done err if err
+                client.should.have.property "name", "OpenMRS Ishmael instance"
+                done()
+
+        it  "should update a client and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, resp) ->
+              return done err if err
+              testMetadata.Clients[0].name = "Test Update"
+              request("https://localhost:8080")
+                .post("/metadata")
+                .set("auth-username", testUtils.rootUser.email)
+                .set("auth-ts", authDetails.authTS)
+                .set("auth-salt", authDetails.authSalt)
+                .set("auth-token", authDetails.authToken)
+                .send(testMetadata)
+                .expect(201)
+                .end (err, res) ->
+                  return done err if err
+
+                  res.body.rows[0].should.have.property "status", "Updated"
+                  Client.findOne { clientID: "YUIAIIIICIIAIA" }, (err, client) ->
+                    return done err if err
+                    client.should.have.property "name", "Test Update"
+                    done()
+
+        it  "should fail to insert a Client and return 201", (done) ->
+          testMetadata.Clients = [{"fakeClient"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+              res.body.rows[0].should.have.property "status", "Error"
+              done()
+
+
+      describe "Mediators", ->
+        testMetadata = {}
+
+        beforeEach (done) ->
+          testMetadata =
+            Mediators: JSON.parse JSON.stringify sampleMetadata.Mediators
+          done()
+
+        afterEach (done) ->
+          Mediator.remove ->
+            done()
+
+        it  "should insert a mediator and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              res.body.rows[0].should.have.property "status", "Inserted"
+              Mediator.findOne { urn: "urn:uuid:EEA84E13-1C92-467C-B0BD-7C480462D1ED" }, (err, mediator) ->
+                return done err if err
+                mediator.should.have.property "name", "Save Encounter Mediator"
+                done()
+
+        it  "should update a mediator and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, resp) ->
+              return done err if err
+              testMetadata.Mediators[0].name = "Updated Encounter Mediator"
+              request("https://localhost:8080")
+                .post("/metadata")
+                .set("auth-username", testUtils.rootUser.email)
+                .set("auth-ts", authDetails.authTS)
+                .set("auth-salt", authDetails.authSalt)
+                .set("auth-token", authDetails.authToken)
+                .send(testMetadata)
+                .expect(201)
+                .end (err, res) ->
+                  return done err if err
+
+                  res.body.rows[0].should.have.property "status", "Updated"
+                  Mediator.findOne { urn: "urn:uuid:EEA84E13-1C92-467C-B0BD-7C480462D1ED" }, (err, mediator) ->
+                    return done err if err
+                    mediator.should.have.property "name", "Updated Encounter Mediator"
+                    done()
+
+        it  "should fail to insert a mediator and return 201", (done) ->
+          testMetadata.Mediators = [{"fakeMediator"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+              res.body.rows[0].should.have.property "status", "Error"
+              done()
+
+      describe "Users", ->
+        testMetadata = {}
+
+        beforeEach (done) ->
+          testMetadata =
+            Users: JSON.parse JSON.stringify sampleMetadata.Users
+          done()
+
+        afterEach (done) ->
+          User.remove ->
+            auth.setupTestUsers (err) ->
+              authDetails = auth.getAuthDetails()
+              done()
+
+        it  "should insert a user and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              res.body.rows[0].should.have.property "status", "Inserted"
+              User.findOne { email: "r..@jembi.org" }, (err, user) ->
+                return done err if err
+                user.should.have.property "firstname", "Namey"
+                done()
+
+        it  "should update a user and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, resp) ->
+              return done err if err
+              testMetadata.Users[0].firstname = "updatedNamey"
+              request("https://localhost:8080")
+                .post("/metadata")
+                .set("auth-username", testUtils.rootUser.email)
+                .set("auth-ts", authDetails.authTS)
+                .set("auth-salt", authDetails.authSalt)
+                .set("auth-token", authDetails.authToken)
+                .send(testMetadata)
+                .expect(201)
+                .end (err, res) ->
+                  return done err if err
+
+                  res.body.rows[0].should.have.property "status", "Updated"
+                  User.findOne { email: "r..@jembi.org" }, (err, user) ->
+                    return done err if err
+                    user.should.have.property "firstname", "updatedNamey"
+                    done()
+
+        it  "should fail to insert a user and return 201", (done) ->
+          testMetadata.Users = [{"fakeUser"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+              res.body.rows[0].should.have.property "status", "Error"
+              done()
+
+      describe "ContactGroups", ->
+        testMetadata = {}
+
+        beforeEach (done) ->
+          testMetadata =
+            ContactGroups: JSON.parse JSON.stringify sampleMetadata.ContactGroups
+          done()
+
+        afterEach (done) ->
+          ContactGroup.remove ->
+            done()
+
+        it  "should insert a contactGroup and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              res.body.rows[0].should.have.property "status", "Inserted"
+              ContactGroup.findOne { group: "Group 1" }, (err, cg) ->
+                return done err if err
+                cg.users.should.have.length 6
+                done()
+
+        it  "should update a contactGroup and return 201", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, resp) ->
+              return done err if err
+              testMetadata.ContactGroups[0].users.push { user: 'User 6', method: 'email', maxAlerts: '1 per day' }
+              request("https://localhost:8080")
+                .post("/metadata")
+                .set("auth-username", testUtils.rootUser.email)
+                .set("auth-ts", authDetails.authTS)
+                .set("auth-salt", authDetails.authSalt)
+                .set("auth-token", authDetails.authToken)
+                .send(testMetadata)
+                .expect(201)
+                .end (err, res) ->
+                  return done err if err
+
+                  res.body.rows[0].should.have.property "status", "Updated"
+                  ContactGroup.findOne { group: "Group 1" }, (err, cg) ->
+                    return done err if err
+                    cg.users.should.have.length 7
+                    done()
+
+        it  "should fail to insert a ContactGroup and return 201", (done) ->
+          testMetadata.ContactGroups = [{"fakeContactGroup"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+              res.body.rows[0].should.have.property "status", "Error"
+              done()
+
+      describe "Full Metadata Import", ->
+
+        after (done) ->
           Channel.remove ->
             Client.remove ->
               Mediator.remove ->
                 ContactGroup.remove ->
-                  server.stop ->
-                    done()
-
-    describe "*upsertMetadata", ->
-
-      it  "should insert valid metadata and return status 201 - metadata created", (done) ->
-        request("https://localhost:8080")
-          .post("/metadata")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(201)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              res.body.errors.length.should.equal 0
-              res.body.successes.length.should.equal 5
+                  User.remove ->
+                    auth.setupTestUsers (err) ->
+                      authDetails = auth.getAuthDetails()
+                      done()
+
+        it  "should ignore invalid metadata, insert valid metadata and return 201", (done) ->
+          testMetadata = {}
+          testMetadata = JSON.parse JSON.stringify sampleMetadata
+          testMetadata.Channels = [{"InvalidChannel"}]
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              Channel.findOne { name: "TestChannel1" }, (err, channel) ->
+                noChannel = if channel then 'false' else 'true'
+                noChannel.should.equal 'true'
+
+                Client.findOne { clientID: "YUIAIIIICIIAIA" }, (err, client) ->
+                  return done err if err
+                  client.should.have.property "name", "OpenMRS Ishmael instance"
+
+                  Mediator.findOne { urn: "urn:uuid:EEA84E13-1C92-467C-B0BD-7C480462D1ED" }, (err, mediator) ->
+                    return done err if err
+                    mediator.should.have.property "name", "Save Encounter Mediator"
+
+                    User.findOne { email: "r..@jembi.org" }, (err, user) ->
+                      return done err if err
+                      user.should.have.property "firstname", "Namey"
+
+                      ContactGroup.findOne { group: "Group 1" }, (err, cg) ->
+                        return done err if err
+                        cg.users.should.have.length 6
+                        done()
+
+      describe "Bad metadata import requests", ->
+
+        it  "should not allow a non admin user to insert metadata", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata")
+            .set("auth-username", testUtils.nonRootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(sampleMetadata)
+            .expect(403)
+            .end (err, res) ->
+              return done err if err
               done()
-
-      it  "should insert partially valid metadata and return status 201 - metadata created", (done) ->
-        testMetadata = {}
-        testMetadata = JSON.parse JSON.stringify sampleMetadata
-        testMetadata.Channels = [{"fakeChannel"}]
-        request("https://localhost:8080")
-          .post("/metadata")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(testMetadata)
-          .expect(201)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              res.body.errors.length.should.equal 1
-              res.body.successes.length.should.equal 4
-              done()
-
-      it  "should fail to insert invalid metadata and return status 400 - bad request", (done) ->
-        testMetadata = {}
-        testMetadata = JSON.parse JSON.stringify sampleMetadata
-        testMetadata.InvalidField = { "name": "value" }
-        request("https://localhost:8080")
-          .post("/metadata")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(testMetadata)
-          .expect(400)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
-
-      it  "should not allow a non admin user to insert metadata", (done) ->
-        request("https://localhost:8080")
-          .post("/metadata")
-          .set("auth-username", testUtils.nonRootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(403)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
-
-      it  "should return 404 if not found", (done) ->
-        request("https://localhost:8080")
-          .post("/metadata/bleh")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(404)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
-
-
-    describe "*getMetadata", ->
-
-      it  "should fetch metadata and return status 200 - Ok", (done) ->
-        request("https://localhost:8080")
-          .get("/metadata")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(200)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              res.body[0].Channels.length.should.equal 1
-              res.body[0].Clients.length.should.equal 1
-              res.body[0].Users.length.should.equal 4
-              res.body[0].Mediators.length.should.equal 1
-              res.body[0].ContactGroups.length.should.equal 1
-              done()
-
-      it  "should not allow a non admin user to get metadata", (done) ->
-        request("https://localhost:8080")
-          .get("/metadata")
-          .set("auth-username", testUtils.nonRootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(403)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
-
-      it  "should return 404 if not found", (done) ->
-        request("https://localhost:8080")
-          .get("/metadata/bleh")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(sampleMetadata)
-          .expect(404)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
-
+
+        it  "should return 404 if not found", (done) ->
+          request("https://localhost:8080")
+            .post("/metadata/bleh")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(sampleMetadata)
+            .expect(404)
+            .end (err, res) ->
+              return done err if err
+              done()
+
+
+
+
+    # POST TO VALIDATE METADATA TESTS
     describe "*validateMetadata", ->
-
-      it  "should validate metadata and return status 201 - metadata validated", (done) ->
+
+      it  "should validate metadata and return status 201", (done) ->
         request("https://localhost:8080")
           .post("/metadata/validate")
           .set("auth-username", testUtils.rootUser.email)
@@ -238,22 +724,23 @@
           .send(sampleMetadata)
           .expect(201)
           .end (err, res) ->
-            if err
-              done err
-            else
-              res.body.errors.length.should.equal 0
-              res.body.successes.length.should.equal 5
-              done()
-
-      it  "should validate partially valid metadata and return status 201 - metadata successfully validated", (done) ->
+            return done err if err
+
+            statusCheckObj = { Valid: 0, Conflict: 0, Error: 0 }
+            for row in res.body.rows
+              statusCheckObj[row.status] = statusCheckObj[row.status] + 1
+
+            statusCheckObj.Valid.should.equal 5
+            statusCheckObj.Conflict.should.equal 0
+            statusCheckObj.Error.should.equal 0
+            done()
+
+
+      it  "should validate partially valid metadata and return status 201", (done) ->
         testMetadata = {}
         testMetadata = JSON.parse JSON.stringify sampleMetadata
-        testMetadata.Channels = [{
-            name: "TestChannel2"
-            allow: [ "PoC", "Test1", "Test2" ]
-            routes: [{ name: "test route", host: "localhost", port: 9876, primary: true }]
-            txViewAcl: "group1"
-        }]
+        testMetadata.Channels = [{ "Invalid Channel" }]
+
         request("https://localhost:8080")
           .post("/metadata/validate")
           .set("auth-username", testUtils.rootUser.email)
@@ -263,30 +750,45 @@
           .send(testMetadata)
           .expect(201)
           .end (err, res) ->
-            if err
-              done err
-            else
-              res.body.errors.length.should.equal 1
-              res.body.successes.length.should.equal 4
-              done()
-
-      it  "should fail to validate invalid metadata and return status 400 - bad request", (done) ->
+            return done err if err
+
+            statusCheckObj = { Valid: 0, Conflict: 0, Error: 0 }
+            for row in res.body.rows
+              statusCheckObj[row.status] = statusCheckObj[row.status] + 1
+
+            statusCheckObj.Valid.should.equal 4
+            statusCheckObj.Conflict.should.equal 0
+            statusCheckObj.Error.should.equal 1
+            done()
+
+
+      it  "should validate metadata with conflicts and return status 201", (done) ->
         testMetadata = {}
         testMetadata = JSON.parse JSON.stringify sampleMetadata
-        testMetadata.InvalidField = { "name": "value" }
-        request("https://localhost:8080")
-          .post("/metadata/validate")
-          .set("auth-username", testUtils.rootUser.email)
-          .set("auth-ts", authDetails.authTS)
-          .set("auth-salt", authDetails.authSalt)
-          .set("auth-token", authDetails.authToken)
-          .send(testMetadata)
-          .expect(400)
-          .end (err, res) ->
-            if err
-              done err
-            else
-              done()
+
+        (new Channel sampleMetadata.Channels[0]).save (err, channel) ->
+          return done err if err
+          request("https://localhost:8080")
+            .post("/metadata/validate")
+            .set("auth-username", testUtils.rootUser.email)
+            .set("auth-ts", authDetails.authTS)
+            .set("auth-salt", authDetails.authSalt)
+            .set("auth-token", authDetails.authToken)
+            .send(testMetadata)
+            .expect(201)
+            .end (err, res) ->
+              return done err if err
+
+              statusCheckObj = { Valid: 0, Conflict: 0, Error: 0 }
+              for row in res.body.rows
+                statusCheckObj[row.status] = statusCheckObj[row.status] + 1
+
+              statusCheckObj.Valid.should.equal 4
+              statusCheckObj.Conflict.should.equal 1
+              statusCheckObj.Error.should.equal 0
+              Channel.remove ->
+                done()
+

       it  "should not allow a non admin user to validate metadata", (done) ->
         request("https://localhost:8080")
@@ -298,10 +800,9 @@
           .send(sampleMetadata)
           .expect(403)
           .end (err, res) ->
-            if err
-              done err
-            else
-              done()
+            return done err if err
+            done()
+

       it  "should return 404 if not found", (done) ->
         request("https://localhost:8080")
@@ -313,10 +814,8 @@
           .send(sampleMetadata)
           .expect(404)
           .end (err, res) ->
-            if err
-              done err
-            else
-              done()
+            return done err if err
+            done()


Native setImageSource bug with source verification fixed

--- impl/native/level0/image.coffee
+++ impl/native/level0/image.coffee
@@ -15,9 +15,6 @@
 		success = reader.getBoolean()
 		width = reader.getFloat()
 		height = reader.getFloat()
-
-		if image.source isnt source
-			return

 		image._impl.imageLoadCallback?.call image, not success,
 			source: source


Sidebar: fetchMachines added

Sidebar: fetchMachines added

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -336,6 +336,26 @@
   listMachines: (machines) ->

     @machineTree.addNode machine  for machine in machines
+
+
+  fetchMachines: (callback) ->
+
+    {computeController} = KD.singletons
+
+    # force refetch from server everytime machines fetched.
+    computeController.fetchMachines force = yes, (err, machines) =>
+      if err
+        ErrorLog.create 'terminal: Couldn\'t fetch machines', reason : err
+        return new KDNotificationView title : 'Couldn\'t fetch your VMs'
+
+      # hostnameAlias comes in format 'vm-0.senthil.kd.io', this helper
+      # gets just the vm number
+      getVMNumber = ({hostnameAlias}) -> +(hostnameAlias.match(/\d+/)[0])
+
+      # sort machines by vm number
+      machines.sort (a,b)-> getVMNumber(a) > getVMNumber(b)
+
+      callback machines

   addVMTree: ->


Activity: Deleted activities are not destroyed bug is fixed. (But the effects must be fixed as well)

--- client/Social/Activity/views/activityitemchild.coffee
+++ client/Social/Activity/views/activityitemchild.coffee
@@ -72,6 +72,7 @@
     data = @getData()

     deleteActivity = (activityItem)->
+      activityItem.destroy() #FIXME
       activityItem.slideOut -> activityItem.destroy()

     @on 'ActivityIsDeleted', =>
@@ -85,13 +86,16 @@
       if KD.whoami().getId() is data.getAt('originId')
         deleteActivity activityItem
       else
-        activityItem.putOverlay
-          isRemovable : no
-          parent      : @parent
-          cssClass    : 'half-white'
+        #CtF: FIXME added just for making it functional
+        activityItem.destroy()

-        @utils.wait 30000, ->
-          activityItem.slideOut -> activityItem.destroy()
+        # activityItem.putOverlay
+        #   isRemovable : no
+        #   parent      : @parent
+        #   cssClass    : 'half-white'
+
+        # @utils.wait 30000, ->
+        #   activityItem.slideOut -> activityItem.destroy()

     data.watch 'repliesCount', (count)=>


Fix for GEN-1478

Fix for GEN-1478

--- client/members/lib/contentdisplays/profileview.coffee
+++ client/members/lib/contentdisplays/profileview.coffee
@@ -34,9 +34,11 @@
     @memberData = @getData()
     { mainController, router } = kd.singletons

-    if @memberData.isExempt
-      if not checkFlag 'super-admin' or not @memberData._id is whoami()._id
-        return router.handleRoute '/Activity'
+    isSameUser    = @memberData._id is whoami()._id
+    isSuperAdmin  = checkFlag 'super-admin'
+
+    if @memberData.isExempt and (not isSuperAdmin and not isSameUser)
+      return router.handleRoute '/Activity'

     @firstName      = new ProfileContentEditableView
       tagName       : "span"


remove log

remove log

--- routes/lib/speaker.coffee
+++ routes/lib/speaker.coffee
@@ -19,7 +19,6 @@
     text: message

   respon = (err, response) ->
-    console.log(err, response)
     if err?
       setTimeout ()->
         this.body = thebody


Use "article_tags" more consistently, so as not to confuse them with "tags"

--- js/outer/docs.coffee
+++ js/outer/docs.coffee
@@ -63,7 +63,7 @@

     # iterate through the articles, and update the categories
     for _, a of @articles
-      for t in a.tags
+      for t in a.article_tags
         @tags[t] or= []
         @tags[t].push a
         if @categories[t]?
@@ -77,12 +77,11 @@
   article_info: (slug, node, context) =>
     uriFragment = slug.replace(/_/g, '-')
     title = node.find('.title > h1').text().trim()
-    tags = context.article_tags or []
     result =
       url: "/docs/#{uriFragment}"
       slug: slug
       title: title
-      tags: tags
+      article_tags: context.article_tags or []
       subtitle: node.find('.title > h4').text().trim()
       lastupdated: node.find('.title > .lastupdated').text().trim()
       icon: node.find('.title > i').attr('class')


crude/quick code for getting a canonical link for linking content

--- tutor/src/components/book-content-mixin.cjsx
+++ tutor/src/components/book-content-mixin.cjsx
@@ -8,7 +8,6 @@
 {CourseStore} = require '../flux/course'
 {TaskStepStore} = require '../flux/task-step'
 {MediaStore} = require '../flux/media'
-{CourseStore} = require '../flux/course'
 ScrollToLinkMixin = require './scroll-to-link-mixin'

 Router = require '../helpers/router'
@@ -146,16 +145,42 @@

   componentDidMount:  ->
     @_linkContentIsMounted = true
+    @insertCanonicalLink()
     @insertOverlays()
     @detectImgAspectRatio()
     @cleanUpAbstracts()
     @processLinks()

   componentDidUpdate: ->
+    @updateCanonicalLink()
     @insertOverlays()
     @detectImgAspectRatio()
     @cleanUpAbstracts()
     @processLinks()
+
+  componentWillUnmount: ->
+    @removeCanonicalLink()
+
+  insertCanonicalLink: ->
+    @linkNode = document.createElement('link')
+    @linkNode.rel = 'canonical'
+    document.head.appendChild(@linkNode)
+
+    @updateCanonicalLink()
+
+  updateCanonicalLink: ->
+    cnxId = @props.cnxId or @getCnxId?()
+    {courseId} = Router.currentParams()
+    {webview_url} = CourseStore.get(courseId)
+    baseWebviewUrl = _.first(webview_url.split('/contents/'))
+
+    # webview actually links to webview_url as it's canonical url.
+    # will need to ask them why.
+    @linkNode.href = "#{baseWebviewUrl}/contents/#{cnxId}"
+
+  removeCanonicalLink: ->
+    # document.head.
+    @linkNode.remove()

   insertOverlays: ->
     title = @getSplashTitle()


Simplify returning error status codes

Simplify returning error status codes


React needed assigned to the Window earlier

--- client.cjsx
+++ client.cjsx
@@ -2,14 +2,15 @@
 require './public/main.css'

 React = require 'react'
+# Assign react to Window so the Chrome React Dev Tools will work as well
+# so don't have to import react in every React component.
+window.React = React
+
 Route = require('react-router').Route

 HelloWorld = require './lib/react_components/hello_world'
 StyleGuide = require './lib/react_components/styleguide'
 App = require './lib/react_components/app'
-
-# Assign react to Window so the Chrome React Dev Tools will work
-window.React = React

 React.renderComponent((
   <Route handler={App}>


Workspace: Separated create user list method.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -299,7 +299,11 @@
     return  if @userList
     @userListContainer.setClass "active"

-    @userListContainer.addSubView @userList = new CollaborativeWorkspaceUserList {
+    @createUserList()
+    @userListContainer.addSubView @userList
+
+  createUserList: ->
+    @userList = new CollaborativeWorkspaceUserList {
       @workspaceRef
       @sessionKey
       container : @userListContainer


Workspace: Updated broadcast reference listening point.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -55,11 +55,6 @@

       @emit "WorkspaceSyncedWithRemote"

-      @broadcastRef.on "value", (snapshot) =>
-        message = snapshot.val()
-        return if not message or not message.data or message.data.sender is @nickname
-        @displayBroadcastMessage message.data
-
     @workspaceRef.child("users").on "child_added", (snapshot) =>
       @fetchUsers()

@@ -80,6 +75,11 @@
       KD.utils.wait 1500, =>
         @workspaceRef.once "value", (snapshot) =>
           @showDisconnectedModal()  unless snapshot.val() or @disconnectedModal
+
+    @broadcastRef.on "value", (snapshot) =>
+      message = snapshot.val()
+      return if not message or not message.data or message.data.sender is @nickname
+      @displayBroadcastMessage message.data

     @broadcastMessage
       title     : "#{@nickname} has joined the session"


Changed wording on request of merijn.

Changed wording on request of merijn.

--- core/app/backbone/views/evidence/react_comment.coffee
+++ core/app/backbone/views/evidence/react_comment.coffee
@@ -54,7 +54,7 @@
             href:"javascript:"
             onClick: @_toggleSubcomments

-            "(#{sub_comment_count}) Reply"
+            "(#{sub_comment_count}) comment"
       ]
       if @state.show_subcomments
         ReactSubComments(model: @model())


Removed unused helpers

Removed unused helpers

--- core/app/backbone/views/facts/fact_bottom_view.coffee
+++ core/app/backbone/views/facts/fact_bottom_view.coffee
@@ -5,7 +5,6 @@

   events:
     "click .js-open-proxy-link" : "openProxyLink"
-    "click .js-arguments-link": "openDiscussionModal"

   templateHelpers: ->
     formatted_time: ->
@@ -19,11 +18,3 @@
   openProxyLink: (e) ->
     mp_track "Factlink: Open proxy link",
       site_url: @model.get("fact_url")
-
-  openDiscussionModal: (e) ->
-    e.preventDefault()
-
-    if e.metaKey or e.ctrlKey or e.altKey
-      window.open @model.get('url'), "_blank"
-    else
-      FactlinkApp.DiscussionModalOnFrontend.openDiscussion @model.clone()


fixed error when unknown language is requested

--- src/service.coffee
+++ src/service.coffee
@@ -220,7 +220,11 @@
         logger.error 'findScoreByLang', {err: err}
         return callback(err)

-      languageDescription = parser.getParser(".#{lang}").parse("", {}, "")
+      langParser = parser.getParser(".#{lang}")
+      if (langParser)
+        languageDescription = parser.getParser(".#{lang}").parse("", {}, "")
+      else
+        callback new Error "#{lang} is not found"

       dailyData = []
       cursor.toArray (err, docs) ->
@@ -242,43 +246,6 @@
           makeResult(lang, dailyData, callback)
         else
           callback new Error "#{lang} is not found"
-
-    makeResult = (lang, dailyData, callback) ->
-      sumData =
-        lang: lang
-        period: []
-        raw: dailyData
-
-      dailyData.forEach (data) ->
-        if not sumData.scores?
-          sumData.scores = data.convention
-          sumData.period.push data.file
-        else
-          (if key isnt 'lang'
-            if (data.convention[key]?)
-              sumData.scores[key].column.forEach (elem) ->
-                sumData.scores[key][elem.key] += data.convention[key][elem.key]
-              sumData.scores[key].commits += data.convention[key].commits
-          ) for key of sumData.scores
-          # add new field not exist
-          (
-            sumData.scores[key] = data.convention[key]
-          ) for key in Object.keys(data.convention).filter (x) ->
-            !~Object.keys(sumData.scores).indexOf x
-
-          sumData.period.push data.file
-
-      # get total for percentage
-      (if key isnt 'lang'
-        total = 0
-        sumData.scores[key].column.forEach (elem) ->
-          total += sumData.scores[key][elem.key]
-          elem.code = hljs.highlight(getHighlightName(lang), elem.code).value
-        sumData.scores[key].total = total
-      ) for key of sumData.scores
-
-      callback null, sumData
-

   findDescription: (force, callback) ->
     # get commit count from cacahing when cache value is exist and in 1 hour
@@ -397,3 +364,39 @@
         else
           conv.commits = conv.commits + newConvention[key].commits
   )for key, conv of baseConvention
+
+makeResult = (lang, dailyData, callback) ->
+  sumData =
+    lang: lang
+    period: []
+    raw: dailyData
+
+  dailyData.forEach (data) ->
+    if not sumData.scores?
+      sumData.scores = data.convention
+      sumData.period.push data.file
+    else
+      (if key isnt 'lang'
+        if (data.convention[key]?)
+          sumData.scores[key].column.forEach (elem) ->
+            sumData.scores[key][elem.key] += data.convention[key][elem.key]
+          sumData.scores[key].commits += data.convention[key].commits
+      ) for key of sumData.scores
+      # add new field not exist
+      (
+        sumData.scores[key] = data.convention[key]
+      ) for key in Object.keys(data.convention).filter (x) ->
+        !~Object.keys(sumData.scores).indexOf x
+
+      sumData.period.push data.file
+
+  # get total for percentage
+  (if key isnt 'lang'
+    total = 0
+    sumData.scores[key].column.forEach (elem) ->
+      total += sumData.scores[key][elem.key]
+      elem.code = hljs.highlight(getHighlightName(lang), elem.code).value
+    sumData.scores[key].total = total
+  ) for key of sumData.scores
+
+  callback null, sumData


Mix ImageSizes in

Mix ImageSizes in

--- models/cover_image.coffee
+++ models/cover_image.coffee
@@ -2,6 +2,8 @@
 Backbone = require 'backbone'
 { Image } = require 'artsy-backbone-mixins'
 { SECURE_IMAGES_URL } = require('sharify').data
+ImageSizes = require './mixins/image_sizes.coffee'

 module.exports = class CoverImage extends Backbone.Model
   _.extend @prototype, Image(SECURE_IMAGES_URL)
+  _.extend @prototype, ImageSizes


Disable soft-wrapping when editing Markdown files

--- config.cson
+++ config.cson
@@ -113,6 +113,12 @@
 ".desc.ditroff.source":
 	editor:
 		tabLength: 16
+".gfm.source":
+	editor:
+		softWrap: false
+".gfm.source:not(.markup.code)":
+	editor:
+		softWrap: false
 ".lisp.source":
 	editor:
 		softTabs: true


VM.set().

VM.set().

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -133,3 +133,39 @@

   # The VM should be properly created.
   true
+
+exports.set = ->
+  params = @getParams {
+    # Identifier of the VM to update.
+    id: { type: 'string' }
+
+    name_label: { type: 'string', optional: true }
+
+    name_description: { type: 'string', optional: true }
+
+    # Number of virtual CPUs to allocate.
+    CPUs: { 'type': integer, optional: true }
+
+    # Memory to allocate (in bytes).
+    memory: { 'type': integer, optional: true }
+  }
+
+  try
+    VM = @getObject params.id
+  catch
+    @throw 'NO_SUCH_OBJECT'
+
+  xapi = @getXAPI VM
+
+  # Some settings can only be changed when the VM is halted.
+  if 'CPUs' of params and VM.power_state isnt 'Halted'
+    @throw 'INVALID_PARAMS'
+
+  for param, field of {
+    CPUs: 'VCPUs_at_startup'
+    memory: 'memory_dynamic_max'
+    'name_label'
+    'name_description'
+  }
+    if field of params?
+      xapi.call "VM.set_#{field}", VM.ref, params[param]


can specify environment with the NODE_ENV variable

--- src/config.coffee
+++ src/config.coffee
@@ -40,11 +40,14 @@
             throw new (Error "When using SQLite, you must specify a database name !")

     loadConfigJSON      : ()            ->
-        tmpJSON = require (Path.join @Paths.Config, 'config.json')
+        fileToOpen  = 'config.json'
+        if process.env.NODE_ENV
+            fileToOpen  += '.' + process.env.NODE_ENV
+        tmpJSON     = require (Path.join @Paths.Config, fileToOpen)

-        @Port   = tmpJSON.port
+        @Port       = tmpJSON.port

-        @Logger =
+        @Logger     =
             Level   : Logger.getLevelFromString tmpJSON.logger.level
             Stdout  : tmpJSON.logger.stdout


profile: setHandle for profiles

profile: setHandle for profiles

--- workers/social/lib/social/models/account/index.coffee
+++ workers/social/lib/social/models/account/index.coffee
@@ -62,7 +62,8 @@
         'fetchNonces', 'glanceMessages', 'glanceActivities', 'fetchRole'
         'fetchAllKites','flagAccount','unflagAccount','isFollowing'
         'fetchFeedByTitle', 'updateFlags','fetchGroups','fetchGroupRoles',
-        'setStaticPageVisibility','addStaticPageType','removeStaticPageType'
+        'setStaticPageVisibility','addStaticPageType','removeStaticPageType',
+        'setHandle'
       ]
     schema                  :
       skillTags             : [String]
@@ -103,6 +104,9 @@
           type              : String
           default           : ''
         description         : String
+        handles             :
+          twitter           : String
+          github            : String
         staticPage          :
           show              :
             type            : Boolean
@@ -206,6 +210,20 @@
         skillTags     : @skillTags
         lastBlogPosts : posts or {}
       }
+
+  setHandle: secure (client, data, callback)->
+    {delegate}    = client.connection
+    {service,value} = data
+    selector = "profile.handles."+service
+    console.log 'setting handle',service, value, "profile.handles."+service
+    isMine        = this.equals delegate
+    if isMine and service in ['twitter','github']
+      operation = $set: {}
+      operation.$set[selector] = value
+      @update operation, callback
+    else
+      callback? new KodingError 'Access denied!'
+

   addStaticPageType: secure (client, type, callback)->
     {delegate}    = client.connection


OX-236 fade added

OX-236 fade added

--- app/assets/javascripts/level_creator/plugins.coffee
+++ app/assets/javascripts/level_creator/plugins.coffee
@@ -6,6 +6,7 @@
 ./plugins/draw_art_cells : DrawArtCells
 ./plugins/filter_probability_segmentation : FilterProbabilitySegmentation
 ./plugins/filter_start_segmentation : FilterStartSegmentation
+./plugins/fade : Fade
 ###

@@ -15,6 +16,7 @@

     @recolor = new Recolor(assetHandler)
     @blur = new Blur()
+    @fade = new Fade()
     @segmentImporter = new SegmentImporter()
     @filterSegmentationByDistance = new FilterSegmentationByDistance()
     @filterProbabilitySegmentation = new FilterProbabilitySegmentation()


Refactors app/assets/javascripts/shared/application.js.coffee

--- app/assets/javascripts/shared/application.js.coffee
+++ app/assets/javascripts/shared/application.js.coffee
@@ -2,5 +2,4 @@
   hostname = window.location.hostname
   $('a').each (i, e) ->
     if e.hostname != hostname
-      $(e).attr('target', '_blank')
-      $(e).attr('data-skip-pjax', 'true')
+      $(e).attr({'target': '_blank', 'data-skip-pjax': 'true'})


#1310 Remove fail hack, use this.fail()

#1310 Remove fail hack, use this.fail()

--- app/assets/javascripts/test/model/model.spec.coffee
+++ app/assets/javascripts/test/model/model.spec.coffee
@@ -55,10 +55,6 @@
       customLayers : []
 }

-fail = (message) ->
-  console.error("Fail: ", message)
-  expect("the test").toBe("not failing.")
-
 Model = require("../../oxalis/model")

 describe "Model", ->
@@ -85,8 +81,8 @@

         model.fetch()
           .then(done)
-          .catch((error) ->
-            fail(error.message)
+          .catch((error) =>
+            @fail(error.message)
             done()
           )

@@ -99,8 +95,8 @@
         Request.receiveJSON.returns(Promise.resolve(tracingObject))

         model.fetch()
-          .then(->
-            fail("Promise should not have been resolved.")
+          .then(=>
+            @fail("Promise should not have been resolved.")
             done()
           ).catch( (error) ->
             expect(error).toBe(Model::HANDLED_ERROR)
@@ -112,8 +108,8 @@
         Request.receiveJSON.returns(Promise.reject(new Error("errorMessage")))

         model.fetch()
-          .then(->
-            fail("Promise should not have been resolved.")
+          .then(=>
+            @fail("Promise should not have been resolved.")
             done()
           ).catch( (error) ->
             expect(error.message).toBe("errorMessage")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -5,6 +5,8 @@
   ok Sprite.NONE

 asyncTest "callback and cache", ->
+  callback = (s) ->
+
   sprite = Sprite.load "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12NgoC4AAABQAAEiE+h1AAAAAElFTkSuQmCC"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -7,8 +7,8 @@
 asyncTest "callback and cache", ->
   spriteUrl = "http://images.pixie.strd6.com/avatars/1/thumb.png"

-  callback = (s) ->
-    sprite2 = Sprite.load spriteUrl, ){->
+  callback = ->
+    sprite2 = Sprite.load spriteUrl, () ->

   sprite = Sprite.load spriteUrl, callback


latex: move zoom (and other) settings from a comment in the document to localStorage

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -2568,6 +2568,9 @@
     hide: () =>
         @element.hide()

+#############################################
+# Viewer/editor (?) for history of changes to a document
+#############################################

 class HistoryEditor extends FileEditor
     constructor: (@editor, @filename, content, opts) ->
@@ -2722,6 +2725,9 @@
         if @ext == 'sagews'
             @worksheet.process_sage_updates()

+#############################################
+# Editor for LaTeX documents
+#############################################

 class LatexEditor extends FileEditor
     constructor: (@editor, @filename, content, opts) ->
@@ -2805,38 +2811,13 @@
         @save_conf(conf)

     load_conf: () =>
-        doc = @latex_editor.codemirror.getValue()
-        i = doc.indexOf("%sagemathcloud=")
-        if i == -1
-            return {}
-
-        j = doc.indexOf('=',i)
-        k = doc.indexOf('\n',i)
-        if k == -1
-            k = doc.length
-        try
-            conf = misc.from_json(doc.slice(j+1,k))
-        catch
+        conf = @local_storage('conf')
+        if not conf?
             conf = {}
-
         return conf

     save_conf: (conf) =>
-        cm  = @latex_editor.codemirror
-        doc = cm.getValue()
-        i = doc.indexOf('%sagemathcloud=')
-        line = '%sagemathcloud=' + misc.to_json(conf)
-        if i != -1
-            # find the line m where it is already
-            for n in [0..cm.doc.lastLine()]
-                z = cm.getLine(n)
-                if z.indexOf('%sagemathcloud=') != -1
-                    m = n
-                    break
-            cm.replaceRange(line+'\n', {line:m,ch:0}, {line:m+1,ch:0})
-        else
-            cm.replaceRange('\n'+line, {line:cm.doc.lastLine()+1,ch:0})
-        @latex_editor.syncdoc.sync()
+        @local_storage('conf', conf)

     _pause_passive_search: (cb) =>
         @_passive_forward_search_disabled = true


close #39

close #39

--- core/coffee/public/js/client.coffee
+++ core/coffee/public/js/client.coffee
@@ -20,6 +20,19 @@
     complete(elm)
   elm.find(".cancel").click () ->
     elm.hide()
+
+  height = elm.height()
+  width = elm.width()
+  wWidth = $(window).width()
+  wHeight = $(window).height()
+
+  if wHeight < height + y
+    y = y - height
+
+  if wWidth < width + x
+    x = wWidth - width
+
+
   elm
     .css({
       left: x