Refactor: Clean up code.

Refactor: Clean up code.

--- app/assets/javascripts/branch-graph.js.coffee
+++ app/assets/javascripts/branch-graph.js.coffee
@@ -212,10 +212,6 @@
     top.push anchor

 Raphael::commitTooltip = (x, y, commit) ->
-  icon = undefined
-  nameText = undefined
-  idText = undefined
-  messageText = undefined
   boxWidth = 300
   boxHeight = 200
   icon = @image(commit.author.icon, x, y, 20, 20)


SocialWorker: Tests: refactor group.test file

--- workers/social/lib/social/models/group/index.test.coffee
+++ workers/social/lib/social/models/group/index.test.coffee
@@ -1,32 +1,26 @@
-{ argv }                      = require 'optimist'
-{ expect }                    = require 'chai'
-{ env : { MONGO_URL } }       = process
-KONFIG                        = require('koding-config-manager').load("main.#{argv.c}")
-mongo                         = MONGO_URL or "mongodb://#{ KONFIG.mongo }"
-Bongo                         = require 'bongo'
-{ daisy }                     = Bongo
 JUser                         = require '../user'
 JGroup                        = require './index'
 JAccount                      = require '../account'
 JSession                      = require '../session'
 JInvitation                   = require '../invitation'
-{ Relationship }              = require 'jraphical'
-{ generateRandomEmail
+{ daisy
+  expect
+  expectRelation
+  withDummyClient
+  withConvertedUser
+  expectAccessDenied
+  generateRandomEmail
   generateDummyClient
   generateRandomString
+  generateRandomUsername
+  checkBongoConnectivity
   generateDummyUserFormData } = require '../../../../testhelper'

 # making sure we have mongo connection before tests
 beforeTests = -> before (done) ->

-  bongo = new Bongo
-    root   : __dirname
-    mongo  : mongo
-    models : ''
-
-  bongo.once 'dbClientReady', ->
-    done()
+  checkBongoConnectivity done

 # here we have actual tests
@@ -49,84 +43,23 @@

       it 'should pass error if client doesnt have permission to kick', (done) ->

-        client  = {}
-        account = {}
-
-        queue = [
-
-          ->
-            # generating client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client  = client_
-              account = client.connection.delegate
-              queue.next()
-
-          ->
-            # expecting error to exist
-            kodingGroup.kickMember client, account._id, (err) ->
-              expect(err?.message).to.be.equal 'Access denied'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        expectAccessDenied kodingGroup, 'kickMember', 'someAccountId', done

     describe 'when group slug is not koding', ->

-      group             = {}
-      groupSlug         = generateRandomString()
-      adminClient       = {}
-      adminAccount      = {}
-
-      groupData         =
-        slug           : groupSlug
-        title          : generateRandomString()
-        visibility     : 'visible'
-        allowedDomains : [ 'koding.com' ]
+      group        = {}
+      groupSlug    = generateRandomString()
+      adminClient  = {}
+      adminAccount = {}

       # before running test cases creating a group
       before (done) ->

-        adminUserFormData = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating admin client to create group
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              adminClient = client_
-              queue.next()
-
-          ->
-            # registering admin client
-            JUser.convert adminClient, adminUserFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken } = data
-
-              # set credentials
-              adminClient.sessionToken        = newToken
-              adminClient.connection.delegate = account
-              adminClient.context.group       = groupSlug
-              adminAccount                    = account
-              queue.next()
-
-          ->
-            # creating a new group
-            JGroup.create adminClient, groupData, adminAccount, (err, group_) ->
-              expect(err).to.not.exist
-              group = group_
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        options = { createGroup : yes, context : { group : groupSlug } }
+        withConvertedUser options, (data) ->
+          { group, client : adminClient, account : adminAccount } = data
+          done()

       it 'should pass error if user tries kick own account', (done) ->
@@ -140,94 +73,79 @@

       it 'should be able to kick member if client has permission', (done) ->

-        client       = {}
-        account      = {}
-        userFormData = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating a client which will be kicked from the group
-            generateDummyClient { group : groupSlug }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # registering client
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken }   = data
-              queue.next()
-
-          ->
-            # making sure client is a member of the group before trying to kick
-            group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
-              expect(err).to.not.exist
-              expect(members.length).to.be.equal 1
-              queue.next()
-
-          ->
-            # expecting session to exist before kicking member
-            params =
-              username  : account.profile.nickname
-              groupName : client.context.group
-
-            JSession.one params, (err, data) ->
-              expect(err).to.not.exist
-              expect(data).to.exist
-              expect(data).to.be.an 'object'
-              queue.next()
-
-          ->
-            # expecting no error from kick member request
-            group.kickMember adminClient, account._id, (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            # admin client search for kicked member within group member
-            group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
-              expect(err).to.not.exist
-              expect(members.length).to.be.equal 0
-              queue.next()
-
-          ->
-            # expecting session to be deleted after kicking member
-            params =
-              username  : account.profile.nickname
-              groupName : client.context.group
-
-            JSession.one params, (err, data) ->
-              expect(err).to.not.exist
-              expect(data).to.not.exist
-              queue.next()
-
-          ->
-            # we cannot add blocked user to group
-            group.approveMember account, (err) ->
-              expect(err).to.exist
-              expect(err.message).to.be.equal 'This account is blocked'
-              queue.next()
-
-          ->
-            # we should be able to unblock the member
-            group.unblockMember adminClient, account.getId(), (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            # unblocked member can be added again
-            group.approveMember account, (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            done()
-
-        ]
-
-        daisy queue
+        options = { context : { group : groupSlug } }
+        withConvertedUser options, ({ client, account, userFormData }) ->
+
+          queue = [
+
+            ->
+              # making sure client is a member of the group before trying to kick
+              group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
+                expect(err).to.not.exist
+                expect(members.length).to.be.equal 1
+                queue.next()
+
+            ->
+              # expecting session to exist before kicking member
+              params =
+                username  : account.profile.nickname
+                groupName : client.context.group
+
+              JSession.one params, (err, data) ->
+                expect(err).to.not.exist
+                expect(data).to.exist
+                expect(data).to.be.an 'object'
+                queue.next()
+
+            ->
+              # expecting no error from kick member request
+              group.kickMember adminClient, account._id, (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              # admin client search for kicked member within group member
+              group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
+                expect(err).to.not.exist
+                expect(members.length).to.be.equal 0
+                queue.next()
+
+            ->
+              # expecting session to be deleted after kicking member
+              params =
+                username  : account.profile.nickname
+                groupName : client.context.group
+
+              JSession.one params, (err, data) ->
+                expect(err).to.not.exist
+                expect(data).to.not.exist
+                queue.next()
+
+            ->
+              # we cannot add blocked user to group
+              group.approveMember account, (err) ->
+                expect(err).to.exist
+                expect(err.message).to.be.equal 'This account is blocked'
+                queue.next()
+
+            ->
+              # we should be able to unblock the member
+              group.unblockMember adminClient, account.getId(), (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              # unblocked member can be added again
+              group.approveMember account, (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              done()
+
+          ]
+
+          daisy queue

   describe '#searchMembers()', ->
@@ -237,315 +155,148 @@
       it 'should not fetch any data', (done) ->

         group    = {}
-        client   = {}
         username = 'someRandomNonExistingUsername'

-        queue = [
-
-          ->
-            # generating client which well make searchMembers request
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # fetching koding group
-            JGroup.one { slug : 'koding' }, (err, group_) ->
-              expect(err).to.not.exist
-              group = group_
-              queue.next()
-
-          ->
-            # expecting to not be able to fetch members data
-            group.searchMembers client, username, {}, (err, members) ->
-              expect(err).to.not.exist
-              expect(members.length).to.be.equal 0
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        withDummyClient ({ client }) ->
+
+          queue = [
+
+            ->
+              # fetching koding group
+              JGroup.one { slug : 'koding' }, (err, group_) ->
+                expect(err).to.not.exist
+                group = group_
+                queue.next()
+
+            ->
+              # expecting to not be able to fetch members data
+              group.searchMembers client, username, {}, (err, members) ->
+                expect(err).to.not.exist
+                expect(members.length).to.be.equal 0
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue

     describe 'if username is registered', ->

       it 'should be able to fetch by username for koding group slug', (done) ->

-        group             = {}
-        client            = {}
-        userFormData      = generateDummyUserFormData()
-
-        groupData         =
-          slug           : 'koding'
-          title          : generateRandomString()
-          visibility     : 'visible'
-          allowedDomains : [ 'koding.com' ]
-
-        queue = [
-
-          ->
-            # fetching group
-            JGroup.one { slug : 'koding' }, (err, group_) ->
-              expect(err).to.not.exist
-              group = group_
-              queue.next()
-
-          ->
-            # generating client which well make searchMembers request
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # registering user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            # expecting to find newly registered member
-            group.searchMembers client, userFormData.username, {}, (err, members) ->
-              expect(err).to.not.exist
-              expect(members.length).to.be.equal 1
-              expect(members[0].profile.nickname).to.be.equal userFormData.username
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        withConvertedUser ({ client, userFormData }) ->
+          group = {}
+
+          queue = [
+
+            ->
+              # fetching group
+              JGroup.one { slug : 'koding' }, (err, group_) ->
+                expect(err).to.not.exist
+                group = group_
+                queue.next()
+
+            ->
+              # expecting to find newly registered member
+              group.searchMembers client, userFormData.username, {}, (err, members) ->
+                expect(err).to.not.exist
+                expect(members.length).to.be.equal 1
+                expect(members[0].profile.nickname).to.be.equal userFormData.username
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue

       describe 'when group is not koding', (done) ->

-        group             = {}
-        client            = {}
-        account           = {}
-        groupSlug         = generateRandomString()
-        adminClient       = {}
-
-        groupData         =
-          slug           : groupSlug
-          title          : generateRandomString()
-          visibility     : 'visible'
-          allowedDomains : [ 'koding.com' ]
+        group       = {}
+        groupSlug   = generateRandomString()
+        adminClient = {}

         # before running test cases creating a group
         before (done) ->
-
-          adminAccount      = {}
-          adminUserFormData = generateDummyUserFormData()
-
-          queue = [
-
-            ->
-              # generating admin client to create group
-              generateDummyClient { group : 'koding' }, (err, client_) ->
-                expect(err).to.not.exist
-                adminClient = client_
-                queue.next()
-
-            ->
-              # registering admin client
-              JUser.convert adminClient, adminUserFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken } = data
-
-                # set credentials
-                adminClient.sessionToken        = newToken
-                adminClient.connection.delegate = account
-                adminClient.context.group       = groupSlug
-                adminAccount                    = account
-                queue.next()
-
-            ->
-              # creating a new group
-              JGroup.create adminClient, groupData, adminAccount, (err, group_) ->
-                expect(err).to.not.exist
-                group = group_
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
-
+          options = { createGroup : yes, context : { group : groupSlug } }
+          withConvertedUser options, (data) ->
+            { group, client : adminClient } = data
+            done()

         it 'should not be able to fetch search result if not a member of group', (done) ->

-          client       = {}
-          userFormData = generateDummyUserFormData()
-
-          queue = [
-
-            ->
-              # generating client which will make search member request
-              generateDummyClient { group : 'koding' }, (err, client_) ->
-                expect(err).to.not.exist
-                client = client_
-                queue.next()
-
-            ->
-              # expecting not to be able to fetch member data
-              group.searchMembers client, userFormData.username, {}, (err, members) ->
-                expect(err?.message).to.be.equal 'Access denied'
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
+          username = generateRandomUsername()
+
+          withDummyClient ({ client }) ->
+            # expecting not to be able to fetch member data
+            group.searchMembers client, username, {}, (err, members) ->
+              expect(err?.message).to.be.equal 'Access denied'
+              done()

         it 'should be able to fetch member data after registering to group', (done) ->

-          client       = {}
-          userFormData = generateDummyUserFormData()
-
-          queue = [
-
-            ->
-              # generating client which will make search member request
-              generateDummyClient { group : groupSlug }, (err, client_) ->
-                expect(err).to.not.exist
-                client = client_
-                queue.next()
-
-            ->
-              # registering client
-              JUser.convert client, userFormData, (err, data) ->
-                expect(err).to.not.exist
-                { account, newToken }   = data
-
-                # set credentials
-                client.sessionToken        = newToken
-                client.connection.delegate = account
-                queue.next()
-
-            ->
-              # expecting new member client to be able fetch self data
-              group.searchMembers client, userFormData.username, {}, (err, members) ->
-                expect(err).to.not.exist
-                expect(members[0].profile.nickname).to.be.equal userFormData.username
-                queue.next()
-
-            ->
-              # expecting admin to be able to fetch new member's data
-              group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
-                expect(err).to.not.exist
-                expect(members[0].profile.nickname).to.be.equal userFormData.username
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
+          options = { context : { group : groupSlug } }
+          withConvertedUser options, ({ client, account, userFormData }) ->
+
+            queue = [
+
+              ->
+                # expecting new member client to be able fetch self data
+                group.searchMembers client, userFormData.username, {}, (err, members) ->
+                  expect(err).to.not.exist
+                  expect(members[0].profile.nickname).to.be.equal userFormData.username
+                  queue.next()
+
+              ->
+                # expecting admin to be able to fetch new member's data
+                group.searchMembers adminClient, userFormData.username, {}, (err, members) ->
+                  expect(err).to.not.exist
+                  expect(members[0].profile.nickname).to.be.equal userFormData.username
+                  queue.next()
+
+              -> done()
+
+            ]
+
+            daisy queue

   describe '#isInAllowedDomain()', ->

     it 'should return true if group slug is koding', (done) ->

-      client            = {}
-      account           = {}
-      groupSlug         = 'koding'
-      userFormData      = generateDummyUserFormData()
-
-      groupData         =
-        slug           : groupSlug
-        title          : generateRandomString()
-        visibility     : 'visible'
-        allowedDomains : [ 'koding.com' ]
-
-      queue = [
-
-        ->
-          # generating client
-          generateDummyClient { group : 'koding' }, (err, client_) ->
-            expect(err).to.not.exist
-            client = client_
-            queue.next()
-
-        ->
-          # registering user
-          JUser.convert client, userFormData, (err, data) ->
-            expect(err).to.not.exist
-            { account } = data
-            queue.next()
-
-        ->
-          # expecting to return true for any domain
-          JGroup.one { slug : groupSlug }, (err, group) ->
-            expect(err).to.not.exist
-            expect(group.isInAllowedDomain 'someRandomDomain').to.be.ok
-            queue.next()
-
-        -> done()
-
-      ]
-
-      daisy queue
+      # expecting to return true for any domain
+      JGroup.one { slug : 'koding' }, (err, group) ->
+        expect(err).to.not.exist
+        expect(group.isInAllowedDomain 'someRandomDomain').to.be.ok
+        done()

     it 'should return false if group does not have allowedDomains', (done) ->

-      client            = {}
-      account           = {}
-      groupSlug         = generateRandomString()
-      userFormData      = generateDummyUserFormData()
-
-      groupData         =
+      groupSlug = generateRandomString()
+
+      groupData =
         slug           : groupSlug
         title          : generateRandomString()
         visibility     : 'visible'
         allowedDomains : []

-      queue = [
-
-        ->
-          # generating client
-          generateDummyClient { group : 'koding' }, (err, client_) ->
-            expect(err).to.not.exist
-            client = client_
-            queue.next()
-
-        ->
-          # registering user
-          JUser.convert client, userFormData, (err, data) ->
-            expect(err).to.not.exist
-            { account } = data
-            queue.next()
-
-        ->
-          # expecting to return false when there are no allowedDomains
-          JGroup.create client, groupData, account, (err, group) ->
-            expect(err).to.not.exist
-            expect(group.isInAllowedDomain 'someRandomDomain').not.to.be.ok
-            queue.next()
-
-        -> done()
-
-      ]
-
-      daisy queue
+      options = { createGroup : yes, context : { group : groupSlug }, groupData }
+      withConvertedUser options, ({ client, account, group }) ->
+        expect(group.isInAllowedDomain 'someRandomDomain').not.to.be.ok
+        done()

     it 'should return true if group is in allowedDomains', (done) ->

-      client            = {}
-      account           = {}
-      groupSlug         = generateRandomString()
-      userFormData      = generateDummyUserFormData()
-
-      groupData         =
+      groupSlug = generateRandomString()
+
+      groupData =
         slug           : groupSlug
         title          : generateRandomString()
         visibility     : 'visible'
@@ -553,34 +304,10 @@

       allowedDomainEmail = generateRandomEmail groupData.allowedDomains[0]

-      queue = [
-
-        ->
-          # generating client
-          generateDummyClient { group : 'koding' }, (err, client_) ->
-            expect(err).to.not.exist
-            client = client_
-            queue.next()
-
-        ->
-          # registering user
-          JUser.convert client, userFormData, (err, data) ->
-            expect(err).to.not.exist
-            { account } = data
-            queue.next()
-
-        ->
-          # expecting to return false when there are no allowedDomains
-          JGroup.create client, groupData, account, (err, group) ->
-            expect(err).to.not.exist
-            expect(group.isInAllowedDomain allowedDomainEmail).to.be.ok
-            queue.next()
-
-        -> done()
-
-      ]
-
-      daisy queue
+      options = { createGroup : yes, context : { group : groupSlug }, groupData }
+      withConvertedUser options, ({ client, account, group }) ->
+        expect(group.isInAllowedDomain allowedDomainEmail).to.be.ok
+        done()

   describe '#create()', ->
@@ -589,14 +316,14 @@

       it 'should be able to create group', (done) ->

-        group             = {}
-        client            = {}
-        account           = {}
-        groupSlug         = generateRandomString()
-        groupTitle        = generateRandomString()
-        userFormData      = generateDummyUserFormData()
-
-        groupData         =
+        group        = {}
+        client       = {}
+        account      = {}
+        groupSlug    = generateRandomString()
+        groupTitle   = generateRandomString()
+        userFormData = generateDummyUserFormData()
+
+        groupData =
           slug           : groupSlug
           title          : groupTitle
           visibility     : 'visible'
@@ -639,9 +366,7 @@
               ]

             # expecting relationship to be created
-            Relationship.one params, (err, relationship) ->
-              expect(err)          .to.not.exist
-              expect(relationship) .to.exist
+            expectRelation.toExist params, ->
               queue.next()

           ->
@@ -653,9 +378,7 @@
                 { targetId : account._id }
               ]

-            Relationship.one params, (err, relationship) ->
-              expect(err)          .to.not.exist
-              expect(relationship) .to.exist
+            expectRelation.toExist params, ->
               queue.next()

           -> done()
@@ -669,100 +392,56 @@

       it 'should pass error if slug is empty or set as koding', (done) ->

-        client            = {}
-        account           = {}
-        userFormData      = generateDummyUserFormData()
-
-        groupData         =
+        groupData =
           slug       : ''
           title      : generateRandomString()
           visibility : 'visible'

-        queue = [
-
-          ->
-            # generating client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # registering user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account } = data
-              queue.next()
-
-          ->
-            # expecting error for 'koding' slug
-            groupData.slug = 'koding'
-
-            expectedError = 'The slug koding is not available.'
-            JGroup.create client, groupData, account, (err, data) ->
-              expect(err?.message).to.be.equal expectedError
-              queue.next()
-
-          ->
-            # expecting validaton error when slug is empty
-            groupData.slug = ''
-
-            JGroup.create client, groupData, account, (err, data) ->
-              expect(err).to.exist
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        withConvertedUser ({ client, account }) ->
+
+          queue = [
+
+            ->
+              # expecting error for 'koding' slug
+              groupData.slug = 'koding'
+
+              expectedError = 'The slug koding is not available.'
+              JGroup.create client, groupData, account, (err, data) ->
+                expect(err?.message).to.be.equal expectedError
+                queue.next()
+
+            ->
+              # expecting validaton error when slug is empty
+              groupData.slug = ''
+
+              JGroup.create client, groupData, account, (err, data) ->
+                expect(err).to.exist
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue

       it 'should pass error if slug is in use', (done) ->

-        client            = {}
-        account           = {}
-        groupSlug         = generateRandomString()
-        userFormData      = generateDummyUserFormData()
-
-        groupData         =
+        groupSlug = generateRandomString()
+
+        groupData =
           slug       : groupSlug
           title      : generateRandomString()
           visibility : 'visible'

-        queue = [
-
-          ->
-            # generating client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # registering user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account } = data
-              queue.next()
-
-          ->
-            # expecting group to be created
-            JGroup.create client, groupData, account, (err, data) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            # expecting error using already claimed slug
-            JGroup.create client, groupData, account, (err, data) ->
-              expect(err?.message).to.be.equal "The slug #{groupSlug} is not available."
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        options = { createGroup : yes, context : { group : groupSlug }, groupData }
+        withConvertedUser options, ({ client, account, group }) ->
+
+          # expecting error using already claimed slug
+          JGroup.create client, groupData, account, (err, data) ->
+            expect(err?.message).to.be.equal "The slug #{groupSlug} is not available."
+            done()
+

 beforeTests()


smarter filtering for default branch

smarter filtering for default branch

--- app/controllers/branches.coffee
+++ app/controllers/branches.coffee
@@ -7,14 +7,14 @@
   defaultBranch: (->
     repos = @get('model')
     output = repos.filter (item, index) ->
-      item if item.repository.default_branch.name == undefined
+      item if item.repository.default_branch['@href'] == item['@href']
     output[0]
   ).property('model')

   activeBranches: (->
     repos = @get('model')
     repos = repos.filter (item, index) ->
-      item if item.exists_on_github == true && item.repository.default_branch.name != undefined
+      item if item.exists_on_github == true && item.repository.default_branch['@href'] != item['@href']
     .sortBy('last_build.finished_at')
     .reverse()
   ).property('model')


Use _.findIndex(arr, pred) instead of _.indexOf(_.find(arr, pred))

--- bokehjs/src/coffee/models/widgets/tabs.coffee
+++ bokehjs/src/coffee/models/widgets/tabs.coffee
@@ -30,9 +30,7 @@
       $(this).tab('show')
       panelId = $(this).attr('href').replace('#tab-','')
       tabs = that.model.tabs
-      panelIdx = _.indexOf(tabs, _.find(tabs, (panel) ->
-        return panel.id == panelId
-      ))
+      panelIdx = _.findIndex(tabs, (panel) -> panel.id == panelId)
       that.model.active = panelIdx
       that.model.callback?.execute(that.model)


compute offsets when axes are on the same side

--- bokehjs/src/coffee/renderer/guide/axis.coffee
+++ bokehjs/src/coffee/renderer/guide/axis.coffee
@@ -190,12 +190,13 @@
       [x, y] = coords = @mget('rule_coords')
       [sx, sy] = @plot_view.map_to_screen(x, "data", y, "data", @x_range_name, @y_range_name)
       [nx, ny] = @mget('normals')
+      [xoff, yoff]  = @mget('offsets')

       @rule_props.set(ctx, @)
       ctx.beginPath()
-      ctx.moveTo(Math.round(sx[0]), Math.round(sy[0]))
+      ctx.moveTo(Math.round(sx[0]+nx*xoff), Math.round(sy[0]+ny*yoff))
       for i in [1...sx.length]
-        ctx.lineTo(Math.round(sx[i]), Math.round(sy[i]))
+        ctx.lineTo(Math.round(sx[i]+nx*xoff), Math.round(sy[i]+ny*yoff))
       ctx.stroke()

     _draw_major_ticks: (ctx) ->
@@ -205,14 +206,15 @@
       [x, y] = coords.major
       [sx, sy] = @plot_view.map_to_screen(x, "data", y, "data", @x_range_name, @y_range_name)
       [nx, ny] = @mget('normals')
+      [xoff, yoff]  = @mget('offsets')

       tin = @mget('major_tick_in')
       tout = @mget('major_tick_out')
       @major_tick_props.set(ctx, @)
       for i in [0...sx.length]
         ctx.beginPath()
-        ctx.moveTo(Math.round(sx[i]+nx*tout), Math.round(sy[i]+ny*tout))
-        ctx.lineTo(Math.round(sx[i]-nx*tin),  Math.round(sy[i]-ny*tin))
+        ctx.moveTo(Math.round(sx[i]+nx*tout+nx*xoff), Math.round(sy[i]+ny*tout+ny*yoff))
+        ctx.lineTo(Math.round(sx[i]-nx*tin+nx*xoff),  Math.round(sy[i]-ny*tin+ny*yoff))
         ctx.stroke()

     _draw_minor_ticks: (ctx) ->
@@ -222,14 +224,15 @@
       [x, y] = coords.minor
       [sx, sy] = @plot_view.map_to_screen(x, "data", y, "data", @x_range_name, @y_range_name)
       [nx, ny] = @mget('normals')
+      [xoff, yoff]  = @mget('offsets')

       tin = @mget('minor_tick_in')
       tout = @mget('minor_tick_out')
       @minor_tick_props.set(ctx, @)
       for i in [0...sx.length]
         ctx.beginPath()
-        ctx.moveTo(Math.round(sx[i]+nx*tout), Math.round(sy[i]+ny*tout))
-        ctx.lineTo(Math.round(sx[i]-nx*tin),  Math.round(sy[i]-ny*tin))
+        ctx.moveTo(Math.round(sx[i]+nx*tout+nx*xoff), Math.round(sy[i]+ny*tout+ny*yoff))
+        ctx.lineTo(Math.round(sx[i]-nx*tin+nx*xoff),  Math.round(sy[i]-ny*tin+ny*yoff))
         ctx.stroke()

     _draw_major_labels: (ctx) ->
@@ -237,6 +240,7 @@
       [x, y] = coords.major
       [sx, sy] = @plot_view.map_to_screen(x, "data", y, "data", @x_range_name, @y_range_name)
       [nx, ny] = @mget('normals')
+      [xoff, yoff]  = @mget('offsets')
       dim = @mget('dimension')
       side = @mget('location')
       orient = @mget('major_label_orientation')
@@ -254,13 +258,13 @@

       for i in [0...sx.length]
         if angle
-          ctx.translate(sx[i]+nx*standoff, sy[i]+ny*standoff)
+          ctx.translate(sx[i]+nx*standoff+nx*xoff, sy[i]+ny*standoff+ny*yoff)
           ctx.rotate(angle)
           ctx.fillText(labels[i], 0, 0)
           ctx.rotate(-angle)
-          ctx.translate(-sx[i]-nx*standoff, -sy[i]-ny*standoff)
+          ctx.translate(-sx[i]-nx*standoff+nx*xoff, -sy[i]-ny*standoff+ny*yoff)
         else
-          ctx.fillText(labels[i], Math.round(sx[i] + nx*standoff), Math.round(sy[i] + ny*standoff))
+          ctx.fillText(labels[i], Math.round(sx[i]+nx*standoff+nx*xoff), Math.round(sy[i]+ny*standoff+ny*yoff))

     _draw_axis_label: (ctx) ->
       label = @mget('axis_label')
@@ -271,6 +275,7 @@
       [x, y] = @mget('rule_coords')
       [sx, sy] = @plot_view.map_to_screen(x, "data", y, "data", @x_range_name, @y_range_name)
       [nx, ny] = @mget('normals')
+      [xoff, yoff]  = @mget('offsets')
       side = @mget('location')
       orient = 'parallel'

@@ -284,13 +289,13 @@
       _apply_location_heuristics(ctx, side, orient)

       if angle
-        ctx.translate(sx+nx*standoff, sy+ny*standoff)
+        ctx.translate(sx+nx*standoff+nx*xoff, sy+ny*standoff+ny*yoff)
         ctx.rotate(angle)
         ctx.fillText(label, 0, 0)
         ctx.rotate(-angle)
-        ctx.translate(-sx-nx*standoff, -sy-ny*standoff)
+        ctx.translate(-sx-nx*standoff+nx*xoff, -sy-ny*standoff+ny*yoff)
       else
-        ctx.fillText(label, sx+nx*standoff, sy+ny*standoff)
+        ctx.fillText(label, sx+nx*standoff+nx*xoff, sy+ny*standoff+ny*yoff)

   class Axis extends HasParent
     default_view: AxisView
@@ -312,6 +317,7 @@
       @register_property('ranges', @_ranges, true)
       @register_property('normals', (() -> @_normals), true)
       @register_property('dimension', (() -> @_dim), true)
+      @register_property('offsets', @_offsets, true)

     initialize_layout: (solver) ->
       panel = new LayoutBox.Model({solver: solver})
@@ -362,6 +368,25 @@
       @_size_constraint = new kiwi.Constraint(new kiwi.Expression(@_size, -size), kiwi.Operator.Eq)
       solver.add_constraint(@_size_constraint)

+    _offsets: () ->
+      side = @get('location')
+      [xoff, yoff] = [0, 0]
+      frame = @get('plot').get('frame')
+
+      if side == "below"
+        yoff = Math.abs(@panel.get("top") - frame.get("bottom"))
+
+      else if side == "above"
+        yoff = Math.abs(@panel.get("bottom") - frame.get("top"))
+
+      else if side == "right"
+        xoff = Math.abs(@panel.get("left") - frame.get("right"))
+
+      else if side == "left"
+        xoff = Math.abs(@panel.get("right") - frame.get("left"))
+
+      return [xoff, yoff]
+
     _ranges: () ->
       i = @get('dimension')
       j = (i + 1) % 2


payment: group view hide/show operations into one place

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -14,13 +14,7 @@
     planInterval   : PaymentWorkflow.planInterval.MONTH
     planTitle      : PaymentWorkflow.planTitle.HOBBYIST
     providerLoaded : no
-    validation     : {
-      cardNumber   : yes
-      cardCVC      : yes
-      cardName     : yes
-      cardMonth    : yes
-      cardYear     : yes
-    }
+

   constructor: (options = {}, data) ->

@@ -38,11 +32,8 @@

   initViews: ->

-    { MONTH, YEAR } = PaymentWorkflow.planInterval
-
     {
-      planTitle, planInterval
-      reducedMonth, discount, currentPlan
+      planTitle, planInterval, reducedMonth, currentPlan
     } = @state

     @plan = new KDCustomHTMLView
@@ -53,8 +44,6 @@
     @price = new KDCustomHTMLView
       cssClass : 'plan-price'
       partial  : pricePartial
-
-    { FREE } = PaymentWorkflow.planTitle

     @form = @initForm()

@@ -67,13 +56,6 @@
     @successMessage = new KDCustomHTMLView
       cssClass : 'success-msg hidden'
       partial  : ''
-
-    # if their currentPlan is not free it means that
-    # we already have their credit card,
-    # so don't show the form show the existing
-    # credit card message.
-    @form.hide()  unless currentPlan is FREE
-    @existingCreditCardMessage.hide()  if currentPlan is FREE

     isUpgrade = PaymentWorkflow.isUpgrade currentPlan, planTitle

@@ -103,15 +85,32 @@
         Koding.com uses 128 Bit SSL Encrypted Transactions
       "

+    # for some cases, we need to show/hide
+    # some of the subviews.
+    @filterViews()
+
+
+  filterViews: ->
+
+    { FREE } = PaymentWorkflow.planTitle
+    { currentPlan, planTitle } = @state
+
     # no need to show those views when they are
     # downgrading to free account.
-    # TODO: move this into more proper place. ~U
-    if planTitle is FREE
+    if selectedPlan = planTitle is FREE
       [
         @securityNote
         @existingCreditCardMessage
       ].forEach (view) -> view.hide()

+    # if their currentPlan is not free it means that
+    # we already have their credit card,
+    # don't show existing cc message, show
+    # cc form.
+    if currentPlan is FREE
+      @form.show()
+      @existingCreditCardMessage.hide()
+

   initForm: ->

@@ -119,6 +118,7 @@
     { planTitle, planInterval } = @state

     { cssClass } = @getOptions()
+    cssClass     = @utils.curry cssClass, 'hidden'

     return new StripeFormView
       state    : @state


autocompile: fixed auto compile problem on linux in stylus files

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -450,7 +450,7 @@

         onRaw = (e, file) ->
           return  unless file
-          if e is 'modified' or e is 'deleted'
+          if e in ['change','modified','deleted']
             console.log "updated #{dir}"
             start = Date.now()
             styl manifest, globs


update route

update route

--- example/routes.coffee
+++ example/routes.coffee
@@ -8,7 +8,7 @@
       subtitle: "This isn't really home, its work."
   },{
     route: 'gitHub'
-    path: "https://github.com/lumapictures/luma-ui"
+    path: "https://github.com/lumapictures/meteor-luma-ui"
     external: true
     page:
       title: "GitHub"
@@ -18,14 +18,14 @@
       icon: 'icon-github'
   },{
     route: 'reportBugs'
-    path: "https://github.com/lumapictures/luma-ui/issues/new"
+    path: "https://github.com/lumapictures/meteor-luma-ui/issues/new"
     external: true
     page:
       title: "Report Bugs"
       subtitle: "GitHub Issues"
   },{
     route: 'source'
-    path: "http://lumapictures.github.io/luma-ui/"
+    path: "http://lumapictures.github.io/meteor-luma-ui/"
     external: true
     page:
       title: "Annotated Source"


activity: render sidebar in own method

activity: render sidebar in own method

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -129,6 +129,13 @@
     </div>

+  renderSidebar: ->
+
+    <ThreadSidebar
+      channelThread={thread}
+      channelParticipants={@state.channelParticipants} />
+
+
   render: ->

     return null  unless thread = @state.channelThread
@@ -142,12 +149,8 @@
         {@renderBody()}
       </section>
       <aside className='ChannelThreadPane-sidebar'>
-        <ThreadSidebar
-          channelThread={thread}
-          channelParticipants={@state.channelParticipants} />
+        {@renderSidebar()}
       </aside>
-
-      {@props.children}
     </div>


Activity:  PublicFeedComponent: #changelog syntax changed

--- client/activity/lib/components/publicfeedpane/feedpane.coffee
+++ client/activity/lib/components/publicfeedpane/feedpane.coffee
@@ -107,7 +107,7 @@

     channelId   = @props.thread.get 'channelId'
     typeConstant = @props.thread.getIn(['channel', 'typeConstant'])
-    isAnnouncementChannel = (typeConstant == 'announcement') ? true : false
+    isAnnouncementChannel = typeConstant is 'announcement'

     <Scroller
       onScroll={@bound 'onScroll'}


vmdetailsview: make RAM Usage view better, about asana task #6623441442867

--- client/app/MainApp/filetree/itemsubviews/vmdetailsview.coffee
+++ client/app/MainApp/filetree/itemsubviews/vmdetailsview.coffee
@@ -8,7 +8,7 @@
     super options, data

     @labelRAM = new KDLabelView
-      title : '0%'
+      title : '0 MB / 0 MB'

     @labelRX = new KDLabelView
       title : '0 byte'
@@ -37,9 +37,13 @@
       return warn err

     if info.state is "RUNNING"
-      # Memory
-      mem = ((info.memoryUsage * 100) / info.totalMemoryLimit).toFixed(2)
-      @labelRAM.updateTitle "#{mem}%"
+      memUsageInPercentage = ((info.memoryUsage * 100) / info.totalMemoryLimit).toFixed(2)
+
+      #convert bytes into mb
+      memUsageInMB = (info.memoryUsage / 1024 / 1024).toFixed(2)
+      totalMemoryLimitInMB = info.totalMemoryLimit / 1024 / 1024
+
+      @labelRAM.updateTitle "#{memUsageInMB} MB (#{memUsageInPercentage}%) of #{totalMemoryLimitInMB} MB"
       # RX
       @kcRun cmdRX, (err, out)=>
         bytes = (parseInt(out) / 1024).toFixed(2)


5960: hide readonly when prefill value is empty

--- app/assets/javascripts/backbone/views/question_form_view.js.coffee
+++ app/assets/javascripts/backbone/views/question_form_view.js.coffee
@@ -4,13 +4,24 @@
     @prefillableTypes = options.prefillableTypes # Will be null for Question form
     @toggleFields()
     @toggleRequired(@$('#questioning_read_only')[0].checked)
+    @toggleReadOnly(@$('#questioning_prefill_pattern')[0].value)

   events:
     'change select[id$="_qtype_name"]': 'typeChanged'
     'click #questioning_read_only': 'readOnlyStatusChanged'
+    'keyup #questioning_prefill_pattern' : 'prefillPatternChanged'

   typeChanged: (e) ->
     @toggleFields()
+
+  prefillPatternChanged: (e) ->
+    console.log("prefill pattern changed")
+    prefill_value = e.target.value
+    @toggleReadOnly(prefill_value)
+
+  toggleReadOnly: (prefill_value) ->
+    console.log('prefill value: ' + prefill_value)
+    @$('.questioning_read_only')[if prefill_value == '' then 'hide' else 'show']()

   readOnlyStatusChanged: (e) ->
     @toggleRequired(e.target.checked)


Add coffee script geolocation

Add coffee script geolocation

--- views/landing.coffee
+++ views/landing.coffee
@@ -6,4 +6,13 @@

       a class: "btn btn-large btn-primary", href: '/auth/google' , "Sign in with Google"

+    coffeescript ->
+      $ ->
+      navigator.geolocation.getCurrentPosition (position) ->
+        startPos = position
+        $('a.btn').before "

Look! Your latitude is #{position.coords.latitude}, and your longitude is #{position.coords.longitude}!

How handy is that?

"
+        console.log position
+
+
+


Restored mp_track

Restored mp_track

--- core/app/backbone/views/facts/auto_complete_facts_view.coffee
+++ core/app/backbone/views/facts/auto_complete_facts_view.coffee
@@ -85,12 +85,12 @@

     @_text_input_view = new Backbone.Factlink.ReactTextInputView
       placeholder: 'Search discussion link to insert...'
-      onChange: (value) => @search_collection.searchFor value
+      onChange: (value) =>
+        @search_collection.searchFor value
+        @queryChanges()
       onUp: => @_search_list_view.moveSelectionUp()
       onDown: => @_search_list_view.moveSelectionDown()
       onReturn: => @addSelectedModel()
-
-    # @listenTo @model, 'change', @queryChanges

   onRender: ->
     @search_list.show new ReactView component: @_search_list_view
@@ -106,7 +106,7 @@
   reset: ->
     # @model.set text: ''

-  # queryChanges: ->
-  #   unless @query_has_changed
-  #     @query_has_changed = true
-  #     mp_track "Evidence: Started searching for insertable discussion"
+  queryChanges: ->
+    unless @query_has_changed
+      @query_has_changed = true
+      mp_track "Evidence: Started searching for insertable discussion"


SidebarWorkspaceItem: Don't show workspace settings modal if machine is not mine.

--- client/app/lib/activity/sidebar/sidebarworkspaceitem.coffee
+++ client/app/lib/activity/sidebar/sidebarworkspaceitem.coffee
@@ -37,7 +37,7 @@

     iconOptions = {}

-    unless workspace.isDefault
+    if not workspace.isDefault and machine.isMine()
       iconOptions =
         tagName   : 'span'
         cssClass  : 'ws-settings-icon'


don't (yet) make unhandled particle data fatal

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -325,8 +325,7 @@
             if name['dropping-particle']
               name.given = ((name.given || '') + ' ' + name['dropping-particle']).trim()

-            # throw error to find these among the current tests
-            throw new Error('enc_creators: ' + JSON.stringify(name)) if name['comma-suffix'] || name['comma-dropping-particle'] || name.suffix
+            Translator.debug('unhandled particla data: ', creator, '->', name) if name['comma-suffix'] || name['comma-dropping-particle'] || name.suffix

         if name.given
           name.given = @enc_latex({value: name.given}).replace(/ and /g, ' {and} ')


atom: Linter stuff

atom: Linter stuff

--- home-atom/.atom/config.cson
+++ home-atom/.atom/config.cson
@@ -65,6 +65,7 @@
     closeEmptyWindows: false
     disabledPackages: [
       "open-on-github"
+      "linter-js-standard"
     ]
     projectHome: "/home/q"
     restorePreviousWindowsOnStart: "no"
@@ -74,7 +75,6 @@
       "one-light-syntax"
     ]
   editor:
-    preferredLineLength: 120
     showIndentGuide: true
     showInvisibles: true
     softWrap: true
@@ -96,16 +96,25 @@
     ignoreWhiteSpaceDiffs: true
   "language-ledger":
     ledgerBinary: "/home/q/docs/ledger/bin/ledger-trimmed-error.sh"
+  linter:
+    disabledProviders: [
+      "js-standard"
+    ]
   "linter-clang":
     clangDefaultCFlags: "-Wall -Wextra -Wno-unused-parameter"
     clangDefaultCppFlags: "-Wall -std=c++11 -Wextra -Wno-unused-parameter"
   "linter-js-standard":
     checkForEslintConfig: false
     showEslintRules: true
+  "linter-pylint":
+    executablePath: "pylint3"
   "linter-ui-default":
     showPanel: true
   "markdown-preview":
     useGitHubStyle: true
+  "python-autopep8":
+    formatOnSave: true
+    maxLineLength: 80
   "spell-check":
     addKnownWords: true
     knownWords: [


bugfix in Dialogs

bugfix in Dialogs

--- imports/util/dialogs.coffee
+++ imports/util/dialogs.coffee
@@ -141,7 +141,7 @@
 				swal.showInputError(inputError)
 				false
 			else
-				swalClose() if closeOnSuccess
+				Dialogs.swalClose() if closeOnSuccess
 				doConfirm(inputValue)

 	swalClose: -> swal title: '', timer: 0


NM_FRAME -+ added

NM_FRAME -+ added

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/get_meta_values.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/get_meta_values.coffee
@@ -20,12 +20,15 @@
     minEndSegmentsAtLastSlide : "Number"
     noStartSegmentAtLastSlide : "Boolean"

+  FRAME_NM_ADD : 3

   constructor : () ->

   execute : (options) ->
+
+    { FRAME_NM_ADD } = @

     # desiredSlidesBeforeProblem
     # minEndSegmentsAtFirstSlide
@@ -86,15 +89,15 @@

     # get StartSlide - apply desiredSlidesBeforeProblem and MIN_SEGMENTS_AT_FIRST_SLIDE

-    firstStartFrame = DataUtils.nmToSlide(mission.start.firstFrame, slidesBeforeProblem)
+    firstStartFrame = DataUtils.nmToSlide(mission.start.firstFrame, slidesBeforeProblem) + FRAME_NM_ADD

     searchStart = Math.max(firstStartFrame, desiredStartSlide)

-    for i in [Math.floor(searchStart)...slidesBeforeProblem] by 1
+    for i in [Math.round(searchStart)...slidesBeforeProblem] by 1
       result = _.filter(mission.possibleEnds, (e) =>
         #console.log DataUtils.nmToSlide(e.firstFrame) + "  - " + i + " -  " + DataUtils.nmToSlide(e.lastFrame)
-        DataUtils.nmToSlide(e.firstFrame, slidesBeforeProblem) < i < DataUtils.nmToSlide(e.lastFrame, slidesBeforeProblem) ).length
+        DataUtils.nmToSlide(e.firstFrame, slidesBeforeProblem) + FRAME_NM_ADD < i < DataUtils.nmToSlide(e.lastFrame, slidesBeforeProblem) - FRAME_NM_ADD ).length
       #console.log i + " " + result
       if result >= minEndSegmentsAtFirstSlide
         startSlide = i
@@ -105,11 +108,11 @@

     # get endSlide
-    lastStartFrame = DataUtils.nmToSlide(mission.start.lastFrame, slidesBeforeProblem)
+    lastStartFrame = DataUtils.nmToSlide(mission.start.lastFrame, slidesBeforeProblem) - FRAME_NM_ADD

     endS = _.detect(mission.possibleEnds, id: mission.end.id)
     if endS?
-      maxEndSlide = Math.min(DataUtils.nmToSlide(endS.lastFrame, slidesBeforeProblem), desiredEndSlide)
+      maxEndSlide = Math.min(DataUtils.nmToSlide(endS.lastFrame, slidesBeforeProblem) - FRAME_NM_ADD, desiredEndSlide)
     else
       maxEndSlide = desiredEndSlide

@@ -118,10 +121,10 @@
     else
       searchEnd = slidesBeforeProblem

-    for i in [Math.floor(maxEndSlide)...searchEnd] by -1
+    for i in [Math.round(maxEndSlide)...searchEnd] by -1
       result = _.filter(mission.possibleEnds, (e) =>
         #console.log DataUtils.nmToSlide(e.firstFrame) + "  - " + i + " -  " + DataUtils.nmToSlide(e.lastFrame)
-        DataUtils.nmToSlide(e.firstFrame, slidesBeforeProblem) < i < DataUtils.nmToSlide(e.lastFrame, slidesBeforeProblem) ).length
+        DataUtils.nmToSlide(e.firstFrame, slidesBeforeProblem) + FRAME_NM_ADD < i < DataUtils.nmToSlide(e.lastFrame, slidesBeforeProblem) - FRAME_NM_ADD ).length
       #console.log i + " " + result
       if result >= minEndSegmentsAtLastSlide
         endSlide = i


trying something

trying something

--- javascripts/gh-pages.coffee
+++ javascripts/gh-pages.coffee
@@ -4,4 +4,4 @@
 #
 ###

-$ -> $(".repository-name").each -> $(@).wrap("")
+$ -> $(".repository-name").each -> $(@).wrap("")


Update emissions text

Update emissions text

--- app/assets/javascripts/analysis/templates/area.coffee
+++ app/assets/javascripts/analysis/templates/area.coffee
@@ -39,7 +39,7 @@

-
Avg. Person CO2 Emissions EQV.

+
Equivalent per capita CO2 emissions


* Fixed memory leak in release of Model relations collections

--- app/assets/javascripts/backbone.rails_store.js.coffee
+++ app/assets/javascripts/backbone.rails_store.js.coffee
@@ -122,6 +122,20 @@
     col = @getCollectionFromModel(model)
     col.remove(model,options)
     modelType = @getModelType(model)
+    modelClass = @_getModelTypeObj(modelType)
+    if modelClass.prototype.hasMany
+      _.each modelClass.prototype.hasMany, (data, key) =>
+        if data.subset and data.subset[model.cid]
+          data.subset[model.cid].dispose()
+          data.subset[model.cid].off()
+          delete data.subset[model.cid]
+    if modelClass.prototype.hasAndBelongsToMany
+      _.each modelClass.prototype.hasAndBelongsToMany, (data, key) =>
+        if data.subset and data.subset[model.cid]
+          if data.subset[model.cid].collection
+            data.subset[model.cid].collection.dispose()
+            data.subset[model.cid].collection.off()
+          delete data.subset[model.cid]
     @_changedModels[modelType] = _.without(@_changedModels[modelType], model) if @_changedModels[modelType]
     @_deletedModels = _.without(@_deletedModel, model) if @_deletedModels
     modelId = model.id if model.id


get textRowTool highlighting to work

get textRowTool highlighting to work

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -269,68 +269,113 @@
               height = {@state.imageHeight} />
           </Draggable>

-          { # HANDLE RECTANGLE TOOL HIGHLIGHTS
-
-            if @props.workflow.name is 'transcribe' and @props.subject.location.spec.toolName is 'rectangleTool'
-            isPriorAnnotation = true # ?
-            <g key={@props.subject.id} className="marks-for-annotation" data-disabled={isPriorAnnotation}>
-              {
-                console.log '@props.subject.location.spec: ', @props.subject.location.spec
-                # Represent the secondary subject as a rectangle mark
-                # TODO Should really check the drawing tool used (encoded somehow in the 2ndary subject) and display a read-only instance of that tool. For now just defaulting to rect:
-                ToolComponent = markingTools['rectangleTool']
-                # TODO: Note that x, y, w h aren't scaled properly:
-                mark = {x: @props.subject.location.spec.x, y: @props.subject.location.spec.y, width: @props.subject.location.spec.width, height: @props.subject.location.spec.height}
-
-                <g>
-                  <rect
-                    className   = "mark-rectangle top"
-                    x           = 0
-                    y           = 0
-                    width       = { @state.imageWidth }
-                    height      = { mark.y }
-                    fill        = "rgba(0,0,0,0.6)"
-                  />
-                  <rect
-                    className   = "mark-rectangle bottom"
-                    x           = 0
-                    y           = { mark.y + mark.height }
-                    width       = { @state.imageWidth }
-                    height      = { @state.imageHeight - mark.y + mark.height }
-                    fill        = "rgba(0,0,0,0.6)"
-                  />
-                  <rect
-                    className   = "mark-rectangle left"
-                    x           = 0
-                    y           = { mark.y }
-                    width       = { mark.x }
-                    height      = { mark.height }
-                    fill        = "rgba(0,0,0,0.6)"
-                  />
-                  <rect
-                    className   = "mark-rectangle right"
-                    x           = { mark.x + mark.width}
-                    y           = { mark.y }
-                    width       = { @state.imageWidth - mark.width - mark.x }
-                    height      = { mark.height }
-                    fill        = "rgba(0,0,0,0.6)"
-                  />
-
-                  <ToolComponent
-                    key={@props.subject.id}
-                    mark={mark}
-                    xScale={scale.horizontal}
-                    yScale={scale.vertical}
-                    disabled={isPriorAnnotation}
-                    selected={mark is @state.selectedMark}
-                    getEventOffset={@getEventOffset}
-                    ref={@refs.sizeRect}
-
-                    onSelect={@selectMark.bind this, @props.subject, mark}
-                  />
-                </g>
-              }
-            </g>
+          { if @props.workflow.name is 'transcribe' and @props.subject.location.spec.toolName is 'rectangleTool'
+              isPriorAnnotation = true # ?
+              <g key={@props.subject.id} className="marks-for-annotation" data-disabled={isPriorAnnotation}>
+                {
+                  console.log '@props.subject.location.spec: ', @props.subject.location.spec
+                  # Represent the secondary subject as a rectangle mark
+                  # TODO Should really check the drawing tool used (encoded somehow in the 2ndary subject) and display a read-only instance of that tool. For now just defaulting to rect:
+                  ToolComponent = markingTools['rectangleTool']
+                  # TODO: Note that x, y, w h aren't scaled properly:
+                  mark = {x: @props.subject.location.spec.x, y: @props.subject.location.spec.y, width: @props.subject.location.spec.width, height: @props.subject.location.spec.height}
+
+                  <g>
+                    <rect
+                      className   = "mark-rectangle top"
+                      x           = 0
+                      y           = 0
+                      width       = { @state.imageWidth }
+                      height      = { mark.y }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+                    <rect
+                      className   = "mark-rectangle bottom"
+                      x           = 0
+                      y           = { mark.y + mark.height }
+                      width       = { @state.imageWidth }
+                      height      = { @state.imageHeight - mark.y + mark.height }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+                    <rect
+                      className   = "mark-rectangle left"
+                      x           = 0
+                      y           = { mark.y }
+                      width       = { mark.x }
+                      height      = { mark.height }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+                    <rect
+                      className   = "mark-rectangle right"
+                      x           = { mark.x + mark.width}
+                      y           = { mark.y }
+                      width       = { @state.imageWidth - mark.width - mark.x }
+                      height      = { mark.height }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+
+                    <ToolComponent
+                      key={@props.subject.id}
+                      mark={mark}
+                      xScale={scale.horizontal}
+                      yScale={scale.vertical}
+                      disabled={isPriorAnnotation}
+                      selected={mark is @state.selectedMark}
+                      getEventOffset={@getEventOffset}
+                      ref={@refs.sizeRect}
+
+                      onSelect={@selectMark.bind this, @props.subject, mark}
+                    />
+                  </g>
+                }
+              </g>
+          }
+
+          { if @props.workflow.name is 'transcribe' and @props.subject.location.spec.toolName is 'textRowTool'
+              isPriorAnnotation = true # ?
+              <g key={@props.subject.id} className="marks-for-annotation" data-disabled={isPriorAnnotation}>
+                {
+                  console.log '@props.subject.location.spec: ', @props.subject.location.spec
+                  # Represent the secondary subject as a rectangle mark
+                  # TODO Should really check the drawing tool used (encoded somehow in the 2ndary subject) and display a read-only instance of that tool. For now just defaulting to rect:
+                  ToolComponent = markingTools['textRowTool']
+                  # TODO: Note that x, y, w h aren't scaled properly:
+                  mark = {x: @props.subject.location.spec.x, y: @props.subject.location.spec.y, yUpper: @props.subject.location.spec.yUpper, yLower: @props.subject.location.spec.yLower}
+
+                  <g>
+                    <rect
+                      className   = "mark-rectangle"
+                      x           = 0
+                      y           = { 0 }
+                      width       = { @state.imageWidth }
+                      height      = { mark.yUpper }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+
+                    <rect
+                      className   = "mark-rectangle"
+                      x           = 0
+                      y           = { mark.yLower }
+                      width       = { @state.imageWidth }
+                      height      = { @state.imageHeight - mark.yLower }
+                      fill        = "rgba(0,0,0,0.6)"
+                    />
+
+                    <ToolComponent
+                      key={@props.subject.id}
+                      mark={mark}
+                      xScale={scale.horizontal}
+                      yScale={scale.vertical}
+                      disabled={isPriorAnnotation}
+                      selected={mark is @state.selectedMark}
+                      getEventOffset={@getEventOffset}
+                      ref={@refs.sizeRect}
+
+                      onSelect={@selectMark.bind this, @props.subject, mark}
+                    />
+                  </g>
+                }
+              </g>
           }

           { for annotation in @props.classification.annotations


Handle both bzipped and non-bzipped data in DataUnpacker

--- app/assets/javascripts/player/data_unpacker.js.coffee
+++ app/assets/javascripts/player/data_unpacker.js.coffee
@@ -1,11 +1,14 @@
 class AsciiIo.DataUnpacker
   unpack: (base64BzippedData, callback) ->
-    bzippedData = atob base64BzippedData
+    data = atob base64BzippedData

-    if window.Worker
-      worker = new Worker(window.unpackWorkerPath)
-      worker.onmessage = (event) => callback event.data
-      worker.postMessage bzippedData
+    if data[0] == 'B' and data[1] == 'Z'
+      if window.Worker
+        worker = new Worker(window.unpackWorkerPath)
+        worker.onmessage = (event) => callback event.data
+        worker.postMessage data
+      else
+        data = ArchUtils.bz2.decode data
+        callback data
     else
-      data = ArchUtils.bz2.decode bzippedData
-      callback data
+      data


Use single parent element for faster innerHTML

--- app/assets/javascripts/player/terminal_view.js.coffee
+++ app/assets/javascripts/player/terminal_view.js.coffee
@@ -68,7 +68,7 @@

     html.push '' if prevBrush

-    @$el.find(".line:eq(" + n + ")")[0].innerHTML = html.join('')
+    @$el.find(".line:eq(" + n + ")")[0].innerHTML = '' + html.join('') + ''

   spanFromBrush: (brush, hasCursor) ->
     key = "#{brush.hash()}_#{hasCursor}"


add dropdown editor to the drawing detail subtasks

--- app/classifier/tasks/drawing-task-details-editor.cjsx
+++ app/classifier/tasks/drawing-task-details-editor.cjsx
@@ -3,6 +3,7 @@
 TriggeredModalForm = require 'modal-form/triggered'
 TextTaskEditor = require './text/editor'
 SliderTaskEditor = require('./slider/editor').default
+DropdownEditor = require './dropdown/editor'

 module.exports = React.createClass
@@ -39,6 +40,15 @@
                   />
                 else if subtask.type is 'slider'
                   <SliderTaskEditor
+                    workflow={@props.workflow}
+                    task={subtask}
+                    taskPrefix="#{@props.toolPath}.details.#{i}"
+                    isSubtask={true}
+                    onChange={@handleSubtaskChange.bind this, i}
+                    onDelete={@handleSubtaskDelete.bind this, i}
+                  />
+                else if subtask.type is 'dropdown'
+                  <DropdownEditor
                     workflow={@props.workflow}
                     task={subtask}
                     taskPrefix="#{@props.toolPath}.details.#{i}"
@@ -86,6 +96,12 @@

                 Text
+              </button>{' '}
+
+              <button type="submit" className="minor-button" onClick={@handleAddTask.bind this, 'dropdown'} title="Dropdown tasks: the volunteer selects a text label from a list.">
+
+

+                Dropdown
               </button>
           </TriggeredModalForm>

@@ -103,6 +119,8 @@
         TaskChoice = require './text'
       when 'slider'
         TaskChoice = require('./slider').default
+      when 'dropdown'
+        TaskChoice = require './dropdown'
     @props.task.tools[@props.toolIndex].details.push TaskChoice.getDefaultTask()
     @props.workflow.update 'tasks'
     @props.workflow.save()


flux: teams: minor changes

flux: teams: minor changes

--- client/app/lib/flux/teams/actions.coffee
+++ client/app/lib/flux/teams/actions.coffee
@@ -17,6 +17,7 @@
   { groupsController, reactor } = kd.singletons

   team = groupsController.getCurrentGroup()
+
   canEditGroup = groupsController.canEditGroup()
   reactor.dispatch actions.LOAD_TEAM_SUCCESS, team

@@ -63,7 +64,7 @@
         email = inviteInput.get('email').trim()

         return  unless email
-        console.log 'validateEmail', validateEmail email
+
         validatedEmail = validateEmail email

         if email.toLowerCase() is ownEmail
@@ -128,13 +129,10 @@

   myId = whoami().getId()
   teamMemberIds = reactor.evaluate getters.TeamMembersIdStore
-
-  ids = []
+
+  ids = teamMemberIds.toArray()
   ids.push myId
-
-  teamMemberIds.map (id) ->
-    ids.push id
-
+
   team.fetchUserRoles ids, (err, roles) ->
     reactor.dispatch actions.FETCH_TEAM_MEMBERS_ROLES_SUCCESS, roles

@@ -151,37 +149,13 @@
       if err
         reject err
       invites = invites.map (invite) -> invite.email
-      pendingInvitations = pendings.reduce (pendingInvitations, invitation, index) ->
-        i = pendingInvitations.size
-        return pendingInvitations.set i, toImmutable(invitation)  if invitation.email in invites
+      pendingInvitations = pendings.reduce (pendingInvitations, invitation) ->
+        index = pendingInvitations.size
+        return pendingInvitations.set index, toImmutable(invitation)  if invitation.email in invites
         return pendingInvitations
       , immutable.Map()

       resolve { pendingInvitations }
-
-
-fetchTeamChannels = (channelUrl) ->
-
-  $.ajax
-    method : 'GET'
-    url : channelUrl
-    success : (res) ->
-      debugger
-      console.log 'res *', res
-    error: (err) ->
-
-      console.log 'err* ', err
-
-
-fetchUsersFromChannel = (usersUrl) ->
-
-  $.ajax
-    method : 'GET'
-    url : usersUrl
-    success : (res) ->
-      console.log 'res'
-    error: (err) ->
-      console.log 'err ', err

 sendInvitations = (invites, pendingInvites) ->
@@ -207,7 +181,6 @@
   new Promise (resolve, reject) ->
     title    = 'Invitation is resent.'
     title    = 'Invitations are resent.'  if pendingInvitations.size > 1
-    console.log 'pendingInvitations ', pendingInvitations

     queue = pendingInvitations.toArray().map (invite) -> (next) ->

@@ -264,6 +237,7 @@
     # handle, what if there is error
     unless err
       reactor.dispatch actions.DELETE_TEAM_MEMBER, memberId
+

 module.exports = {
@@ -274,8 +248,6 @@
   fetchMembers
   fetchMembersRole
   loadPendingInvites
-  fetchTeamChannels
-  fetchUsersFromChannel
   getNewInvitations
   sendInvitations
   resendInvitations


Add classes for size

Add classes for size

--- client/lanes/components/shared/Icon.cjsx
+++ client/lanes/components/shared/Icon.cjsx
@@ -9,6 +9,12 @@
         classes = _.classnames 'icon', "icon-#{@props.type}", @props.className,
             'cursor-pointer'      : @props.onClick,
             "icon-#{@props.size}" : @props.size,
-            'icon-pulse'          : @props.animated
+            'icon-pulse' : @props.animated
+            'flush'      : @props.flush
+            'icon-lg'    : @props['lg']
+            'icon-2x'    : @props['2x']
+            'icon-3x'    : @props['3x']
+            'icon-4x'    : @props['4x']
+            'icon-5x'    : @props['5x']

         <i {...@props} className={classes} />


activity: scroll to individual element for single post route

--- client/activity/lib/components/chatlist/index.coffee
+++ client/activity/lib/components/chatlist/index.coffee
@@ -8,6 +8,7 @@
 NewMessageMarker   = require 'activity/components/newmessagemarker'
 KDReactorMixin     = require 'app/flux/reactormixin'
 ActivityFlux       = require 'activity/flux'
+scrollToElement    = require 'app/util/scrollToElement'

 module.exports = class ChatList extends React.Component
@@ -58,6 +59,16 @@
     return markers

+  componentDidUpdate: (prevProps, prevState) ->
+
+    prevSelectedId = prevState.selectedMessageId
+    currentSelectedId = @state.selectedMessageId
+
+    if currentSelectedId and currentSelectedId isnt prevSelectedId
+      target = React.findDOMNode @refs.selectedComponent
+      scrollToElement target
+
+
   renderChildren: ->

     { messages, showItemMenu, channelName } = @props
@@ -72,7 +83,10 @@
         message      : message
         showItemMenu : showItemMenu
         channelName  : channelName
-        isSelected   : selectedMessageId is message.get 'id'
+
+      if selectedMessageId is message.get 'id'
+        itemProps['isSelected'] = yes
+        itemProps['ref'] = 'selectedComponent'

       children = children.concat @getMarkers message, prevMessage, i


Client: Activity: Deleted unnecessary channelstore handlers

--- client/activity/lib/flux/stores/channelsstore.coffee
+++ client/activity/lib/flux/stores/channelsstore.coffee
@@ -38,24 +38,12 @@
       return map

-  handleFollowChannelBegin: (channels, { channelId }) -> channels
-
-
-  handleFollowChannelFail: (channels, { channelId }) -> channels
-
-
   handleFollowChannelSuccess: (channels, { channelId }) ->

     if channels.has channelId
       channels = channels.setIn [channelId, 'isParticipant'], yes

     return channels
-
-
-  handleUnfollowChannelBegin: (channels, { channelId }) -> channels
-
-
-  handleUnfollowChannelFail: (channels, { channelId }) -> channels

   handleUnfollowChannelSuccess: (channels, { channelId }) ->


VMSettings: Disabled "Specs, Domains, Snapshots" views for managed machines.

--- client/app/lib/providers/machinesettingsmodal.coffee
+++ client/app/lib/providers/machinesettingsmodal.coffee
@@ -70,13 +70,18 @@

   createPanes: ->

-    isMachineRunning = @getData().status.state is Machine.State.Running
-    disabledTabs     = [ 'Disk Usage', 'Domains', 'VM Sharing', 'Snapshots' ]
+    machine                     = @getData()
+    isMachineRunning            = machine.status.state is Machine.State.Running
+    disabledTabsForNotRunningVM = [ 'Disk Usage', 'Domains', 'VM Sharing', 'Snapshots' ]
+    disabledTabsForManagedVM    = [ 'Specs', 'Domains', 'Snapshots' ]

     for item in PANE_CONFIG when item.title and item.viewClass

-      subView    = new item.viewClass item.viewOptions, @getData()
-      isDisabled = not isMachineRunning and disabledTabs.indexOf(item.title) > -1
+      subView    = new item.viewClass item.viewOptions, machine
+      isDisabled = not isMachineRunning and disabledTabsForNotRunningVM.indexOf(item.title) > -1
+
+      if machine.isManaged()
+        isDisabled = disabledTabsForManagedVM.indexOf(item.title) > -1

       @tabView.addPane pane = new KDTabPaneView
         name     : item.title


fixes #1878

fixes #1878

--- lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -345,8 +345,8 @@
             ''' + gettext("Problem Name") + '''
             ''' + gettext("Graded") + '''
             ''' + gettext("Available to Grade") + '''
-            ''' + gettext("Required") + '''
-            ''' + gettext("Progress") + '''
+            ''' + gettext("Required") + '''
+            ''' + gettext("Progress") + '''

     ''')
     @breadcrumbs.html('')
@@ -394,12 +394,12 @@
       problem_row.append($('').append(@problem_link(problem)))
       problem_row.append($('').append("#{problem.num_graded}"))
       problem_row.append($('').append("#{problem.num_pending}"))
-      problem_row.append($('').append("#{problem.num_required}"))
+      problem_row.append($('').append("#{problem.num_required}"))
       row_progress_bar = $('
').addClass('progress-bar')
       progress_value = parseInt(problem.num_graded)
       progress_max = parseInt(problem.num_required) + progress_value
       row_progress_bar.progressbar({value: progress_value, max: progress_max})
-      problem_row.append($('').append(row_progress_bar))
+      problem_row.append($('').append(row_progress_bar))
       @problem_list.append(problem_row)

   render_problem: () ->


Clarify overlapping test

Clarify overlapping test

--- enttec.coffee
+++ enttec.coffee
@@ -55,9 +55,10 @@
       throw new Error "Cannot add #{ device.type } device to #{ @type } host"

     for other in @devices
-      if (device.getStart() < other.getStart() and other.getStart() < device.getEnd() or
-         other.getStart() < device.getStart() and device.getStart() < other.getEnd() or
-         other.getStart() is device.getStart() and device.getEnd() is other.getEnd())
+      startOverLaps = device.getStart() < other.getStart() and other.getStart() < device.getEnd()
+      endOverLaps = other.getStart() < device.getStart() and device.getStart() < other.getEnd()
+      onTop = other.getStart() is device.getStart() and device.getEnd() is other.getEnd()
+      if startOverLaps or endOverLaps or onTop
         throw new Error "Device in #{ device.getStart() }...#{ device.getEnd() } clashes with device in  #{ other.getStart() }...#{ other.getEnd() }"

     if device not in @devices


remove debug logs

remove debug logs

--- models.coffee
+++ models.coffee
@@ -10,10 +10,7 @@

     all: (callback) ->
         path = "request/#{@type.toLowerCase()}/all/"
-        console.log path
         @dbClient.post path, {}, (err, response, users) ->
-            console.log err
-
             if err
                 callback err
             else if response.statusCode isnt 200


enable mp3

enable mp3

--- jample/app/assets/javascripts/components/jample/index.js.cjsx
+++ jample/app/assets/javascripts/components/jample/index.js.cjsx
@@ -150,8 +150,8 @@

   mp3_display: (mp3_data) ->
-    # for key,value of mp3_data
-    #
{key}: {value}

+    for key,value of mp3_data
+
{key}: {value}

   set_current_patch_set: (e) ->


Client: Activity: Fixed private message item links

--- client/activity/lib/components/privatechannellistitem/index.coffee
+++ client/activity/lib/components/privatechannellistitem/index.coffee
@@ -69,9 +69,9 @@

     typeConstant = channel.get 'typeConstant'
     title        = MessageListItemHelper.prepareThreadTitle channel
-    channelName  = channel.get 'name'
+    channelId    = channel.get '_id'

-    <Link href="/Channels/#{channelName}" className='ChannelListItem'>
+    <Link href="/Messages/#{channelId}" className='ChannelListItem' onClick={@props.onItemClick}>
       {title}
       {@renderDeleteButton()}
       <ActivityPromptModal {...@getDeleteItemModalProps()} isOpen={@state.isDeleting}>


Fix actions on parent post in conversation view

--- assets/javascripts/views/posts/conversation.js.coffee
+++ assets/javascripts/views/posts/conversation.js.coffee
@@ -59,8 +59,8 @@
   initPostViews: =>
     _.each ($ 'li.post', @container.$el), (el) =>
       post_id = ($ el).attr('data-id')
-      posts = [@post].concat(@get('posts')?.toArray() || []).
+      posts = [@post, @parent_post].concat(@get('posts')?.toArray() || []).
                       concat(@get('parent_posts')?.toArray() || [])
-      post = _.find posts, (p) => p.get('id') == post_id
+      post = _.find posts, (p) => p?.get('id') == post_id
       view = new TentStatus.Views.Post el: el, post: post, parentView: @
       view.trigger 'ready'


change initial temperature

change initial temperature

--- atmospheric/001-balanced-temperature/avgtfield.coffee
+++ atmospheric/001-balanced-temperature/avgtfield.coffee
@@ -45,9 +45,9 @@

     lat = (j) -> Math.PI / 256 * (128 - j)

-    #avgt.init = (263.15 for i in [0...256])
+    avgt.init = (273.15 for i in [0...256])
     #avgt.init = (253.15 + 40 * Math.cos(lat(i)) for i in [0...256])
-    avgt.init = (303.15 for i in [0...256])
+    #avgt.init = (303.15 for i in [0...256])

     avgt.evolve =  (data, energyIn) ->
         [gland, land, gair, air] = data


fix project move, which was TOTALLY BROKEN -- good thing tested before release :-)

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -1173,7 +1173,7 @@
                 ], cb)
             (cb) =>
                 dbg("update database with new project location")
-                @set_project_host
+                @compute_server.database.set_project_host
                     project_id : @project_id
                     host       : opts.target
                     cb         : (err, assigned) =>


storage: change the timeout back to 15 -- a timeout of 60 is causing a lot of people to not fail over all (many complaints)

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -253,7 +253,7 @@
             dbg("mount filesystem")
             execute_on
                 host    : opts.host
-                timeout : 60  # relatively small timeout due to zfs deadlocks -- just move onto another host
+                timeout : 15  # relatively small timeout due to zfs deadlocks -- just move onto another host
                 command : "sudo zfs set mountpoint=#{mountpoint(opts.project_id)} #{filesystem(opts.project_id)}&&sudo zfs mount #{filesystem(opts.project_id)}"
                 cb      : (err, output) ->
                     if err
@@ -3348,7 +3348,28 @@
     last_migrated2 = cassandra.now()
     host = undefined
     client = undefined
+    last_migrate2_error = undefined   # could be useful to know below...
     async.series([
+        (cb) ->
+            dbg("getting last migration error...")
+            database.select_one
+                table : 'projects'
+                columns : ['last_migrate2_error']
+                where : {project_id : opts.project_id}
+                cb    : (err, result) =>
+                    if err
+                        cb(err)
+                    else
+                        last_migrate2_error = result[0]
+                        dbg("last_migrate2_error = #{last_migrate2_error}")
+                        cb()
+        (cb) ->
+            dbg("setting last_migrate2_error to start...")
+            database.update
+                table : 'projects'
+                set   : {last_migrate2_error : 'start'}
+                where : {project_id : opts.project_id}
+                cb    : cb
         (cb) ->
             dbg("project not deployed, so choose best host based on snapshots")
             get_snapshots
@@ -3419,7 +3440,7 @@
         stop  : undefined
         exclude : undefined    # if given, any project_id in this array is skipped
         retry_errors : false   # also retry to migrate ones that failed with an error last time (normally those are ignored the next time)
-        status: undefined      # if given, should be a map, which will get status for projects as they are running.
+        status: undefined      # if given, should be a list, which will get status for projects push'd as they are running.
         cb    : undefined      # cb(err, {project_id:errors when migrating that project})

     projects = undefined
@@ -3464,7 +3485,8 @@
                 dbg("Starting to migrate #{project_id}: #{i+1}/#{todo}")
                 dbg("*******************************************")
                 if opts.status?
-                    stat = opts.status[project_id] = {status:'migrating...'}
+                    stat = {status:'migrating...', project_id:project_id}
+                    opts.status.push(stat)
                 exports.migrate2
                     project_id : project_id
                     status     : stat


fix disasm output

fix disasm output

--- src/disassembler.coffee
+++ src/disassembler.coffee
@@ -74,8 +74,9 @@
   for f in class_file.fields
     rv += "#{access_string f.access_flags} #{pp_type(f.type)} #{f.name};\n"
     const_attr = _.find(f.attrs, (attr) -> attr.constructor.name == 'ConstantValue')
-    entry = pool.get(const_attr.ref)
-    rv += "  Constant value: #{entry.type} #{entry.deref?() or entry.value}\n" if const_attr?
+    if const_attr?
+      entry = pool.get(const_attr.ref)
+      rv += "  Constant value: #{entry.type} #{entry.deref?() or entry.value}\n"
     rv += "\n\n"

   for m in class_file.methods


add comment

add comment

--- lib/emulator/emulator-view.coffee
+++ lib/emulator/emulator-view.coffee
@@ -1,5 +1,9 @@
 {View} = require 'atom'

+#
+# using webview tag:
+# https://github.com/atom/atom-shell/blob/master/docs/api/web-view-tag.md
+#
 module.exports =
 class EmulatorView extends View
   @content: ->


packageCheck is a little more clear

packageCheck is a little more clear

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -114,13 +114,13 @@
       promises.push UserUtilities.checkPackageUpToDate(packageName) if packageName?

       Promise.all(promises).then (allData) ->
-        [issue, newIssueUrl, latestPackage] = allData
+        [issue, newIssueUrl, packageCheck] = allData

         if issue?
           issueButton.setAttribute('href', issue.html_url)
           issueButton.textContent = "View Issue"
           fatalNotification.textContent += " This issue has already been reported."
-        else if latestPackage? and !latestPackage.upToDate and !latestPackage.isCore
+        else if packageCheck? and !packageCheck.upToDate and !packageCheck.isCore
           issueButton.setAttribute('href', '#')
           issueButton.textContent = "Check for package updates"
           issueButton.addEventListener 'click', (e) ->
@@ -129,8 +129,8 @@
             atom.commands.dispatch(atom.views.getView(atom.workspace), command)

           fatalNotification.textContent += """
-            #{packageName} is out of date: #{latestPackage.installedVersion} installed;
-            #{latestPackage.latestVersion} latest.
+            #{packageName} is out of date: #{packageCheck.installedVersion} installed;
+            #{packageCheck.latestVersion} latest.
             Upgrading to the latest version may fix this issue.
           """
         else


get tooltips for buttons programmatically

--- lib/notifications-log-item.coffee
+++ lib/notifications-log-item.coffee
@@ -52,8 +52,8 @@
         logButton.addEventListener 'click', (e) ->
           newEvent = new MouseEvent('click', e)
           e.target.originalButton.dispatchEvent(newEvent)
-        if button.classList.contains('btn-copy-report')
-          @subscriptions.add atom.tooltips.add(logButton, title: 'Copy error report to clipboard')
+        for tooltip in atom.tooltips.findTooltips button
+          @subscriptions.add atom.tooltips.add(logButton, tooltip.options)
         buttons.appendChild(logButton)

     nElement = document.createElement('div')


refactor implementation of the localization prelude

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -79,21 +79,23 @@
     # certain services that are booted well before all the wiring.
     # A bit of black magic is used here, for a later refactoring.
     prelude: (symbol, context, request, next) ->
-        assert send = context.transit.bind context
         assert inline = context.inline.bind context
+        assert sender = context.transit.bind context
         assert shadow = Object.create this # isolated
+        assert shader = __isolated: true, __origin: @
         try _.extend shadow, session: request.session
         try _.extend shadow, request: request or null
-        _.extend shadow, __isolated: yes, __origin: @
+        try _.extend shadow, shader # shade the context
         translation = @obtainTranslation # get provider
-        setup = @setupTranslationTools # get installer
+        setup = @setupTranslationTools # get installing
         execute = (a...) => translation.apply shadow, a
         spoof = (l, c) -> c @_tmpLanguage, @_tmpMessages
+        assert _.isFunction try trx = sender or undefined
         return execute undefined, (language, messages) =>
             do -> assert context._tmpLanguage = language
             do -> assert context._tmpMessages = messages
-            send spoof, (f) -> this.obtainTranslation = f
-            send setup, (f) -> @setupTranslationTools = f
+            trx spoof, (f) -> this.obtainTranslation = f
+            trx setup, (f) -> @setupTranslationTools = f
             (inline -> @setupTranslationTools()); next()

     # A routine intended for server side execution inside of the


removed multiple instances

removed multiple instances

--- core/app/backbone/factlink/tooltip_definition.coffee
+++ core/app/backbone/factlink/tooltip_definition.coffee
@@ -9,19 +9,16 @@

 Backbone.Factlink ||= {}

-
 class Backbone.Factlink.TooltipDefinition
   defaults = closingtimeout: 500
-  ttCounter = 0 #each TooltipDefinition gets its own id based on this.
-
+  counter = 0

   constructor: (options) ->
     @_options = _.defaults options, defaults

-    @_uid = ttCounter++
-    @_uidStr = 'tooltip_Uid-' + @_uid
-    @_instCounter = 0 #one call can cause multiple tooltips
-    @_instances = {}
+    @_uid =
+    @_uidStr = 'tooltip_Uid-' + counter++
+    @_open_tooltip = null

     @_options.$container.hoverIntent
       timeout: @_options.closingtimeout
@@ -30,27 +27,21 @@
       out: @_hoverTarget false

   close: ->
-    ttActions.remove() for id, ttActions of @_instances
-    @_instances = null #reuse hereafter is a bug: this causes a crash then.
+    @instance?.remove()
+    @_open_tooltip = null #reuse hereafter is a bug: this causes a crash then.
     @_options.$container.off(".hoverIntent") #unfortunately, we can't do better than this.

   _openInstance: ($target) ->
-    console.log @_options, $target
-    instId = 'tt' + @_instCounter++
-    $target.data(@_uidStr, instId)
-    @_instances[instId] =
+    @_open_tooltip =
       new TooltipInstance @_options, $target, ($tooltip) =>
-        delete @_instances[instId]
-        $target.removeData(@_uidStr)
+        delete @_open_tooltip
         @_options.removeTooltip @_options.$container, $target, $tooltip

-  _hoverTarget: (inTarget) ->
-    (e) =>
+  _hoverTarget: (target_is_hovered) -> (e) =>
       $target = $(e.currentTarget)
-      ttActions = @_instances[$target.data(@_uidStr)]
-      if ttActions #this is a known (i.e. open) tooltip
-        ttActions.setTargetHover inTarget
-      else if inTarget #target has no tooltip but is hovered
+      if @_open_tooltip
+        @_open_tooltip.setTargetHover target_is_hovered
+      else if target_is_hovered
         @_openInstance $target

 class TooltipInstance #only local!


Fix # "OK" should be retrieved by hook

Fix # "OK" should be retrieved by hook

--- server/api/hook/hook.controller.coffee
+++ server/api/hook/hook.controller.coffee
@@ -10,7 +10,7 @@

   # TODO Get

-  res.status(200).json 'ヽ(^ᴗ^)丿'
+  res.status(200).json 'OK'
   return


fetchLikedContents for JAccount to fetch liked contents and their teasers for given selector

--- server/app/bongo/models/account.coffee
+++ server/app/bongo/models/account.coffee
@@ -44,7 +44,7 @@
         'fetchFollowingWithRelationship', 'fetchTopics'
         'fetchMounts','fetchActivityTeasers','fetchRepos','fetchDatabases'
         'fetchMail','fetchNotificationsTimeline','fetchActivities'
-        'fetchStorage','count','addTags','fetchLimit'
+        'fetchStorage','count','addTags','fetchLimit', 'fetchLikedContents'
         'fetchFollowedTopics', 'fetchKiteChannelId', 'setEmailPreferences'
         'fetchNonces', 'glanceMessages', 'glanceActivities', 'fetchRole'
         'fetchAllKiteClusters','setKiteConnection','flagAccount','unflagAccount'
@@ -223,6 +223,30 @@
     else
       callback null, "private-#{kiteName}-#{delegate.profile.nickname}"

+  fetchLikedContents: secure ({connection}, options, selector, callback)->
+
+    {delegate} = connection
+    [callback, selector] = [selector, callback] unless callback
+
+    selector            or= {}
+    selector.as           = 'like'
+    selector.targetId     = @getId()
+    selector.sourceName or= $in: ['JCodeSnip', 'JStatusUpdate']
+
+    Relationship.some selector, options, (err, contents)=>
+      if err then callback err, []
+      else if contents.length is 0 then callback null, []
+      else
+        teasers = []
+        collectTeasers = bongo.race (i, root, fin)->
+          root.fetchSource (err, teaser)->
+            if err then callback err
+            else
+              teasers.push(teaser) if teaser
+              fin()
+        , -> callback null, teasers
+        collectTeasers node for node in contents
+
   dummyAdmins = ["sinan", "devrim", "aleksey-m", "gokmen", "chris"]

   flagAccount: secure (client, flag, callback)->


Use `.constructor.prototype` for promisifying instead of `__proto__`.

--- src/docker-utils.coffee
+++ src/docker-utils.coffee
@@ -8,8 +8,8 @@

 docker = Promise.promisifyAll(new Docker(socketPath: config.dockerSocket))
 # Hack dockerode to promisify internal classes' prototypes
-Promise.promisifyAll(docker.getImage().__proto__)
-Promise.promisifyAll(docker.getContainer().__proto__)
+Promise.promisifyAll(docker.getImage().constructor.prototype)
+Promise.promisifyAll(docker.getContainer().constructor.prototype)

 exports.docker = docker


Added 'You're Awesome' memes

Added 'You're Awesome' memes

--- scripts/awesome.team.coffee
+++ scripts/awesome.team.coffee
@@ -1,6 +1,18 @@
 module.exports = (robot) ->

   $baseurl = process.env.AWESOME_API_URL || "http://localhost:"+process.env.PORT
+
+  youre_awesome = [
+    "http://who-is-awesome.com/who-is-awesome.jpg",
+    "http://9buz.com/content/uploads/images/October2014/You_there_Yes_you_Youre_awesome_9buz.jpg",
+    "http://www.desicomments.com/wp-content/uploads/Youre-Awesome-Image.jpg",
+    "https://cdn.meme.am/instances/500x/54941232.jpg",
+    "https://sheerepiphany.files.wordpress.com/2015/04/a.jpg",
+    "http://i.imgur.com/aimsF.jpg"
+  ]
+
+  robot.hear /who's awesome/i, (msg) ->
+    msg.send msg.random youre_awesome

   robot.hear /give me all the teams/i, (msg) ->
     msg.http($baseurl+"/teams")


Use instanceof instead of nodeName for consistency's sake

--- lib/written/app/assets/javascripts/written/core/cursor.coffee
+++ lib/written/app/assets/javascripts/written/core/cursor.coffee
@@ -18,7 +18,7 @@
           @offset += child.toString().length
           child = child.previousSibling

-        if node.nodeName == 'LI'
+        if node instanceof HTMLLIElement
           this.offset += Array.prototype.indexOf.call(parent.children, node)

       node = parent


payment: stop refunding users since recurly does it automatically

--- workers/social/lib/social/models/payment/subscription.coffee
+++ workers/social/lib/social/models/payment/subscription.coffee
@@ -145,14 +145,19 @@
         Refund for the remaining days of your current plan: #{ oldPlan.title }
         """

-      if applyRefund
-        @refund { amount, description }, (err) ->
-          return callback err  if err
-
-          callback null, amount
-      else
-        callback null, 0
-
+      # There is a bug somewhere that's causing us to refund
+      # and charge less when upgrading, temporarily commenting
+      # this out: SA
+      #
+      # if applyRefund
+      #   @refund { amount, description }, (err) ->
+      #     return callback err  if err
+
+      #     callback null, amount
+      # else
+      #   callback null, 0
+
+      callback null, 0

   resume: (callback) ->
     @fetchLinkedSubscription (err, subscription) =>


Send hipchat message for prepaid purchase

--- server/prepaids/prepaid_handler.coffee
+++ server/prepaids/prepaid_handler.coffee
@@ -2,6 +2,7 @@
 Prepaid = require './Prepaid'
 StripeUtils = require '../lib/stripe_utils'
 {getPrepaidCodeAmount} = require '../../app/core/utils'
+hipchat = require '../hipchat'

 # TODO: Should this happen on a save() call instead of a prepaid/-/create post?
 # TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
@@ -116,6 +117,8 @@
                 months: req.body.months
             prepaid.save (err) =>
               return @sendDatabaseError(res, err) if err
+              msg = "Prepaid code (#{req.body.maxRedeemers} users / #{req.body.months} months) purchased by #{req.user.get('email')}"
+              hipchat.sendHipChatMessage msg, ['tower']
               @sendSuccess(res, prepaid.toObject())

 module.exports = new PrepaidHandler()


some are using the name history for changelog

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -112,7 +112,7 @@
       for child in fs.listSync(@pack.path)
         extension = path.extname(child)
         name = path.basename(child, extension)
-        if name.toLowerCase() is 'changelog'
+        if name.toLowerCase() is 'changelog' | 'history'
           atom.workspaceView.open(child)
           break
       false


AceAppView: Fix empty filetree issues on Ace when switching to Ace from another app

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -18,7 +18,6 @@
     @finderWrapper       = new KDCustomHTMLView
       tagName            : 'aside'

-    @embedFinder()
     @attachEvents()
     @attachAppMenuEvents()

@@ -125,7 +124,9 @@

   viewAppended:->
     super
-    @utils.wait 100, => @addNewTab() if @tabView.panes.length is 0
+    @utils.wait 100, =>
+      @embedFinder()
+      @addNewTab() if @tabView.panes.length is 0

   addNewTab: (file) ->
     file = file or FSHelper.createFileFromPath 'localfile:/Untitled.txt'


feature: String#repeat

feature: String#repeat

--- src/galao/string.coffee
+++ src/galao/string.coffee
@@ -3,3 +3,13 @@

 String::startsWith ?= (prefix) ->
   @indexOf(prefix) is 0
+
+# source: http://coffeescript.org/documentation/docs/helpers.html
+String::repeat ?= (n) ->
+  str = this
+  res = ''
+  while n > 0
+    res += str if n & 1
+    n >>>= 1
+    str += str
+  res


Force areas to become square on the way in to the terrain model

--- client/terrain/object.coffee
+++ client/terrain/object.coffee
@@ -41,9 +41,16 @@
     @add(@mesh)

   # Called by the client when it wants to update which area is being watched.
-  # Currently the area _must_ be a _square_ block of UTM33.
+  # Currently the area is forced to become square
   show: (nwPoint, sePoint) ->
-    @streamer.setBounds(new L.Bounds(nwPoint, sePoint))
+    area = new L.Bounds(nwPoint, sePoint)
+    center = area.getCenter()
+    size = area.getSize()
+    halfside = (size.x+size.y)/4
+    @streamer.setBounds(new L.Bounds(
+        [center.x-halfside, center.y-halfside],
+        [center.x+halfside, center.y+halfside]
+      ))

   terrainUpdateHandler: ->
     @textureMaterial.map.needsUpdate = true


update fatal view after fetching issue

update fatal view after fetching issue

--- lib/notifications-log-item.coffee
+++ lib/notifications-log-item.coffee
@@ -10,7 +10,12 @@

   render: ->
     # TODO: should probably create new element instead of cloning
-    atomNotification = atom.views.getView(@notification).element.cloneNode(true)
+    notificationView = atom.views.getView(@notification)
+    notificationElement = @renderNotification(notificationView)
+
+    if @notification.getType() is 'fatal'
+      notificationView.getRenderPromise().then =>
+        @element.replaceChild(@renderNotification(notificationView), notificationElement)

     @timestamp = document.createElement('div')
     @timestamp.classList.add('timestamp')
@@ -19,9 +24,12 @@

     @element = document.createElement('li')
     @element.classList.add('notifications-log-item', @notification.getType())
-    @element.appendChild(atomNotification)
+    @element.appendChild(notificationElement)
     @element.appendChild(@timestamp)
     @element.addEventListener('click', => @emitter.emit 'click')
+
+  renderNotification: (view) ->
+    view.element.cloneNode(true)

   getElement: -> @element


only stub functions

only stub functions

--- shared/test/helpers/fake-window.coffee
+++ shared/test/helpers/fake-window.coffee
@@ -1,5 +1,7 @@
 defer = require 'lodash/defer'
 merge = require 'lodash/merge'
+isFunction = require 'lodash/isFunction'
+

 EmptyFn = ->
   return undefined
@@ -19,14 +21,15 @@
   location:
     pathname: '/'
     search: ''
+
   constructor: (attrs = {}) ->
-    for method in _.functions(@)
-      sinon.spy(@, method)
+    merge(@, attrs)
+    for name, method of @ when isFunction(method)
+      sinon.spy(@, name)
     @localStorage =
       getItem: sinon.stub().returns('[]')
       setItem: sinon.stub()
     @history =
       pushState: sinon.spy()
-    merge(@, attrs)

 module.exports = FakeWindow


feat(editable): added on_save_success and on_save_fail functions for notifications

--- src/lib/editable.coffee
+++ src/lib/editable.coffee
@@ -14,7 +14,7 @@
 DelayedSave = (options, self, datahandler = null) ->
   (value, prop, delay = 250) =>
     if self.id?
-      if self[prop].editing?
+      if self[prop] && self[prop].editing?
         self[prop].editing 'pending'

       window.timeoutEditor = {} unless window.timeoutEditor?
@@ -22,20 +22,25 @@

       clearTimeout(window.timeoutEditor[options.api + self.id][prop])
       window.timeoutEditor[options.api + self.id][prop] = setTimeout(=>
-        if self[prop].editing?
+        if self[prop] && self[prop].editing?
           self[prop].editing 'success'

         edit_value = {}
         edit_value[prop] = value
         if datahandler?
           edit_value[prop] = datahandler.save(value)
-        self.update edit_value
+        res = self.update edit_value
+
+        if self.on_save_success?
+          res.done self.on_save_success
+        if self.on_save_fail?
+          res.fail self.on_save_fail
       , delay)

 HashedSave = (options, self) ->
   (value, prop) =>
     if self.id?
-      if self[prop].editing?
+      if self[prop] && self[prop].editing?
         self[prop].editing 'pending'

       if self.hash_salt?
@@ -45,4 +50,9 @@

       edit_value = {}
       edit_value[prop] = getHash(value)
-      self.update edit_value
+      res = self.update edit_value
+
+      if self.on_save_success?
+        res.done self.on_save_success
+      if self.on_save_fail?
+        res.fail self.on_save_fail