Compare commits

..

38 Commits

Author SHA1 Message Date
Piotr Sarnacki
1cd460e020 Experimental implementation of travis-cmd 2015-11-20 19:09:34 +01:00
Lisa P
59c9fce76c add osx_image to keys-map 2015-11-20 14:47:10 +01:00
Piotr Sarnacki
4a73c17d4c Model#typeKey -> Model#modelName 2015-11-18 17:15:08 +01:00
Piotr Sarnacki
fbe384d5ae Fix key for repo relationship for V3 API 2015-11-18 17:05:32 +01:00
Piotr Sarnacki
e60d16209d Fix loading record by slug
In a repo route we need to find record by slug there is no easy way to
do it with a public finders API, so we need to use adapter and
serializers directly. The problem is that the old way of doing this
didn't use the normalizePayload function and also it didn't add included
records properly. New code properly normalizes response and adds all of
the embedded records that were extracted from the response.
2015-11-18 17:01:59 +01:00
Piotr Sarnacki
bd3b62c264 Fix handling branches
This commit fixes handling of branches when using both V3 and V2. The
changes include:

  * proper definition of relationships that reflect V3 structure, so for
    example build belongs to a branch
  * setting up inverse records for some of the relationships. without
    doing that Ember Data can handle relationships in a surprising way,
    for example if the same record is referenced in 2 places in a
    belongsTo relationship, Ember Data will remove one of the references
    without proper inverse definitions
  * we need to add id when extracting branch as a relationship. Ember
    Data expects all of the relationships to have an id
  * lastly, we need to mimic the structure of the V3 API in V2 payloads,
    so for a build payload I'm now creating a branch record
2015-11-18 17:00:34 +01:00
Piotr Sarnacki
9a9708df31 Fix references in V3 payloads
V3 API doesn't return any of the records more than 2 times. If a record
is already included in the response any other occurences will be
represented as a reference, ie. a hash with just an @href. Ember Data
doesn't play nice with such references as it needs an id to identify a
record.

The code in this commit traverses payloads from V3 API and adds an id to
each of the references that are present.

For example a following payload:

    {
      "@href": "/build/1",
      "@type": "build"
      "id": 1,
      "state": "passed",
      "branch": {
        "@href": "/repo/1/branch/master",
        "name": "master",
        "lastBuild": {
          "@href": "/build/1"
        }
      }
    }

Will be changed to:

    {
      "@href": "/build/1",
      "@type": "build"
      "id": 1,
      "state": "passed",
      "branch": {
        "@href": "/repo/1/branch/master",
        "name": "master",
        "lastBuild": {
          "@href": "/build/1",
          "id": 1
        }
      }
    }

In this case an "id" field was added to "branch.lastBuild" field.
2015-11-18 16:48:21 +01:00
Piotr Sarnacki
042e058f2a Update ember-cli-coffeescript to newest version 2015-11-18 16:47:31 +01:00
Piotr Sarnacki
76c604a39c Update ember-cli to 1.13.12 (ember.js 1.13.10, ember-data 1.13.15) 2015-11-18 12:30:53 +01:00
Piotr Sarnacki
df0f883ad0 app/serializers/repo.coffee -> app/serializers/repo.js 2015-11-18 11:48:59 +01:00
Piotr Sarnacki
4e1976d30c Simplify pusher handling in store 2015-11-17 11:48:29 +01:00
Piotr Sarnacki
9222276f87 Remove console.log 2015-11-12 16:13:45 +01:00
Piotr Sarnacki
2267babac0 Fix pusher to work with new ember-data
This commit just fixes things to the point where pusher updates are
applied to the store properly. This still lacks a business logic fixes,
so for example we won't update lastBuild's field, because there's no
such information from pusher.
2015-11-12 16:02:53 +01:00
Piotr Sarnacki
706ea836e4 Don't run Sauce tests for now 2015-11-12 12:44:04 +01:00
Piotr Sarnacki
b4ad8a54b7 Disable dashboard test for now 2015-11-12 12:25:55 +01:00
Piotr Sarnacki
d1b9bddfe0 Fix sidebar repos list
We need this list to update with pusher, so filtering is better than
using store.query.
2015-11-11 17:38:59 +01:00
Piotr Sarnacki
8956dedeaf Attribute mappings in serializers should use underscore notation 2015-11-11 16:24:23 +01:00
Piotr Sarnacki
c072738d58 Use job_ids as a key for jobs relationship for build 2015-11-11 15:37:55 +01:00
Piotr Sarnacki
0ceb677aed Don't add commit data to included array in build serializer
We already do it in an abstracted way (ie. for all relationships) in v2
fallback serializer.
2015-11-11 15:17:04 +01:00
Piotr Sarnacki
cf1e5d6ad3 No need to add repo to the list of attributes now 2015-11-11 15:15:36 +01:00
Piotr Sarnacki
3f6ad9cf1a Fix handling relationships for V2 API
* we should look for both embedded relationship and relationship key,
  so in cases like for commit, when there's a full commit data on
  "commit" property, and only id at "commit_id", we will use commit data
* we can't add @type to V2 fallback, because in other places we chack
  for @type to distinguish V2 and V3 payloads
* there's no need to include a record in "included" if there's only a
  type and an id there
2015-11-11 15:15:36 +01:00
Piotr Sarnacki
c6bac57047 Fix job route's use of job.build relationship
build is an async relationship now, so job.get('build') returns a
promise.
2015-11-11 14:50:45 +01:00
Piotr Sarnacki
4a62d4ac9c Fix including to-many relationships for V3 payloads 2015-11-11 12:38:44 +01:00
Piotr Sarnacki
5a2c86697b Properly include relationships for V2 payloads 2015-11-11 12:38:22 +01:00
Piotr Sarnacki
f09742abe1 Convert build serializer to javascript 2015-11-11 11:52:29 +01:00
Piotr Sarnacki
b83e70c324 Update serializers to work with jobs endpoint response 2015-11-10 17:54:43 +01:00
Piotr Sarnacki
5c5c433363 Include repositoryId as an attribute on build
For some reason (probably some problem with one of the serializers) we
sometimes lack an id attribute for a promise that we get for a repo
relationship on build. Because of that doing `build.get('repo.id')` may
sometimes return undefined. A temporary workaround is to make sure that
we always can access the `repository_id` property.
2015-11-10 17:52:32 +01:00
Piotr Sarnacki
264d780931 Use V2FallbackSerializer for jobs 2015-11-10 09:58:59 +01:00
Piotr Sarnacki
1efdcf1290 Don't throw error if broadcastArray is undefined 2015-11-10 09:58:37 +01:00
Piotr Sarnacki
3d7833d64b Use storage service instead of Travis.storage 2015-11-09 14:06:42 +01:00
Piotr Sarnacki
18838acd27 Remove unused settings/index controller 2015-11-09 13:48:31 +01:00
Piotr Sarnacki
bc4b666d9e Observe repos.firstObject on reposController
Since we change repos property on reposController, we can't set observer
on repos, because as soon as it's changed, we loose the observer.
Instead, we should observe only on reposController, which is not going
to change.
2015-11-09 13:48:25 +01:00
Piotr Sarnacki
310627cc55 Sort repos in repos-list component, not in controller 2015-11-09 13:46:55 +01:00
Piotr Sarnacki
2a2b3f018b Use lastBuild from defaultBranch on repository
One thing that is not standard here is a serializer for branch, which
uses @href as id. At this point branches don't have ids and ember-data
needs one, so using @href is the easiest way.
2015-11-09 13:46:54 +01:00
Piotr Sarnacki
6992a79a93 Create adapters and serializers working with v3 and v2 APIs
This commit adds adapters and serializers for v3, but also a fallback
serializer for v2, which allows to handle v2 and v3 payloads at the same
time. This is needed, because when we use v3 endpoint for one of the
models (in this case repo), we can also get embedded records of other
types (like branch or build).
2015-11-09 13:46:54 +01:00
Piotr Sarnacki
0976672f9e Remove unneeded test 2015-11-09 13:46:54 +01:00
Piotr Sarnacki
22d4f59ce9 Move ajax and auth into services 2015-11-09 13:46:54 +01:00
Piotr Sarnacki
b9de261388 Remove some more deprecations 2015-11-09 13:43:56 +01:00
1208 changed files with 24722 additions and 32496 deletions

View File

@ -1,19 +1,9 @@
{ {
"predef": [ "predef": [
"server",
"document", "document",
"window", "window",
"-Promise", "-Promise",
"jQuery", "jQuery"
"Visibility",
"$",
"Travis",
"_cio",
"_gaq",
"Log",
"moment",
"Pusher",
"md5"
], ],
"browser": true, "browser": true,
"boss": true, "boss": true,

View File

@ -1,6 +1,6 @@
--- ---
language: node_js language: node_js
node_js: stable node_js: "0.10.36"
env: env:
- EMBER_VERSION=default - EMBER_VERSION=default
@ -16,8 +16,8 @@ matrix:
fast_finish: true fast_finish: true
addons: #addons:
sauce_connect: true # sauce_connect: true
sudo: false sudo: false
@ -39,6 +39,9 @@ install:
- npm install - npm install
- bower install - bower install
before_script:
- ruby ci/prepare_testem.rb
script: script:
- ember try $EMBER_VERSION - ember try $EMBER_VERSION

View File

@ -65,6 +65,3 @@ DEPENDENCIES
sinatra sinatra
sinatra-contrib sinatra-contrib
travis-web! travis-web!
BUNDLED WITH
1.10.1

View File

@ -16,39 +16,6 @@ Now you can run the server:
And open http://localhost:4200 in the browser. And open http://localhost:4200 in the browser.
Alternatively you can run `ember build --watch` and start the server with `waiter/script/server`
### Running the app in private repos mode
At the moment Travis CI is available as two separate sites - https://travis-ci.org for Open Source
projects and https://travis-ci.com for private projects. travis-web will connect
to the Open Source version by default. In order to connect it to the API for private projects
you need to run:
```
TRAVIS_PRO=true ember serve --ssl --ssl-key=ssl/server.key --ssl-cert=ssl/server.crt
```
One caveat here is that the command will start server with SSL, so the page will
be accessible at https://localhost:4200 (note `https` part).
### Running on SSL in general
Sometimes there is a need to test the app with an SSL connection. This is required
to make Pusher work when running Travis CI Pro, but it may also be needed in other
situations.
There's already an SSL certificate in the `ssl` directory, which is set for `localhost`
host. If you want to use it, you can start the server with:
```
ember serve --ssl --ssl-key=ssl/server.key --ssl-cert=ssl/server.crt
```
In case you want your own certificate, you can follow the instructions posted
here: https://gist.github.com/trcarden/3295935 and then point the server to your
certificate with `--ssl-key` and `--ssl-cert`.
### Running tests ### Running tests
To run a test suite execute: To run a test suite execute:
@ -58,21 +25,3 @@ To run a test suite execute:
You can also start an interactive test runner for easier development: You can also start an interactive test runner for easier development:
ember test --serve ember test --serve
### Updating the team page
The team information can be found in `app/routes/team.js`.
To add another member just add the info in the same style as the previous ones. Like so
{
name: 'Mr T'
title: 'Mascot'
handle: 'travisci'
nationality: 'internet'
country: 'internet'
image: 'mrt'
}
The order of value pairs does not matter, the quotationmarks do. Name and title will be displayed as they are. The handle will be used to generate a link to Twitter and displayed with a '@' in front of it. Nationality and country determine the flags. Please use the name of the country and not the adjective (like 'germany' and NOT 'german'). Image is the identifier to find the right image and animated gif. 'mrt' in the example will result in `team-mrt.png` and `mrt-animated.gif`.
Add the images themselves to `public/images/team/` and additional flags to `public/images/pro-landing/`. Mind the naming conventions already in place.

14
SSL_LOCALLY.md Normal file
View File

@ -0,0 +1,14 @@
Sometimes there is a need to test the app with an SSL connection, for example for
testing pusher on Travis CI Pro.
In order to run the app with SSL enabled you need to:
* generate self signed certificate as described here: https://gist.github.com/trcarden/3295935
* one difference is that you need to use localhost.ssl, because travis-api
doesn't whitelist localhost.ssl at the moment
* run ruby server with `SSL=true TRAVIS_PRO=true waiter/script/server`
* run Ember app in build mode only `TRAVIS_PRO=true ember build --watch`
Now open the app on whatever port ruby server is running (3001 by default). The
ruby app will serve files generated by `ember build` and it will allow SSL
connections

View File

@ -0,0 +1,25 @@
`import DS from 'ember-data'`
`import config from 'travis/config/environment'`
Adapter = DS.ActiveModelAdapter.extend
auth: Ember.inject.service()
host: config.apiEndpoint
coalesceFindRequests: true
ajaxOptions: (url, type, options) ->
hash = @_super(url, type, options)
hash.headers ||= {}
hash.headers['accept'] = 'application/json; version=2'
if token = @get('auth').token()
hash.headers['Authorization'] ||= "token #{token}"
hash
findMany: (store, type, ids) ->
@ajax(@buildURL(type.modelName), 'GET', data: { ids: ids })
`export default Adapter`

View File

@ -1,57 +0,0 @@
import config from 'travis/config/environment';
import Ember from 'ember';
import ActiveModelAdapter from 'active-model-adapter';
export default ActiveModelAdapter.extend({
auth: Ember.inject.service(),
host: config.apiEndpoint,
coalesceFindRequests: true,
// Before Ember Data 2.0 the default behaviour of running `findAll` was to get
// new records only when there're no records in the store. This will change
// to a different strategy in 2.0: when you run `findAll` it will not get any
// new data initially, but it will try loading new data in the background.
//
// I'm disabling the new behaviour for now.
shouldBackgroundReloadRecord() {
return false;
},
ajaxOptions(url, type, options) {
var base, hash, token;
hash = this._super(...arguments);
hash.headers = hash.headers || {};
hash.headers['accept'] = 'application/json; version=2';
if (token = this.get('auth').token()) {
if(!hash.headers['Authorization']) {
hash.headers['Authorization'] = "token " + token;
}
}
return hash;
},
findMany(store, type, ids) {
return this.ajax(this.buildURL(type.modelName), 'GET', {
data: {
ids: ids
}
});
},
handleResponse(status, headers, payload) {
if (status > 299) {
console.log("[ERROR] API responded with an error (" + status + "): " + (JSON.stringify(payload)));
}
return this._super(...arguments);
},
// this can be removed once this PR is merged and live:
// https://github.com/emberjs/data/pull/4204
findRecord(store, type, id, snapshot) {
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET');
}
});

View File

@ -0,0 +1,26 @@
`import Ember from 'ember'`
`import ApplicationAdapter from 'travis/adapters/application'`
Adapter = ApplicationAdapter.extend
namespace: 'settings'
buildURL: (type, id, record) ->
url = @_super.apply this, arguments
if record && (repoId = Ember.get(record, 'repo.id'))
delimiter = if url.indexOf('?') != -1 then '&' else '?'
url = "#{url}#{delimiter}repository_id=#{repoId}"
url
updateRecord: (store, type, record) ->
data = {};
serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record);
id = Ember.get(record, 'id');
this.ajax(this.buildURL(type.typeKey, id, record), "PATCH", { data: data })
`export default Adapter`

View File

@ -1,27 +0,0 @@
import Ember from 'ember';
import ApplicationAdapter from 'travis/adapters/application';
export default ApplicationAdapter.extend({
namespace: 'settings',
buildURL(type, id, record) {
var delimiter, repoId, url;
url = this._super.apply(this, arguments);
if (record && record.belongsTo('repo') && (repoId = record.belongsTo('repo').id)) {
delimiter = url.indexOf('?') !== -1 ? '&' : '?';
url = "" + url + delimiter + "repository_id=" + repoId;
}
return url;
},
updateRecord(store, type, record) {
var data, serializer;
data = {};
serializer = store.serializerFor(type.modelName);
serializer.serializeIntoHash(data, type, record);
var id = record.id;
return this.ajax(this.buildURL(type.modelName, id, record), "PATCH", {
data: data
});
}
});

View File

@ -1,9 +0,0 @@
import ApplicationAdapter from 'travis/adapters/application';
export default ApplicationAdapter.extend({
updateRecord(store, type, snapshot) {
return this._super(...arguments).then( (data) => {
return { hook: { id: snapshot.id } };
});
}
});

View File

@ -1,11 +1,11 @@
import V3Adapter from 'travis/adapters/v3'; import V3Adapter from 'travis/adapters/v3';
import ApplicationAdapter from 'travis/adapters/application';
import Config from 'travis/config/environment';
let Adapter = Config.useV3API ? V3Adapter : ApplicationAdapter; export default V3Adapter.extend({
buildUrl(modelName, id, snapshot, requestType, query) {
var url = this._super(...arguments);
export default Adapter.extend({ return url;
defaultSerializer: '-repo', },
ajaxOptions(url, type, options) { ajaxOptions(url, type, options) {
var hash = options || {}; var hash = options || {};
@ -13,12 +13,10 @@ export default Adapter.extend({
hash.data = {}; hash.data = {};
} }
if(Config.useV3API) { if(hash.data.include) {
if(hash.data.include) { hash.data.include += ',repository.default_branch,branch.last_build,build.commit';
hash.data.include += ',repository.default_branch,branch.last_build,build.commit'; } else {
} else { hash.data.include = 'repository.default_branch,branch.last_build,build.commit';
hash.data.include = 'repository.default_branch,branch.last_build,build.commit';
}
} }
return this._super(url, type, hash); return this._super(url, type, hash);

View File

@ -0,0 +1,24 @@
`import Ember from 'ember'`
`import ApplicationAdapter from 'travis/adapters/application'`
Adapter = ApplicationAdapter.extend
namespace: 'settings'
find: (store, type, id, record) ->
@ajax(this.urlPrefix() + '/ssh_key/' + id, 'GET')
deleteRecord: (store, type, record) ->
id = Ember.get(record, 'id')
@ajax(this.urlPrefix() + '/ssh_key/' + id, "DELETE");
createRecord: (store, type, record) ->
data = {};
serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: true });
id = Ember.get(record, 'id')
this.ajax(this.urlPrefix() + '/ssh_key/' + id, "PATCH", { data: data })
`export default Adapter`

View File

@ -1,29 +0,0 @@
import Ember from 'ember';
import ApplicationAdapter from 'travis/adapters/application';
export default ApplicationAdapter.extend({
namespace: 'settings',
findRecord(store, type, id, record) {
return this.ajax(this.urlPrefix() + '/ssh_key/' + id, 'GET');
},
deleteRecord(store, type, record) {
var id = record.id;
return this.ajax(this.urlPrefix() + '/ssh_key/' + id, "DELETE");
},
createRecord(store, type, record) {
var data, serializer;
data = {};
serializer = store.serializerFor(type.modelName);
serializer.serializeIntoHash(data, type, record, {
includeId: true
});
var id = record.id;
return this.ajax(this.urlPrefix() + '/ssh_key/' + id, "PATCH", {
data: data
});
}
});

View File

@ -1,11 +1,13 @@
import Ember from 'ember'; import Ember from 'ember';
import DS from 'ember-data';
import config from 'travis/config/environment'; import config from 'travis/config/environment';
import RESTAdapter from 'ember-data/adapters/rest';
export default RESTAdapter.extend({ export default DS.RESTAdapter.extend({
auth: Ember.inject.service(), auth: Ember.inject.service(),
host: config.apiEndpoint, host: config.apiEndpoint,
defaultSerializer: '-repo',
sortQueryParams: false, sortQueryParams: false,
coalesceFindRequests: false, coalesceFindRequests: false,
headers: { headers: {
@ -54,11 +56,5 @@ export default RESTAdapter.extend({
pathForType: function(modelName, id) { pathForType: function(modelName, id) {
var underscored = Ember.String.underscore(modelName); var underscored = Ember.String.underscore(modelName);
return id ? underscored : Ember.String.pluralize(underscored); return id ? underscored : Ember.String.pluralize(underscored);
},
// this can be removed once this PR is merged and live:
// https://github.com/emberjs/data/pull/4204
findRecord(store, type, id, snapshot) {
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET');
} }
}); });

107
app/app.coffee Normal file
View File

@ -0,0 +1,107 @@
`import Ember from 'ember'`
`import Resolver from 'ember/resolver'`
`import loadInitializers from 'ember/load-initializers'`
`import config from './config/environment'`
Ember.MODEL_FACTORY_INJECTIONS = true
App = Ember.Application.extend(Ember.Evented,
LOG_TRANSITIONS: true
LOG_TRANSITIONS_INTERNAL: true
LOG_ACTIVE_GENERATION: true
LOG_MODULE_RESOLVER: true
LOG_VIEW_LOOKUPS: true
#LOG_RESOLVER: true
modulePrefix: config.modulePrefix
podModulePrefix: config.podModulePrefix
Resolver: Resolver
lookup: ->
@__container__.lookup.apply @__container__, arguments
flash: (options) ->
Travis.lookup('controller:flash').loadFlashes([options])
toggleSidebar: ->
$('body').toggleClass('maximized')
# TODO gotta force redraws here :/
element = $('<span></span>')
$('#top .profile').append(element)
Em.run.later (-> element.remove()), 10
element = $('<span></span>')
$('#repo').append(element)
Em.run.later (-> element.remove()), 10
ready: ->
location.href = location.href.replace('#!/', '') if location.hash.slice(0, 2) == '#!'
@on 'user:signed_in', (user) ->
Travis.onUserUpdate(user)
@on 'user:refreshed', (user) ->
Travis.onUserUpdate(user)
@on 'user:synced', (user) ->
Travis.onUserUpdate(user)
@on 'user:signed_out', () ->
if config.userlike
Travis.removeUserlike()
currentDate: ->
new Date()
onUserUpdate: (user) ->
if config.pro
@identifyCustomer(user)
if config.userlike
@setupUserlike(user)
@subscribePusher(user)
subscribePusher: (user) ->
return unless user.channels
channels = user.channels
if config.pro
channels = channels.map (channel) ->
if channel.match /^private-/
channel
else
"private-#{channel}"
Travis.pusher.subscribeAll(channels)
setupUserlike: (user) ->
btn = document.getElementById('userlikeCustomTab')
btn.classList.add("logged-in")
userlikeData = window.userlikeData = {}
userlikeData.user = {}
userlikeData.user.name= user.login;
userlikeData.user.email = user.email;
unless document.getElementById('userlike-script')
s = document.createElement('script')
s.id = 'userlike-script'
s.src = '//userlike-cdn-widgets.s3-eu-west-1.amazonaws.com/0327dbb23382ccbbb91b445b76e8a91d4b37d90ef9f2faf84e11177847ff7bb9.js'
document.body.appendChild(s)
removeUserlike: () ->
btn = document.getElementById('userlikeCustomTab')
btn.classList.remove("logged-in")
identifyCustomer: (user) ->
if _cio && _cio.identify
_cio.identify
id: user.id
email: user.email
name: user.name
created_at: (Date.parse(user.created_at) / 1000) || null
login: user.login
)
loadInitializers(App, config.modulePrefix)
`export default App`

View File

@ -1,130 +0,0 @@
import Ember from 'ember';
import Resolver from './resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
Ember.MODEL_FACTORY_INJECTIONS = true;
Ember.LinkComponent.reopen({
attributeBindings: ['alt']
});
var App = Ember.Application.extend(Ember.Evented, {
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true,
LOG_ACTIVE_GENERATION: true,
LOG_MODULE_RESOLVER: true,
LOG_VIEW_LOOKUPS: true,
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver: Resolver,
flash(options) {
return Ember.getOwner(Travis).lookup('controller:flash').loadFlashes([options]);
},
toggleSidebar() {
var element;
$('body').toggleClass('maximized');
element = $('<span></span>');
$('#top .profile').append(element);
Ember.run.later((function() {
return element.remove();
}), 10);
element = $('<span></span>');
$('#repo').append(element);
return Ember.run.later((function() {
return element.remove();
}), 10);
},
ready() {
if (location.hash.slice(0, 2) === '#!') {
location.href = location.href.replace('#!/', '');
}
this.on('user:signed_in', function(user) {
return Travis.onUserUpdate(user);
});
this.on('user:refreshed', function(user) {
return Travis.onUserUpdate(user);
});
this.on('user:synced', function(user) {
return Travis.onUserUpdate(user);
});
return this.on('user:signed_out', function() {
if (config.userlike) {
return Travis.removeUserlike();
}
});
},
currentDate() {
return new Date();
},
onUserUpdate(user) {
if (config.pro) {
this.identifyCustomer(user);
}
if (config.userlike) {
this.setupUserlike(user);
}
return this.subscribePusher(user);
},
subscribePusher(user) {
var channels;
if (!user.channels) {
return;
}
channels = user.channels;
if (config.pro) {
channels = channels.map(function(channel) {
if (channel.match(/^private-/)) {
return channel;
} else {
return "private-" + channel;
}
});
}
return Travis.pusher.subscribeAll(channels);
},
setupUserlike(user) {
var btn, s, userlikeData;
btn = document.getElementById('userlikeCustomTab');
btn.classList.add("logged-in");
userlikeData = window.userlikeData = {};
userlikeData.user = {};
userlikeData.user.name = user.login;
userlikeData.user.email = user.email;
if (!document.getElementById('userlike-script')) {
s = document.createElement('script');
s.id = 'userlike-script';
s.src = '//userlike-cdn-widgets.s3-eu-west-1.amazonaws.com/0327dbb23382ccbbb91b445b76e8a91d4b37d90ef9f2faf84e11177847ff7bb9.js';
return document.body.appendChild(s);
}
},
removeUserlike() {
var btn;
btn = document.getElementById('userlikeCustomTab');
return btn.classList.remove("logged-in");
},
identifyCustomer(user) {
if (_cio && _cio.identify) {
return _cio.identify({
id: user.id,
email: user.email,
name: user.name,
created_at: (Date.parse(user.created_at) / 1000) || null,
login: user.login
});
}
}
});
loadInitializers(App, config.modulePrefix);
export default App;

View File

@ -0,0 +1,46 @@
`import Ember from 'ember'`
AddEnvVarComponent = Ember.Component.extend
classNames: ['form--envvar']
classNameBindings: ['nameIsBlank:form-error']
store: Ember.inject.service()
isValid: () ->
if Ember.isBlank(@get('name'))
this.set('nameIsBlank', true)
false
else
true
reset: ->
@setProperties(name: null, value: null, public: null)
actions:
save: ->
return if @get('isSaving')
@set('isSaving', true)
if @isValid()
env_var = @get('store').createRecord('env_var',
name: @get('name')
value: @get('value')
public: @get('public')
repo: @get('repo')
)
self = this
env_var.save().then =>
@set('isSaving', false)
@reset()
, =>
@set('isSaving', false)
else
@set('isSaving', false)
nameChanged: ->
this.set('nameIsBlank', false)
`export default AddEnvVarComponent`

View File

@ -1,55 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['form--envvar'],
classNameBindings: ['nameIsBlank:form-error'],
store: Ember.inject.service(),
isValid() {
if (Ember.isBlank(this.get('name'))) {
this.set('nameIsBlank', true);
return false;
} else {
return true;
}
},
reset() {
return this.setProperties({
name: null,
value: null,
"public": null
});
},
actions: {
save() {
var env_var, self;
if (this.get('isSaving')) {
return;
}
this.set('isSaving', true);
if (this.isValid()) {
env_var = this.get('store').createRecord('env_var', {
name: this.get('name'),
value: this.get('value'),
"public": this.get('public'),
repo: this.get('repo')
});
self = this;
return env_var.save().then(() => {
this.set('isSaving', false);
return this.reset();
}, () => {
return this.set('isSaving', false);
});
} else {
return this.set('isSaving', false);
}
},
nameChanged() {
return this.set('nameIsBlank', false);
}
}
});

View File

@ -0,0 +1,73 @@
# `import Ember from 'ember'`
AddSshKeyComponent = Ember.Component.extend
classNames: ['form--sshkey']
classNameBindings: ['valueError:form-error']
store: Ember.inject.service()
isSaving: false
didInsertElement: () ->
id = @get('repo.id')
model = @get('store').recordForId('ssh_key', id)
# TODO: this can be removed in favor of simply unloading record
# once https://github.com/emberjs/data/pull/2867
# and https://github.com/emberjs/data/pull/2870 are merged
if model
@get('store').dematerializeRecord(model._internalModel)
typeMap = @get('store').typeMapFor(model.constructor)
idToRecord = typeMap.idToRecord
delete idToRecord[id]
model = @get('store').createRecord('ssh_key', id: id)
@set('model', model)
isValid: () ->
if Ember.isBlank(@get('value'))
this.set('valueError', 'Value can\'t be blank.')
false
else
true
reset: ->
@setProperties(description: null, value: null)
valueChanged: (->
this.set('valueError', false)
).observes('value')
addErrorsFromResponse: (errArr) ->
error = errArr[0].detail
if error.code == 'not_a_private_key'
this.set('valueError', 'This key is not a private key.')
else if error.code == 'key_with_a_passphrase'
this.set('valueError', 'The key can\'t have a passphrase.')
actions:
save: ->
this.set('valueError', false)
return if @get('isSaving')
@set('isSaving', true)
if @isValid()
ssh_key = @get('model').setProperties(
description: @get('description')
value: @get('value')
)
ssh_key.save().then =>
@set('isSaving', false)
@reset()
@sendAction('sshKeyAdded', ssh_key)
, (error) =>
@set('isSaving', false)
if error.errors
@addErrorsFromResponse(error.errors)
else
@set('isSaving', false)
`export default AddSshKeyComponent`

View File

@ -1,85 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['form--sshkey'],
classNameBindings: ['valueError:form-error'],
store: Ember.inject.service(),
isSaving: false,
didInsertElement() {
var id = this.get('repo.id');
var model = this.get('store').recordForId('ssh_key', id);
if (model) {
this.get('store').unloadRecord(model);
var typeMap = this.get('store').typeMapFor(model.constructor);
var idToRecord = typeMap.idToRecord;
delete idToRecord[id];
}
model = this.get('store').createRecord('ssh_key', { id: id });
return this.set('model', model);
},
isValid() {
if (Ember.isBlank(this.get('value'))) {
this.set('valueError', 'Value can\'t be blank.');
return false;
} else {
return true;
}
},
reset() {
return this.setProperties({
description: null,
value: null
});
},
valueChanged: function() {
return this.set('valueError', false);
}.observes('value'),
addErrorsFromResponse(errArr) {
var error = errArr[0].detail;
if (error.code === 'not_a_private_key') {
return this.set('valueError', 'This key is not a private key.');
} else if (error.code === 'key_with_a_passphrase') {
return this.set('valueError', 'The key can\'t have a passphrase.');
}
},
actions: {
save() {
var ssh_key;
this.set('valueError', false);
if (this.get('isSaving')) {
return;
}
this.set('isSaving', true);
if (this.isValid()) {
ssh_key = this.get('model');
ssh_key.setProperties({
description: this.get('description'),
value: this.get('value')
});
return ssh_key.save().then(() => {
this.set('isSaving', false);
this.reset();
return this.sendAction('sshKeyAdded', ssh_key);
}, (error) => {
this.set('isSaving', false);
if (error.errors) {
return this.addErrorsFromResponse(error.errors);
}
});
} else {
return this.set('isSaving', false);
}
}
}
});

View File

@ -0,0 +1,91 @@
`import Ember from 'ember'`
`import { gravatarImage } from 'travis/utils/urls'`
`import { githubCommit as githubCommitUrl } from 'travis/utils/urls'`
`import TravisRoute from 'travis/routes/basic'`
`import config from 'travis/config/environment'`
BranchRowComponent = Ember.Component.extend
routing: Ember.inject.service('-routing')
tagName: 'li'
classNameBindings: ['build.last_build.state']
classNames: ['branch-row', 'row-li']
isLoading: false
isTriggering: false
hasTriggered: false
urlGithubCommit: (->
githubCommitUrl(@get('build.repository.slug'), @get('build.last_build.commit.sha'))
).property('build.last_build')
getLast5Builds: (->
lastBuilds = Ember.ArrayProxy.create(
content: [{}, {}, {}, {}, {}]
isLoading: true,
count: 0
)
if !@get('build.last_build')
lastBuilds.set('isLoading', false)
else
apiEndpoint = config.apiEndpoint
repoId = @get('build.repository.id')
branchName = @get('build.name')
options = {}
if @get('auth.signedIn')
options.headers = { Authorization: "token #{@auth.token()}" }
$.ajax("#{apiEndpoint}/v3/repo/#{repoId}/builds?branch.name=#{branchName}&limit=5&build.event_type=push,api", options).then (response) ->
array = response.builds.map( (build) ->
Ember.Object.create(build)
)
if array.length < 5
for i in [1..5 - array.length] by 1
array.push({})
lastBuilds.set('count', response['@pagination'].count)
lastBuilds.set('content', array)
lastBuilds.set('isLoading', false)
lastBuilds
).property()
canTrigger: (->
if !@get('auth.signedIn')
false
else
permissions = @get('auth.currentUser.permissions')
if permissions.contains parseInt(@get('build.repository.id'))
true
else
false
).property()
triggerBuild: (->
apiEndpoint = config.apiEndpoint
repoId = @get('build.repository.id')
options = {
type: 'POST',
body: {
request: {
branch: @get('build.name')
}
}
}
if @get('auth.signedIn')
options.headers = { Authorization: "token #{@auth.token()}" }
$.ajax("#{apiEndpoint}/v3/repo/#{repoId}/requests", options).then (response) =>
@set('isTriggering', false)
@set('hasTriggered', true)
)
actions:
tiggerBuild: (branch) ->
@set('isTriggering', true)
@triggerBuild()
viewAllBuilds: (branch) ->
@get('routing').transitionTo('builds')
`export default BranchRowComponent`

View File

@ -1,103 +0,0 @@
import Ember from 'ember';
import { githubCommit as githubCommitUrl } from 'travis/utils/urls';
import TravisRoute from 'travis/routes/basic';
import config from 'travis/config/environment';
export default Ember.Component.extend({
routing: Ember.inject.service('-routing'),
tagName: 'li',
classNameBindings: ['build.last_build.state'],
classNames: ['branch-row', 'row-li'],
isLoading: false,
isTriggering: false,
hasTriggered: false,
urlGithubCommit: function() {
return githubCommitUrl(this.get('build.repository.slug'), this.get('build.last_build.commit.sha'));
}.property('build.last_build'),
getLast5Builds: function() {
var apiEndpoint, branchName, lastBuilds, options, repoId;
lastBuilds = Ember.ArrayProxy.create({
content: [{}, {}, {}, {}, {}],
isLoading: true,
count: 0
});
if (!this.get('build.last_build')) {
lastBuilds.set('isLoading', false);
} else {
apiEndpoint = config.apiEndpoint;
repoId = this.get('build.repository.id');
branchName = this.get('build.name');
options = {};
if (this.get('auth.signedIn')) {
options.headers = {
Authorization: "token " + (this.auth.token())
};
}
$.ajax(apiEndpoint + "/v3/repo/" + repoId + "/builds?branch.name=" + branchName + "&limit=5&build.event_type=push,api", options).then(function(response) {
var array, i, j, ref;
array = response.builds.map(function(build) {
return Ember.Object.create(build);
});
if (array.length < 5) {
for (i = j = 1, ref = 5 - array.length; j <= ref; i = j += 1) {
array.push({});
}
}
lastBuilds.set('count', response['@pagination'].count);
lastBuilds.set('content', array);
return lastBuilds.set('isLoading', false);
});
}
return lastBuilds;
}.property(),
canTrigger: function() {
var permissions;
if (!this.get('auth.signedIn')) {
return false;
} else {
permissions = this.get('auth.currentUser.permissions');
if (permissions.contains(parseInt(this.get('build.repository.id')))) {
return true;
} else {
return false;
}
}
}.property(),
triggerBuild: function() {
var apiEndpoint, options, repoId;
apiEndpoint = config.apiEndpoint;
repoId = this.get('build.repository.id');
options = {
type: 'POST',
body: {
request: {
branch: this.get('build.name')
}
}
};
if (this.get('auth.signedIn')) {
options.headers = {
Authorization: "token " + (this.auth.token())
};
}
return $.ajax(apiEndpoint + "/v3/repo/" + repoId + "/requests", options).then(() => {
this.set('isTriggering', false);
return this.set('hasTriggered', true);
});
},
actions: {
tiggerBuild(branch) {
this.set('isTriggering', true);
return this.triggerBuild();
},
viewAllBuilds(branch) {
return this.get('routing').transitionTo('builds');
}
}
});

View File

@ -0,0 +1,24 @@
`import Ember from 'ember'`
BroadcastTowerComponent = Ember.Component.extend
classNames: ['broadcast']
isOpen: false
timeoutId: ''
actions:
toggleBroadcasts:() ->
@toggleProperty('isOpen')
@sendAction('toggleBroadcasts')
if @get('isOpen') == true
@set('timeoutId', setTimeout =>
@toggleProperty('isOpen')
@sendAction('toggleBroadcasts')
, 10000
)
else
clearTimeout(@get('timeoutId'))
`export default BroadcastTowerComponent`

View File

@ -1,21 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['broadcast'],
isOpen: false,
timeoutId: '',
actions: {
toggleBroadcasts() {
this.toggleProperty('isOpen');
this.sendAction('toggleBroadcasts');
if (this.get('isOpen') === true) {
return this.set('timeoutId', setTimeout(() => {
this.toggleProperty('isOpen');
return this.sendAction('toggleBroadcasts');
}, 10000));
} else {
return clearTimeout(this.get('timeoutId'));
}
}
}
});

View File

@ -1,27 +0,0 @@
import Ember from 'ember';
import { gravatarImage } from 'travis/utils/urls';
import GithubUrlProperties from 'travis/mixins/github-url-properties';
import { durationFrom, safe } from 'travis/utils/helpers';
import { githubCommit } from 'travis/utils/urls';
export default Ember.Component.extend({
tagName: 'section',
classNames: ['build-header'],
classNameBindings: ['item.state'],
isJob: function() {
if (this.get('item.build')) {
return true;
} else {
return false;
}
}.property('item'),
urlGithubCommit: function() {
return githubCommit(this.get('repo.slug'), this.get('commit.sha'));
}.property('item'),
elapsedTime: function() {
return durationFrom(this.get('item.startedAt'), this.get('item.finishedAt'));
}.property('item.startedAt', 'item.finishedAt', 'item.duration')
});

View File

@ -0,0 +1,9 @@
`import Ember from 'ember'`
`import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin'`
BuildRepoActionsComponent = Ember.Component.extend(RepoActionsItemComponentMixin,
item: Ember.computed.alias('build')
type: 'build'
)
`export default BuildRepoActionsComponent`

View File

@ -1,7 +0,0 @@
import Ember from 'ember';
import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin';
export default Ember.Component.extend(RepoActionsItemComponentMixin, {
item: Ember.computed.alias('build'),
type: 'build'
});

View File

@ -0,0 +1,14 @@
`import Ember from 'ember'`
BuildTileComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['build.state']
attributeBindings: ['title'],
title: (->
num = @get('build.number')
"##{num}"
).property('build')
`export default BuildTileComponent`

View File

@ -1,14 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['build.state'],
attributeBindings: ['title'],
title: function() {
var num, state;
num = this.get('build.number');
state = this.get('build.state');
return "Build #" + num + " " + state;
}.property('build')
});

View File

@ -1,12 +0,0 @@
import Ember from 'ember';
import { colorForState } from 'travis/utils/helpers';
import Polling from 'travis/mixins/polling';
export default Ember.Component.extend({
classNameBindings: ['color'],
pollModels: 'build',
color: function() {
return colorForState(this.get('build.state'));
}.property('build.state')
});

View File

@ -0,0 +1,17 @@
`import Ember from 'ember'`
`import { gravatarImage } from 'travis/utils/urls'`
`import { githubCommit as githubCommitUrl } from 'travis/utils/urls'`
BuildsItemComponent = Ember.Component.extend
classNameBindings: ['build.state']
classNames: ['tile', 'tile--small', 'tile--build', 'row']
urlAuthorGravatarImage: (->
gravatarImage(@get('build.commit.authorEmail'), 40)
).property('build.commit.authorEmail')
urlGithubCommit: (->
githubCommitUrl(@get('build.repo.slug'), @get('build.commit.sha'))
).property('build.commit.sha')
`export default BuildsItemComponent`

View File

@ -1,12 +0,0 @@
import Ember from 'ember';
import { githubCommit as githubCommitUrl } from 'travis/utils/urls';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['build.state'],
classNames: ['row-li', 'pr-row'],
urlGithubCommit: function() {
return githubCommitUrl(this.get('build.repo.slug'), this.get('build.commit.sha'));
}.property('build.commit.sha')
});

View File

@ -1,30 +0,0 @@
import Ember from 'ember';
import Polling from 'travis/mixins/polling';
export default Ember.Component.extend({
store: Ember.inject.service('store'),
pollHook: function(store) {
var contentType, repositoryId;
contentType = this.get('contentType');
repositoryId = this.get('repo.id');
store = this.get('store');
if (contentType === 'builds') {
return store.query('build', {
event_type: 'push',
repository_id: repositoryId
});
} else if (contentType === 'pull_requests') {
return store.filter('build', {
event_type: 'pull_request',
repository_id: repositoryId
});
} else {
return store.query('build', {
repository_id: repositoryId,
branches: true
});
}
}
});

View File

@ -0,0 +1,27 @@
`import Ember from 'ember'`
CachesItemComponent = Ember.Component.extend
ajax: Ember.inject.service()
tagName: 'li'
classNames: ['tile', 'tile--xs', 'row']
classNameBindings: ['cache.type']
isDeleting: false
actions:
delete: ->
return if @get('isDeleting')
if confirm('Are you sure?')
@set('isDeleting', true)
data = { branch: @get('cache.branch') }
deletingDone = => @set('isDeleting', false)
repo = @get('repo')
@get('ajax').ajax("/repos/#{repo.get('id')}/caches", "DELETE", data: data).then(deletingDone, deletingDone).then =>
@get('caches').removeObject(@get('cache'))
`export default CachesItemComponent`

View File

@ -1,33 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
ajax: Ember.inject.service(),
tagName: 'li',
classNames: ['cache-item'],
classNameBindings: ['cache.type'],
isDeleting: false,
actions: {
"delete": function() {
var data, deletingDone, repo;
if (this.get('isDeleting')) {
return;
}
if (confirm('Are you sure?')) {
this.set('isDeleting', true);
data = {
branch: this.get('cache.branch')
};
deletingDone = () => {
return this.set('isDeleting', false);
};
repo = this.get('repo');
return this.get('ajax').ajax("/repos/" + (repo.get('id')) + "/caches", "DELETE", {
data: data
}).then(deletingDone, deletingDone).then(() => {
return this.get('caches').removeObject(this.get('cache'));
});
}
}
}
});

View File

@ -0,0 +1,10 @@
`import Ember from 'ember'`
Component = Ember.Component.extend(
actions:
close: ->
$('.popup').removeClass('display')
return false
)
`export default Component`

View File

@ -0,0 +1,55 @@
`import Ember from 'ember'`
`import { githubCommit as githubCommitUrl } from 'travis/utils/urls'`
`import config from 'travis/config/environment'`
DashboardRowComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['repo.default_branch.last_build.state']
classNames: ['dashboard-row', 'row-li']
isLoading: false
isTriggering: false
hasTriggered: false
urlGithubCommit: (->
githubCommitUrl(@get('repo.slug'), @get('repo.default_branch.last_build.commit.sha'))
).property('repo')
# canTrigger: (->
# if !@get('auth.signedIn')
# false
# else
# permissions = @get('auth.currentUser.permissions')
# if permissions.contains parseInt(@get('build.repository.id'))
# true
# else
# false
# ).property()
# triggerBuild: (->
# apiEndpoint = config.apiEndpoint
# repoId = @get('build.repository.id')
# options = {
# type: 'POST',
# body: {
# request: {
# branch: @get('build.name')
# }
# }
# }
# if @get('auth.signedIn')
# options.headers = { Authorization: "token #{@auth.token()}" }
# $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/requests", options).then (response) =>
# @set('isTriggering', false)
# @set('hasTriggered', true)
# )
actions:
tiggerBuild: (branch) ->
@set('isTriggering', true)
@triggerBuild()
# viewAllBuilds: (branch) ->
# @get('routing').transitionTo('builds')
`export default DashboardRowComponent`

View File

@ -1,36 +0,0 @@
import Ember from 'ember';
import { githubCommit as githubCommitUrl } from 'travis/utils/urls';
import config from 'travis/config/environment';
import { hasAdminPermission, hasPushPermission } from 'travis/utils/permission';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['repo.default_branch.last_build.state'],
classNames: ['rows', 'rows--dashboard'],
isLoading: false,
isTriggering: false,
hasTriggered: false,
dropupIsOpen: false,
urlGithubCommit: function() {
return githubCommitUrl(this.get('repo.slug'), this.get('repo.default_branch.last_build.commit.sha'));
}.property('repo'),
displayMenuTofu: function() {
return hasPushPermission(this.get('currentUser'), this.get('repo.id'));
},
displayActivateLink: function() {
return hasAdminPermission(this.get('currentUser'), this.get('repo.id'));
},
actions: {
tiggerBuild(branch) {
this.set('isTriggering', true);
return this.triggerBuild();
},
openDropup() {
this.toggleProperty('dropupIsOpen');
}
}
});

View File

@ -0,0 +1,29 @@
`import Ember from 'ember'`
EnvVarComponent = Ember.Component.extend
classNames: ['settings-envvar']
classNameBindings: ['envVar.public:is-public']
isDeleting: false
validates:
name: ['presence']
actionType: 'Save'
showValueField: Ember.computed.alias('public')
value: ( (key, value) ->
if @get('envVar.public')
@get('envVar.value')
else
'••••••••••••••••'
).property('envVar.value', 'envVar.public')
actions:
delete: ->
return if @get('isDeleting')
@set('isDeleting', true)
@get('envVar').destroyRecord()
`export default EnvVarComponent`

View File

@ -1,28 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['settings-envvar'],
classNameBindings: ['envVar.public:is-public'],
isDeleting: false,
validates: { name: ['presence'] },
actionType: 'Save',
showValueField: Ember.computed.alias('public'),
value: function(key) {
if (this.get('envVar.public')) {
return this.get('envVar.value');
} else {
return '••••••••••••••••';
}
}.property('envVar.value', 'envVar.public'),
actions: {
"delete": function() {
if (this.get('isDeleting')) {
return;
}
this.set('isDeleting', true);
return this.get('envVar').destroyRecord();
}
}
});

View File

@ -0,0 +1,8 @@
`import Ember from 'ember'`
EyeIconComponent = Ember.Component.extend
tagName: 'span'
classNames: ['icon-eye']
`export default EyeIconComponent`

View File

@ -1,6 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'span',
classNames: ['icon-eye']
});

View File

@ -0,0 +1,15 @@
`import Ember from 'ember'`
FlashDisplayComponent = Ember.Component.extend
flashes: Ember.inject.service()
classNames: ['flash']
tagName: 'ul'
messagesBinding: 'flashes.messages'
actions:
closeMessage: (msg) ->
@get('flashes').close(msg)
`export default FlashDisplayComponent`

View File

@ -1,14 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
flashes: Ember.inject.service(),
classNames: ['flash'],
tagName: 'ul',
messagesBinding: 'flashes.messages',
actions: {
closeMessage(msg) {
return this.get('flashes').close(msg);
}
}
});

View File

@ -0,0 +1,15 @@
`import Ember from 'ember'`
FlashItemComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['type']
type: (->
@get('flash.type') || 'broadcast'
).property('flash.type')
actions:
close: ->
this.attrs.close(@get('flash'))
`export default FlashItemComponent`

View File

@ -1,16 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['type'],
type: function() {
return this.get('flash.type') || 'broadcast';
}.property('flash.type'),
actions: {
close() {
return this.attrs.close(this.get('flash'));
}
}
});

View File

@ -0,0 +1,18 @@
`import Ember from 'ember'`
HookSwitchComponent = Ember.Component.extend
tagName: 'a'
classNames: ['switch--icon']
classNameBindings: ['active']
activeBinding: "hook.active"
click: ->
@sendAction('onToggle')
hook = @get('hook')
hook.toggle().then( (->), =>
@toggleProperty('hook.active')
@sendAction('onToggleError', hook)
)
`export default HookSwitchComponent`

View File

@ -1,17 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
classNames: ['switch--icon'],
classNameBindings: ['active'],
activeBinding: "hook.active",
click() {
var hook;
this.sendAction('onToggle');
hook = this.get('hook');
return hook.toggle().then((function() {}), () => {
this.toggleProperty('hook.active');
return this.sendAction('onToggleError', hook);
});
}
});

View File

@ -0,0 +1,21 @@
`import Ember from 'ember'`
`import config from 'travis/config/environment'`
HooksListItemComponent = Ember.Component.extend
tagName: 'li'
classNames: ['row']
classNameBindings: ['hook.active:active']
githubOrgsOauthAccessSettingsUrl: config.githubOrgsOauthAccessSettingsUrl
actions:
handleToggleError: ->
@set("showError", true)
close: ->
@send('resetErrors')
resetErrors: ->
@set("showError", false)
`export default HooksListItemComponent`

View File

@ -1,23 +0,0 @@
import Ember from 'ember';
import config from 'travis/config/environment';
export default Ember.Component.extend({
tagName: 'li',
classNames: ['row'],
classNameBindings: ['hook.active:active'],
githubOrgsOauthAccessSettingsUrl: config.githubOrgsOauthAccessSettingsUrl,
actions: {
handleToggleError() {
return this.set("showError", true);
},
close() {
return this.send('resetErrors');
},
resetErrors() {
return this.set("showError", false);
}
}
});

View File

@ -0,0 +1,30 @@
`import Ember from 'ember'`
JobLogComponent = Ember.Component.extend
logBinding: 'job.log'
didInsertElement: ->
@setupLog()
logDidChange: (->
@setupLog()
).observes('log')
logWillChange: (->
@teardownLog()
).observesBefore('log')
willDestroyElement: ->
@teardownLog()
teardownLog: ->
job = @get('job')
job.unsubscribe() if job
setupLog: ->
job = @get('job')
if job
job.get('log').fetch()
job.subscribe()
`export default JobLogComponent`

View File

@ -1,35 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
logBinding: 'job.log',
classNames: ['job-log'],
didReceiveAttrs: function(options) {
this._super(...arguments);
let oldJob = options.oldAttrs && options.oldAttrs.job && options.oldAttrs.job.value,
newJob = options.newAttrs && options.newAttrs.job && options.newAttrs.job.value;
if(newJob !== oldJob) {
if(newJob) {
this.setupLog(newJob);
}
if(oldJob) {
this.teardownLog(oldJob);
}
}
},
teardownLog(job) {
job.unsubscribe();
},
setupLog(job) {
this.set('error', false);
job.get('log').fetch().then(function() { }, () => {
this.set('error', true);
});
job.subscribe();
}
});

View File

@ -0,0 +1,9 @@
`import Ember from 'ember'`
`import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin'`
JobRepoActionsComponent = Ember.Component.extend(RepoActionsItemComponentMixin,
item: Ember.computed.alias('job')
type: 'job'
)
`export default JobRepoActionsComponent`

View File

@ -1,7 +0,0 @@
import Ember from 'ember';
import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin';
export default Ember.Component.extend(RepoActionsItemComponentMixin, {
item: Ember.computed.alias('job'),
type: 'job'
});

View File

@ -1,18 +0,0 @@
import Ember from 'ember';
import { colorForState } from 'travis/utils/helpers';
import { githubCommit } from 'travis/utils/urls';
import Polling from 'travis/mixins/polling';
export default Ember.Component.extend({
pollModels: 'job.build',
commitBinding: 'job.commit',
currentItemBinding: 'job',
color: function() {
return colorForState(this.get('job.state'));
}.property('job.state'),
urlGithubCommit: function() {
return githubCommit(this.get('repo.slug'), this.get('commit.sha'));
}.property('repo.slug', 'commit.sha')
});

View File

@ -0,0 +1,41 @@
`import Ember from 'ember'`
`import { colorForState } from 'travis/utils/helpers'`
`import { languageConfigKeys } from 'travis/utils/keys-map';`
JobsItemComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['job.state']
classNames: ['tile', 'tile--jobs', 'row']
isAnimating: (->
state = @get('job.state')
animationStates = ['received', 'queued', 'started', 'booting']
unless animationStates.indexOf(state) == -1
true
).property('job.state')
languages: (->
output = []
if config = @get('job.config')
for key, languageName of languageConfigKeys
if version = config[key]
output.push(languageName + ': ' + version)
gemfile = @get('job.config.gemfile')
if gemfile && @get('job.config.env')
output.push "Gemfile: #{gemfile}"
output.join(' ')
).property('job.config')
environment: (->
if env = @get('job.config.env')
env
else if gemfile = @get('job.config.gemfile')
"Gemfile: #{gemfile}"
).property('job.config.env', 'job.config.gemfile')
`export default JobsItemComponent`

View File

@ -1,36 +0,0 @@
import Ember from 'ember';
import { colorForState } from 'travis/utils/helpers';
import { languageConfigKeys } from 'travis/utils/keys-map';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['job.state'],
classNames: ['jobs-item'],
languages: function() {
var config, gemfile, key, languageName, output, version;
output = [];
if (config = this.get('job.config')) {
for (key in languageConfigKeys) {
languageName = languageConfigKeys[key];
if (version = config[key]) {
output.push(languageName + ': ' + version);
}
}
gemfile = this.get('job.config.gemfile');
if (gemfile && this.get('job.config.env')) {
output.push("Gemfile: " + gemfile);
}
}
return output.join(' ');
}.property('job.config'),
environment: function() {
var env, gemfile;
if (env = this.get('job.config.env')) {
return env;
} else if (gemfile = this.get('job.config.gemfile')) {
return "Gemfile: " + gemfile;
}
}.property('job.config.env', 'job.config.gemfile')
});

View File

@ -0,0 +1,11 @@
`import Ember from 'ember'`
JobsListComponent = Ember.Component.extend
jobTableId: Ember.computed(->
if @get('required')
'jobs'
else
'allowed_failure_jobs'
)
`export default JobsListComponent`

View File

@ -1,13 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'section',
classNames: ['jobs'],
jobTableId: Ember.computed(function() {
if (this.get('required')) {
return 'jobs';
} else {
return 'allowed_failure_jobs';
}
})
});

View File

@ -0,0 +1,12 @@
`import Ember from 'ember'`
`import { githubCommit as githubCommitUrl } from 'travis/utils/urls'`
`import TravisRoute from 'travis/routes/basic'`
`import config from 'travis/config/environment'`
LandingRowComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['repo.lastBuildState']
classNames: ['landing-row', 'row-li']
`export default LandingRowComponent`

View File

@ -1,8 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['build.state']
});

View File

@ -0,0 +1,40 @@
`import Ember from 'ember'`
LimitConcurrentBuildsComponent = Ember.Component.extend
classNames: ['limit-concurrent-builds']
description: (->
description = "Limit concurrent jobs"
if @get('enabled')
description += " "
description
).property('enabled')
limitChanged: ->
repo = @get('repo')
limit = parseInt(@get('value'))
if limit
@set('isSaving', true)
savingFinished = =>
@set('isSaving', false)
repo.saveSettings(maximum_number_of_builds: limit).
then(savingFinished, savingFinished)
actions:
toggle: ->
unless @get('enabled')
return if @get('value') == 0
return if @get('isSaving')
@set('isSaving', true)
savingFinished = =>
@set('isSaving', false)
@get('repo').saveSettings(maximum_number_of_builds: 0).then(savingFinished, savingFinished)
@set('value', 0)
limitChanged: ->
Ember.run.debounce(this, 'limitChanged', 1000)
`export default LimitConcurrentBuildsComponent`

View File

@ -1,55 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['limit-concurrent-builds'],
description: function() {
var description;
description = "Limit concurrent jobs";
if (this.get('enabled')) {
description += " ";
}
return description;
}.property('enabled'),
limitChanged() {
var limit, repo, savingFinished;
repo = this.get('repo');
limit = parseInt(this.get('value'));
if (limit) {
this.set('isSaving', true);
savingFinished = () => {
return this.set('isSaving', false);
};
return repo.saveSettings({
maximum_number_of_builds: limit
}).then(savingFinished, savingFinished);
}
},
actions: {
toggle() {
var savingFinished;
if (!this.get('enabled')) {
if (this.get('value') === 0) {
return;
}
if (this.get('isSaving')) {
return;
}
this.set('isSaving', true);
savingFinished = () => {
return this.set('isSaving', false);
};
this.get('repo').saveSettings({
maximum_number_of_builds: 0
}).then(savingFinished, savingFinished);
return this.set('value', 0);
}
},
limitChanged() {
return Ember.run.debounce(this, 'limitChanged', 1000);
}
}
});

View File

@ -0,0 +1,8 @@
`import Ember from 'ember'`
LoadingIndicatorComponent = Ember.Component.extend
tagName: 'div'
classNameBindings: ['center:loading-container', 'inline:inline-block']
center: false
`export default LoadingIndicatorComponent`

View File

@ -1,7 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'div',
classNameBindings: ['center:loading-container', 'inline:inline-block', 'height:icon-height'],
center: false
});

View File

@ -0,0 +1,157 @@
`import Ember from 'ember'`
`import LinesSelector from 'travis/utils/lines-selector'`
`import LogFolder from 'travis/utils/log-folder'`
`import config from 'travis/config/environment'`
`import { plainTextLog as plainTextLogUrl } from 'travis/utils/urls'`
Log.DEBUG = false
Log.LIMIT = 10000
Log.Scroll = (options) ->
options ||= {}
@beforeScroll = options.beforeScroll
this
Log.Scroll.prototype = $.extend new Log.Listener,
insert: (log, data, pos) ->
@tryScroll() if @numbers
true
tryScroll: ->
if element = $("#log p:visible.highlight:first")
if @beforeScroll
@beforeScroll()
$('#main').scrollTop(0)
$('html, body').scrollTop(element.offset()?.top - (window.innerHeight / 3)) # weird, html works in chrome, body in firefox
Log.Limit = (max_lines, limitedLogCallback) ->
@max_lines = max_lines || 1000
@limitedLogCallback = limitedLogCallback || (->)
this
Log.Limit.prototype = Log.extend new Log.Listener,
count: 0,
insert: (log, node, pos) ->
if node.type == 'paragraph' && !node.hidden
@count += 1
if @limited
@limitedLogCallback()
return @count
Object.defineProperty Log.Limit.prototype, 'limited',
get: ->
@count >= @max_lines
LogContentComponent = Ember.Component.extend
popup: Ember.inject.service()
currentUserBinding: 'auth.currentUser'
didInsertElement: ->
console.log 'log view: did insert' if Log.DEBUG
@_super.apply this, arguments
@createEngine()
willDestroyElement: ->
console.log 'log view: will destroy' if Log.DEBUG
@teardownLog()
teardownLog: (log) ->
if log || log = @get('log')
parts = log.get('parts')
parts.removeArrayObserver(@, didChange: 'partsDidChange', willChange: 'noop')
parts.destroy()
log.notifyPropertyChange('parts')
@lineSelector?.willDestroy()
if logElement = this.$('#log')
logElement.empty()
createEngine: (log) ->
if log || log = @get('log')
if logElement = this.$('#log')
logElement.empty()
log.onClear =>
@teardownLog()
@createEngine()
@scroll = new Log.Scroll beforeScroll: =>
@unfoldHighlight()
@limit = new Log.Limit Log.LIMIT, =>
@set('limited', true)
@engine = Log.create(listeners: [@scroll, @limit])
@engine.limit = @limit
@logFolder = new LogFolder(@$('#log'))
@lineSelector = new LinesSelector(@$('#log'), @scroll, @logFolder)
@observeParts(log)
didUpdateAttrs: (changes) ->
@_super.apply(this, arguments)
return unless changes.oldAttrs
if changes.newAttrs.job.value && changes.oldAttrs.job.value &&
changes.newAttrs.job.value != changes.oldAttrs.job.value
@teardownLog(changes.oldAttrs.job.value.get('log'))
@createEngine(changes.newAttrs.job.value.get('log'))
unfoldHighlight: ->
@lineSelector.unfoldLines()
observeParts: (log) ->
if log || log = @get('log')
parts = log.get('parts')
parts.addArrayObserver(@, didChange: 'partsDidChange', willChange: 'noop')
parts = parts.slice(0)
@partsDidChange(parts, 0, null, parts.length)
partsDidChange: (parts, start, _, added) ->
console.log 'log view: parts did change' if Log.DEBUG
return unless @get('state') == 'inDOM'
for part, i in parts.slice(start, start + added)
# console.log "limit in log view: #{@get('limited')}"
break if @engine?.limit?.limited
@engine.set(part.number, part.content)
plainTextLogUrl: (->
if id = @get('log.job.id')
url = plainTextLogUrl(id)
if config.pro
url += "&access_token=#{@get('job.log.token')}"
url
).property('job.log.id', 'job.log.token')
hasPermission: (->
if permissions = @get('currentUser.permissions')
permissions.contains parseInt(@get('job.repo.id'))
).property('currentUser.permissions.length', 'job.repo.id')
canRemoveLog: (->
if job = @get('job')
job.get('canRemoveLog') && @get('hasPermission')
).property('job.canRemoveLog', 'hasPermission')
showToTop: (->
@get('log.hasContent') && @get('job.canRemoveLog')
).property('log.hasContent', 'job.canRemoveLog')
showTailing: Ember.computed.alias('showToTop')
actions:
toTop: () ->
Travis.tailing.stop()
$(window).scrollTop(0)
toggleTailing: ->
Travis.tailing.toggle()
@engine.autoCloseFold = !Travis.tailing.isActive()
event.preventDefault()
removeLogPopup: ->
if @get('canRemoveLog')
@get('popup').open('remove-log-popup')
return false
noop: -> # TODO required?
`export default LogContentComponent`

View File

@ -1,238 +0,0 @@
import Ember from 'ember';
import LinesSelector from 'travis/utils/lines-selector';
import LogFolder from 'travis/utils/log-folder';
import config from 'travis/config/environment';
import { plainTextLog as plainTextLogUrl } from 'travis/utils/urls';
Log.DEBUG = false;
Log.LIMIT = 10000;
Log.Scroll = function(options) {
options = options || {};
this.beforeScroll = options.beforeScroll;
return this;
};
Log.Scroll.prototype = $.extend(new Log.Listener(), {
insert: function(log, data, pos) {
if (this.numbers) {
this.tryScroll();
}
return true;
},
tryScroll: function() {
var element, ref;
if (element = $("#log p:visible.highlight:first")) {
if (this.beforeScroll) {
this.beforeScroll();
}
$('#main').scrollTop(0);
return $('html, body').scrollTop(((ref = element.offset()) != null ? ref.top : void 0) - (window.innerHeight / 3));
}
}
});
Log.Limit = function(max_lines, limitedLogCallback) {
this.max_lines = max_lines || 1000;
this.limitedLogCallback = limitedLogCallback || (function() {});
return this;
};
Log.Limit.prototype = Log.extend(new Log.Listener(), {
count: 0,
insert: function(log, node, pos) {
if (node.type === 'paragraph' && !node.hidden) {
this.count += 1;
if (this.limited) {
this.limitedLogCallback();
}
return this.count;
}
}
});
Object.defineProperty(Log.Limit.prototype, 'limited', {
get: function() {
return this.count >= this.max_lines;
}
});
export default Ember.Component.extend({
popup: Ember.inject.service(),
classNameBindings: ['logIsVisible:is-open'],
logIsVisible: false,
currentUserBinding: 'auth.currentUser',
didInsertElement() {
if (Log.DEBUG) {
console.log('log view: did insert');
}
this._super.apply(this, arguments);
Ember.run.scheduleOnce('afterRender', this, 'createEngine');
},
willDestroyElement() {
if (Log.DEBUG) {
console.log('log view: will destroy');
}
Ember.run.scheduleOnce('afterRender', this, 'teardownLog');
},
teardownLog(log) {
var parts, ref;
if (log || (log = this.get('log'))) {
parts = log.get('parts');
parts.removeArrayObserver(this, {
didChange: 'partsDidChange',
willChange: 'noop'
});
parts.destroy();
log.notifyPropertyChange('parts');
if ((ref = this.lineSelector) != null) {
ref.willDestroy();
}
this.clearLogElement();
}
},
clearLogElement() {
var logElement = this.$('#log');
if (logElement && logElement[0]) {
logElement[0].innerHTML = '';
}
},
createEngine(log) {
if (log || (log = this.get('log'))) {
this.clearLogElement();
log.onClear(() => {
this.teardownLog();
return this.createEngine();
});
this.scroll = new Log.Scroll({
beforeScroll: () => {
return this.unfoldHighlight();
}
});
this.limit = new Log.Limit(Log.LIMIT, () => {
return this.set('limited', true);
});
this.engine = Log.create({
listeners: [this.scroll, this.limit]
});
this.engine.limit = this.limit;
this.logFolder = new LogFolder(this.$('#log'));
this.lineSelector = new LinesSelector(this.$('#log'), this.scroll, this.logFolder);
this.observeParts(log);
}
},
didUpdateAttrs(changes) {
this._super.apply(this, arguments);
if (!changes.oldAttrs) {
return;
}
if (changes.newAttrs.job.value && changes.oldAttrs.job.value && changes.newAttrs.job.value !== changes.oldAttrs.job.value) {
this.teardownLog(changes.oldAttrs.job.value.get('log'));
return this.createEngine(changes.newAttrs.job.value.get('log'));
}
},
unfoldHighlight() {
return this.lineSelector.unfoldLines();
},
observeParts(log) {
var parts;
if (log || (log = this.get('log'))) {
parts = log.get('parts');
parts.addArrayObserver(this, {
didChange: 'partsDidChange',
willChange: 'noop'
});
parts = parts.slice(0);
this.partsDidChange(parts, 0, null, parts.length);
}
},
partsDidChange(parts, start, _, added) {
Ember.run.schedule('afterRender', this, function() {
var i, j, len, part, ref, ref1, ref2, results;
if (Log.DEBUG) {
console.log('log view: parts did change');
}
if (this.get('_state') !== 'inDOM') {
return;
}
ref = parts.slice(start, start + added);
results = [];
for (i = j = 0, len = ref.length; j < len; i = ++j) {
part = ref[i];
if ((ref1 = this.engine) != null ? (ref2 = ref1.limit) != null ? ref2.limited : void 0 : void 0) {
break;
}
results.push(this.engine.set(part.number, part.content));
}
return results;
});
},
plainTextLogUrl: function() {
var id, url;
if (id = this.get('log.job.id')) {
url = plainTextLogUrl(id);
if (config.pro) {
url += "&access_token=" + (this.get('job.log.token'));
}
return url;
}
}.property('job.log.id', 'job.log.token'),
hasPermission: function() {
var permissions;
if (permissions = this.get('currentUser.permissions')) {
return permissions.contains(parseInt(this.get('job.repo.id')));
}
}.property('currentUser.permissions.length', 'job.repo.id'),
canRemoveLog: function() {
var job;
if (job = this.get('job')) {
return job.get('canRemoveLog') && this.get('hasPermission');
}
}.property('job.canRemoveLog', 'hasPermission'),
showToTop: function() {
return this.get('log.hasContent') && this.get('job.canRemoveLog');
}.property('log.hasContent', 'job.canRemoveLog'),
showTailing: Ember.computed.alias('showToTop'),
actions: {
toTop() {
Travis.tailing.stop();
return $(window).scrollTop(0);
},
toggleTailing() {
Travis.tailing.toggle();
this.engine.autoCloseFold = !Travis.tailing.isActive();
return false;
},
removeLogPopup() {
if (this.get('canRemoveLog')) {
this.get('popup').open('remove-log-popup');
return false;
}
},
toggleLog() {
this.toggleProperty('logIsVisible');
}
},
// don't remove this, it's needed as an empty willChange callback
noop: function() {}
});

View File

@ -0,0 +1,19 @@
`import Ember from 'ember'`
`import config from 'travis/config/environment'`
NoBuildsComponent = Ember.Component.extend
actions:
triggerBuild: () ->
@set('isLoading', true)
apiEndpoint = config.apiEndpoint
$.ajax(apiEndpoint + "/v3/repo/#{@get('repo.repo.id')}/requests", {
headers: {
Authorization: 'token ' + @get('repo.auth')
},
type: "POST"
}).then( =>
@set('isLoading', false)
# @transitionToRoute('repo')
);
`export default NoBuildsComponent`

View File

@ -1,20 +0,0 @@
import Ember from 'ember';
import config from 'travis/config/environment';
export default Ember.Component.extend({
actions: {
triggerBuild() {
var apiEndpoint;
this.set('isLoading', true);
apiEndpoint = config.apiEndpoint;
return $.ajax(apiEndpoint + ("/v3/repo/" + (this.get('repo.repo.id')) + "/requests"), {
headers: {
Authorization: 'token ' + this.get('repo.auth')
},
type: "POST"
}).then(() => {
return this.set('isLoading', false);
});
}
}
});

View File

@ -0,0 +1,5 @@
`import Ember from 'ember'`
NoReposComponent = Ember.Component.extend()
`export default NoReposComponent`

View File

@ -1,3 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend();

View File

@ -0,0 +1,5 @@
`import Ember from 'ember'`
NotActiveComponent = Ember.Component.extend()
`export default NotActiveComponent`

View File

@ -1,3 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend();

View File

@ -1,28 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['media', 'account'],
tagName: 'li',
classNameBindings: ['type', 'selected'],
typeBinding: 'account.type',
selectedBinding: 'account.selected',
tokenIsVisible: false,
name: function() {
return this.get('account.name') || this.get('account.login');
}.property('account'),
avatarUrl: function() {
return this.get('account.avatarUrl') || false;
}.property('account'),
isUser: function() {
return this.get('account.type') === 'user';
}.property('account'),
actions: {
tokenVisibility() {
return this.toggleProperty('tokenIsVisible');
}
}
});

View File

@ -0,0 +1,13 @@
`import Ember from 'ember'`
Component = Ember.Component.extend
actions:
toggleOrgFilter: () ->
@toggleProperty('showFilter')
false
select: (org) ->
@toggleProperty('showFilter')
@sendAction('action', org)
`export default Component`

View File

@ -1,17 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['organisation-filter'],
actions: {
toggleOrgFilter() {
this.toggleProperty('showFilter');
return false;
},
select(org) {
this.toggleProperty('showFilter');
return this.sendAction('action', org);
}
}
});

View File

@ -0,0 +1,29 @@
`import Ember from 'ember'`
OwnerRepoTileComponent = Ember.Component.extend
tagName: 'li'
classNames: ['owner-tile', 'row-li']
classNameBindings: ['repo.default_branch.last_build.state']
ownerName: (->
@get('repo.slug').split(/\//)[0]
).property('repo.slug')
repoName: (->
@get('repo.slug').split(/\//)[1]
).property('repo.slug')
isAnimating: (->
state = @get('repo.default_branch.last_build.state')
animationStates = ['received', 'queued', 'started', 'booting']
unless animationStates.indexOf(state) == -1
true
).property('repo.default_branch.last_build.state')
`export default OwnerRepoTileComponent`

View File

@ -1,24 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'li',
classNames: ['owner-tile', 'row-li'],
classNameBindings: ['repo.default_branch.last_build.state'],
ownerName: function() {
return this.get('repo.slug').split(/\//)[0];
}.property('repo.slug'),
repoName: function() {
return this.get('repo.slug').split(/\//)[1];
}.property('repo.slug'),
isAnimating: function() {
var animationStates, state;
state = this.get('repo.default_branch.last_build.state');
animationStates = ['received', 'queued', 'started', 'booting'];
if (animationStates.indexOf(state) !== -1) {
return true;
}
}.property('repo.default_branch.last_build.state')
});

View File

@ -0,0 +1,6 @@
`import Ember from 'ember'`
`import SyncButton from 'travis/components/sync-button'`
Component = SyncButton.extend()
`export default Component`

View File

@ -1,4 +0,0 @@
import Ember from 'ember';
import SyncButton from 'travis/components/sync-button';
export default SyncButton.extend();

View File

@ -1,17 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
popup: Ember.inject.service(),
classNames: ['application'],
click(event) {
var targetAndParents = $(event.target).parents().andSelf();
if (!(targetAndParents.hasClass('open-popup') || targetAndParents.hasClass('popup'))) {
this.get('popup').close();
}
if (!targetAndParents.hasClass('menu') && !targetAndParents.is('#tools > a')) {
$('.menu').removeClass('display');
}
}
});

View File

@ -1,6 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['profile-orglist', 'columns', 'medium-4'],
tagName: 'aside',
});

View File

@ -0,0 +1,16 @@
`import Ember from 'ember'`
`import config from 'travis/config/environment'`
QueuedJobsComponent = Ember.Component.extend
store: Ember.inject.service()
init: ->
@_super.apply this, arguments
if !Ember.testing
Visibility.every config.intervals.updateTimes, @updateTimes.bind(this)
updateTimes: ->
if jobs = @get('jobs')
jobs.forEach (job) -> job.updateTimes()
`export default QueuedJobsComponent`

View File

@ -1,18 +0,0 @@
import Ember from 'ember';
import config from 'travis/config/environment';
export default Ember.Component.extend({
store: Ember.inject.service(),
updateTimesService: Ember.inject.service('updateTimes'),
init() {
this._super.apply(this, arguments);
if (!Ember.testing) {
return Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
}
},
updateTimes() {
this.get('updateTimesService').push(this.get('jobs'));
}
});

View File

@ -0,0 +1,23 @@
`import Ember from 'ember'`
Component = Ember.Component.extend(
actions:
close: ->
$('.popup').removeClass('display')
return false
removeLog: ->
$('.popup').removeClass('display')
job = @get('job')
job.removeLog().then ->
Travis.flash(success: 'Log has been successfully removed.')
, (xhr) ->
if xhr.status == 409
Travis.flash(error: 'Log can\'t be removed')
else if xhr.status == 401
Travis.flash(error: 'You don\'t have sufficient access to remove the log')
else
Travis.flash(error: 'An error occured when removing the log')
)
`export default Component`

View File

@ -1,27 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
close() {
$('.popup').removeClass('display');
return false;
},
removeLog() {
var job = this.get('job');
$('.popup').removeClass('display');
return job.removeLog().then(function() {
return Travis.flash({ success: 'Log has been successfully removed.' });
}, function(xhr) {
if (xhr.status === 409) {
return Travis.flash({ error: 'Log can\'t be removed' });
} else if (xhr.status === 401) {
return Travis.flash({ error: 'You don\'t have sufficient access to remove the log' });
} else {
return Travis.flash({ error: 'An error occured when removing the log' });
}
});
}
}
});

View File

@ -0,0 +1,15 @@
`import Ember from 'ember'`
RepoActionsComponent = Ember.Component.extend(
displayCodeClimate: (->
@get('repo.githubLanguage') == 'Ruby'
).property('repo.githubLanguage')
actions:
codeClimatePopup: ->
$('.popup').removeClass('display')
$('#code-climate').addClass('display')
return false
)
`export default RepoActionsComponent`

View File

@ -1,7 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['repo-main-tools']
});

View File

@ -0,0 +1,54 @@
`import Ember from 'ember'`
RepoShowTabsComponent = Ember.Component.extend
# hrm. how to parametrize bind-attr?
classCurrent: (->
'active' if @get('tab') == 'current'
).property('tab')
classBuilds: (->
'active' if @get('tab') == 'builds'
).property('tab')
classPullRequests: (->
'active' if @get('tab') == 'pull_requests'
).property('tab')
classBranches: (->
'active' if @get('tab') == 'branches'
).property('tab')
classEvents: (->
'active' if @get('tab') == 'events'
).property('tab')
classBuild: (->
tab = @get('tab')
classes = []
classes.push('active') if tab == 'build'
classes.push('display-inline') if tab == 'build' || tab == 'job'
classes.join(' ')
).property('tab')
# TODO: refactor tabs, most of the things here are not really DRY
classJob: (->
'active' if @get('tab') == 'job'
).property('tab')
classRequests: (->
'active' if @get('tab') == 'requests'
).property('tab')
classCaches: (->
'active' if @get('tab') == 'caches'
).property('tab')
classSettings: (->
'active' if @get('tab') == 'settings'
).property('tab')
classRequest: (->
'active' if @get('tab') == 'request'
).property('tab')
`export default RepoShowTabsComponent`

View File

@ -1,74 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'nav',
classNames: ['tabnav'],
ariaRole: 'tablist',
classCurrent: function() {
if (this.get('tab') === 'current') {
return 'active';
}
}.property('tab'),
classBuilds: function() {
if (this.get('tab') === 'builds') {
return 'active';
}
}.property('tab'),
classPullRequests: function() {
if (this.get('tab') === 'pull_requests') {
return 'active';
}
}.property('tab'),
classBranches: function() {
if (this.get('tab') === 'branches') {
return 'active';
}
}.property('tab'),
classBuild: function() {
var classes, tab;
tab = this.get('tab');
classes = [];
if (tab === 'build') {
classes.push('active');
}
if (tab === 'build' || tab === 'job') {
classes.push('display-inline');
}
return classes.join(' ');
}.property('tab'),
classJob: function() {
if (this.get('tab') === 'job') {
return 'active';
}
}.property('tab'),
classRequests: function() {
if (this.get('tab') === 'requests') {
return 'active';
}
}.property('tab'),
classCaches: function() {
if (this.get('tab') === 'caches') {
return 'active';
}
}.property('tab'),
classSettings: function() {
if (this.get('tab') === 'settings') {
return 'active';
}
}.property('tab'),
classRequest: function() {
if (this.get('tab') === 'request') {
return 'active';
}
}.property('tab')
});

View File

@ -0,0 +1,47 @@
`import Ember from 'ember'`
`import config from 'travis/config/environment'`
RepoShowToolsComponent = Ember.Component.extend
popup: Ember.inject.service()
click: (event) ->
if $(event.target).is('a') && $(event.target).parents('.dropdown-menu').length
@closeMenu()
closeMenu: ->
$('.menu').removeClass('display')
actions:
menu: ->
@get('popup').close()
$('#tools .menu').toggleClass('display')
return false
hasPermission: (->
if permissions = @get('currentUser.permissions')
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.permissions.length', 'repo.id')
hasPushPermission: (->
if permissions = @get('currentUser.pushPermissions')
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.pushPermissions.length', 'repo.id')
hasAdminPermission: (->
if permissions = @get('currentUser.adminPermissions')
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.adminPermissions.length', 'repo.id')
displaySettingsLink: (->
@get('hasPushPermission')
).property('hasPushPermission')
displayCachesLink: (->
@get('hasPushPermission') && config.endpoints.caches
).property('hasPushPermission')
displayStatusImages: (->
@get('hasPermission')
).property('hasPermission')
`export default RepoShowToolsComponent`

View File

@ -1,38 +0,0 @@
import Ember from 'ember';
import config from 'travis/config/environment';
import { hasPermission, hasPushPermission } from 'travis/utils/permission';
export default Ember.Component.extend({
popup: Ember.inject.service(),
classNames: ['option-button'],
classNameBindings: ['isOpen:display'],
isOpen: false,
click(event) {
if ($(event.target).is('a') && $(event.target).parents('.settings-dropdown').length) {
return this.closeMenu();
}
},
closeMenu() {
return this.toggleProperty('isOpen');
},
actions: {
menu() {
return this.toggleProperty('isOpen');
}
},
displaySettingsLink: function() {
return hasPushPermission(this.get('currentUser'), this.get('repo.id'));
}.property('currentUser.pushPermissions.length', 'repo'),
displayCachesLink: function() {
return hasPushPermission(this.get('currentUser'), this.get('repo.id')) && config.endpoints.caches;
}.property('currentUser.pushPermissions.length', 'repo'),
displayStatusImages: function() {
return hasPermission(this.get('currentUser'), this.get('repo.id'));
}.property('currentUser.permissions.length', 'repo.id')
});

View File

@ -1,7 +0,0 @@
import Polling from 'travis/mixins/polling';
import Ember from 'ember';
export default Ember.Component.extend(Polling, {
pollModels: 'repo',
classNameBindings: ['isLoading:loading']
});

View File

@ -0,0 +1,5 @@
`import Ember from 'ember'`
ReposEmptyComponent = Ember.Component.extend()
`export default ReposEmptyComponent`

View File

@ -1,3 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend();

View File

@ -0,0 +1,31 @@
`import Ember from 'ember'`
`import Polling from 'travis/mixins/polling'`
`import { colorForState } from 'travis/utils/helpers'`
ReposListItemComponent = Ember.Component.extend Polling,
routing: Ember.inject.service('-routing')
tagName: 'li'
pollModels: 'repo'
classNames: ['repo']
classNameBindings: ['color', 'selected']
selected: (->
@get('repo') == @get('selectedRepo')
).property('selectedRepo')
color: (->
colorForState(@get('repo.defaultBranch.lastBuild.state'))
).property('repo.defaultBranch.lastBuild.state')
scrollTop: (->
if (window.scrollY > 0)
$('html, body').animate({scrollTop: 0}, 200)
)
click: ->
@scrollTop()
@get('routing').transitionTo('repo', @get('repo.slug').split('/'))
`export default ReposListItemComponent`

View File

@ -1,27 +0,0 @@
import Ember from 'ember';
import Polling from 'travis/mixins/polling';
import { colorForState } from 'travis/utils/helpers';
export default Ember.Component.extend(Polling, {
routing: Ember.inject.service('-routing'),
tagName: 'li',
pollModels: 'repo',
classNames: ['repo'],
classNameBindings: ['selected'],
selected: function() {
return this.get('repo') === this.get('selectedRepo');
}.property('selectedRepo'),
color: function() {
return colorForState(this.get('repo.lastBuildState'));
}.property('repo.lastBuildState'),
scrollTop: function() {
if (window.scrollY > 0) {
return $('html, body').animate({
scrollTop: 0
}, 200);
}
}
});

View File

@ -1,48 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
auth: Ember.inject.service(),
currentUserBinding: 'auth.currentUser',
classRecent: function() {
if (this.get('tab') === 'recent') {
return 'active';
} else if (this.get('tab') === 'search' && this.get('auth.signedIn')) {
return 'hidden';
}
}.property('tab'),
classRunning: function() {
var classes;
classes = [];
if (this.get('tab') === 'running') {
classes.push('active');
}
return classes.join(' ');
}.property('tab'),
classOwned: function() {
var classes;
classes = [];
if (this.get('tab') === 'owned') {
classes.push('active');
}
if (this.get('currentUser')) {
classes.push('display-inline');
}
return classes.join(' ');
}.property('tab', 'currentUser'),
classSearch: function() {
if (this.get('tab') === 'search') {
return 'active';
}
}.property('tab'),
classNew: function() {
if (this.get('currentUser')) {
return 'display-inline';
}
}.property('currentUser')
});

View File

@ -1,7 +1,66 @@
import Ember from 'ember'; import Ember from 'ember';
var sortCallback = function(repo1, repo2) {
// this function could be made simpler, but I think it's clearer this way
// what're we really trying to achieve
var lastBuild1 = repo1.get('defaultBranch.lastBuild');
var lastBuild2 = repo2.get('defaultBranch.lastBuild');
if(!lastBuild1 && !lastBuild2) {
// if both repos lack builds, put newer repo first
return repo1.get('id') > repo2.get('id') ? -1 : 1;
} else if(lastBuild1 && !lastBuild2) {
// if only repo1 has a build, it goes first
return -1;
} else if(lastBuild2 && !lastBuild1) {
// if only repo2 has a build, it goes first
return 1;
}
var finishedAt1 = lastBuild1.get('finishedAt');
var finishedAt2 = lastBuild2.get('finishedAt');
if(finishedAt1) {
finishedAt1 = new Date(finishedAt1);
}
if(finishedAt2) {
finishedAt2 = new Date(finishedAt2);
}
if(finishedAt1 && finishedAt2) {
// if both builds finished, put newer first
return finishedAt1.getTime() > finishedAt2.getTime() ? -1 : 1;
} else if(finishedAt1 && !finishedAt2) {
// if repo1 finished, but repo2 didn't, put repo2 first
return 1;
} else if(finishedAt2 && !finishedAt1) {
// if repo2 finisher, but repo1 didn't, put repo1 first
return -1;
} else {
// none of the builds finished, put newer build first
return lastBuild1.get('id') > lastBuild2.get('id') ? -1 : 1;
}
throw "should not happen";
};
var ReposListComponent = Ember.Component.extend({ var ReposListComponent = Ember.Component.extend({
tagName: 'ul' tagName: 'ul',
sortedRepos: function() {
var repos = this.get('repos');
if(repos && repos.toArray) {
repos = repos.toArray();
}
if(repos && repos.sort) {
return repos.sort(sortCallback);
} else {
return [];
}
}.property('repos.[]', 'repos')
}); });
export default ReposListComponent; export default ReposListComponent;

View File

@ -0,0 +1,30 @@
`import Ember from 'ember'`
RequestIconComponent = Ember.Component.extend
tagName: 'span'
classNames: ['icon-request', 'icon']
classNameBindings: ['build.last_build.state', 'build.state']
isPush: (->
@get('build.last_build.event_type') == 'push' ||
@get('build.event_type') == 'push'
).property('build.last_build')
isPR: (->
@get('build.last_build.event_type') == 'pull_request' ||
@get('build.event_type') == 'pull_request'
).property('build.last_build')
isAPI: (->
@get('build.last_build.event_type') == 'api' ||
@get('build.event_type') == 'api'
).property('build.last_build')
isEmpty: (->
true if @get('build.last_build') == null || @get('build') == null
).property('build.last_build')
`export default RequestIconComponent`

Some files were not shown because too many files have changed in this diff Show More