diff --git a/.ember-cli b/.ember-cli
index ee64cfed2a..8c1812cff8 100644
--- a/.ember-cli
+++ b/.ember-cli
@@ -5,5 +5,11 @@
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
- "disableAnalytics": false
+ "disableAnalytics": false,
+
+ /**
+ Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
+ rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
+ */
+ "isTypeScriptProject": false
}
diff --git a/.eslintignore b/.eslintignore
index 42f2fcb285..5712961348 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -15,8 +15,13 @@
# misc
/coverage/
!.*
+.*/
+.eslintcache
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
+/npm-shrinkwrap.json.ember-try
/package.json.ember-try
+/package-lock.json.ember-try
+/yarn.lock.ember-try
diff --git a/.eslintrc.js b/.eslintrc.js
index cbeb5c658e..eaf563f3e4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,16 +2,22 @@
module.exports = {
root: true,
+ parser: '@babel/eslint-parser',
parserOptions: {
- ecmaVersion: 2018,
- sourceType: 'module'
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ requireConfigFile: false,
+ babelOptions: {
+ plugins: [
+ ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
+ ],
+ },
},
- plugins: [
- 'ember'
- ],
+ plugins: ['ember'],
extends: [
'eslint:recommended',
- 'plugin:ember/recommended'
+ 'plugin:ember/recommended',
+ 'plugin:prettier/recommended',
],
env: {
browser: true,
@@ -317,21 +323,23 @@ module.exports = {
// node files
{
files: [
- '.eslintrc.js',
- '.template-lintrc.js',
- 'ember-cli-build.js',
- 'testem.js',
- 'blueprints/*/index.js',
- 'config/**/*.js',
- 'lib/*/index.js',
- 'server/**/*.js'
+ './.eslintrc.js',
+ './.prettierrc.js',
+ './.stylelintrc.js',
+ './.template-lintrc.js',
+ './ember-cli-build.js',
+ './testem.js',
+ './blueprints/*/index.js',
+ './config/**/*.js',
+ './lib/*/index.js',
+ './server/**/*.js',
],
parserOptions: {
- sourceType: 'script'
+ sourceType: 'script',
},
env: {
browser: false,
- node: true
+ node: true,
}
},
@@ -347,13 +355,22 @@ module.exports = {
triggerCopyError: true,
signInUser: true,
withFeature: true,
- percySnapshot: true,
waitForElement: true
},
+ plugins: ['node'],
+ extends: ['plugin:node/recommended'],
rules: {
+ // this can be removed once the following is fixed
+ // https://github.com/mysticatea/eslint-plugin-node/issues/77
+ 'node/no-unpublished-require': 'off',
'max-len': 0,
'no-useless-escape': 0,
- }
- }
- ]
+ },
+ },
+ {
+ // test files
+ files: ['tests/**/*-test.{js,ts}'],
+ extends: ['plugin:qunit/recommended'],
+ },
+ ],
};
diff --git a/.gitignore b/.gitignore
index 3d5203f0f9..57b77ca506 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
/.env
/.pnp*
/.sass-cache
+/.eslintcache
/connect.lock
/coverage/
/libpeerconnection.log
@@ -19,11 +20,15 @@
/testem.log
/yarn-error.log
.DS_Store
+env
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
+/npm-shrinkwrap.json.ember-try
/package.json.ember-try
+/package-lock.json.ember-try
+/yarn.lock.ember-try
# vs code JavaScript config file
jsconfig.json
@@ -31,3 +36,7 @@ jsconfig.json
# IDEs folders
.idea
.history
+
+
+# broccoli-debug
+/DEBUG/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000..9221655522
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,21 @@
+# unconventional js
+/blueprints/*/files/
+/vendor/
+
+# compiled output
+/dist/
+/tmp/
+
+# dependencies
+/bower_components/
+/node_modules/
+
+# misc
+/coverage/
+!.*
+.eslintcache
+
+# ember-try
+/.node_modules.ember-try/
+/bower.json.ember-try
+/package.json.ember-try
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000000..e5f7b6d1ee
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,12 @@
+'use strict';
+
+module.exports = {
+ overrides: [
+ {
+ files: '*.{js,ts}',
+ options: {
+ singleQuote: true,
+ },
+ },
+ ],
+};
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000000..379a703682
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,32 @@
+require: rubocop-performance
+
+Documentation:
+ Enabled: false
+Metrics/ClassLength:
+ Enabled: false
+Style/ClassAndModuleChildren:
+ Enabled: false
+Metrics/LineLength:
+ Enabled: false
+Metrics/MethodLength:
+ Max: 40
+Style/AsciiComments:
+ Enabled: false
+Metrics/AbcSize:
+ Enabled: false
+Style/GuardClause:
+ Enabled: false
+Style/FormatStringToken:
+ Enabled: false
+Lint/AssignmentInCondition:
+ Enabled: false
+Style/IfUnlessModifier:
+ Enabled: false
+Naming/MemoizedInstanceVariableName:
+ EnforcedStyleForLeadingUnderscores: required
+Style/MultilineBlockChain:
+ Enabled: false
+Lint/ConstantDefinitionInBlock:
+ Enabled: false
+Naming/VariableNumber:
+ Enabled: false
diff --git a/.ruby-version b/.ruby-version
index 8e8299dcc0..be94e6f53d 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.4.2
+3.2.2
diff --git a/.stylelintignore b/.stylelintignore
new file mode 100644
index 0000000000..a0cf71cbd1
--- /dev/null
+++ b/.stylelintignore
@@ -0,0 +1,8 @@
+# unconventional files
+/blueprints/*/files/
+
+# compiled output
+/dist/
+
+# addons
+/.node_modules.ember-try/
diff --git a/.stylelintrc.js b/.stylelintrc.js
new file mode 100644
index 0000000000..021c539ad0
--- /dev/null
+++ b/.stylelintrc.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
+};
diff --git a/.template-lintrc.js b/.template-lintrc.js
index b45e96ffdd..f35f61c7b3 100644
--- a/.template-lintrc.js
+++ b/.template-lintrc.js
@@ -1,5 +1,5 @@
'use strict';
module.exports = {
- extends: 'recommended'
+ extends: 'recommended',
};
diff --git a/.travis.yml b/.travis.yml
index e63d64adc3..0fec4027e4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,25 +1,29 @@
---
language: node_js
-node_js: 10
+
+node_js:
+ - "18"
env:
global:
- PATH=/snap/bin:$PATH
- PERCY_ENABLE=0
- JOBS=1
+ - NODE_OPTIONS=--no-experimental-fetch
# See https://git.io/vdao3 for details.
os: linux
-dist: xenial
+dist: focal
addons:
- chrome: stable
cache:
npm: true
+ directories:
+ - $HOME/.npm
before_install:
- - npm config set spin false
- npm install -g greenkeeper-lockfile@1
install:
@@ -47,7 +51,7 @@ jobs:
env: TRY_CONFIG=ember-beta
- if: type = cron
env: TRY_CONFIG=ember-data-beta
- - node_js: 10
+ - node_js: 18
- stage: ":ship: it to quay.io"
before_install: skip
install: skip
@@ -58,3 +62,4 @@ jobs:
allow_failures:
- env: TRY_CONFIG=ember-beta
- env: TRY_CONFIG=ember-data-beta
+
diff --git a/Dockerfile b/Dockerfile
index 47853d60e2..888a4a3203 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.4
+FROM ruby:3.2.2
LABEL maintainer Travis CI GmbH
@@ -6,7 +6,8 @@ RUN groupadd --gid 1000 node \
&& useradd --uid 1000 --gid node --shell /bin/bash --create-home node
ENV NPM_CONFIG_LOGLEVEL info
-ENV NODE_VERSION 10.7.0
+ENV NODE_VERSION 18.17.1
+ENV NODE_OPTIONS --no-experimental-fetch
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
@@ -37,7 +38,7 @@ RUN npm install --silent -g ember-cli
COPY . /app
-RUN npm ci --silent
+RUN npm ci --silent --force
RUN ember build --environment=production
RUN cp -a public/* dist/
diff --git a/Gemfile b/Gemfile
index 310f476c44..721522ec7e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,28 +1,35 @@
-ruby "~> 2.4.2"
+# frozen_string_literal: true
+
+ruby '~> 3.2.2'
source 'https://rubygems.org'
-gem 'travis-web', path: 'waiter'
-gem 'puma', '~> 3.12.4'
-gem 'rack-ssl', '~> 1.4'
-gem 'rack-protection', '~> 1.4'
-gem 'rack-mobile-detect'
-gem 'sinatra'
gem 'hashr'
+gem 'puma', '~> 6'
+gem 'rack-mobile-detect'
+gem 'rack-protection', '~> 3.0'
+gem 'rack-ssl', '~> 1.4'
gem 'sanitize'
+gem 'sinatra'
+gem 'travis-web', path: 'waiter'
group :development, :test do
gem 'rake'
end
-
group :development do
# gem 'debugger'
gem 'foreman'
+ gem 'rubocop'
+ gem 'rubocop-performance'
+ gem 'rubocop-rspec'
+ gem 'simplecov'
+ gem 'simplecov-console'
end
group :test do
- gem 'rspec', '~> 2.11'
- gem 'test-unit'
+ gem 'rspec', '~> 3.12'
+ gem 'rack-test'
gem 'sinatra-contrib'
+ gem 'test-unit'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index ca0403923c..f6ef837989 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -6,78 +6,138 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- backports (3.6.8)
+ ansi (1.5.0)
+ ast (2.4.2)
crass (1.0.6)
- diff-lcs (1.2.5)
- foreman (0.82.0)
- thor (~> 0.19.1)
- hashr (2.0.0)
- mini_portile2 (2.4.0)
- multi_json (1.12.1)
- nokogiri (1.10.9)
- mini_portile2 (~> 2.4.0)
- nokogumbo (2.0.2)
- nokogiri (~> 1.8, >= 1.8.4)
- power_assert (0.4.1)
- puma (3.12.6)
- rack (1.6.12)
+ diff-lcs (1.5.0)
+ docile (1.4.0)
+ foreman (0.87.2)
+ hashr (2.0.1)
+ json (2.6.3)
+ language_server-protocol (3.17.0.3)
+ multi_json (1.15.0)
+ mustermann (3.0.0)
+ ruby2_keywords (~> 0.0.1)
+ nio4r (2.5.9)
+ nokogiri (1.15.2-x86_64-linux)
+ racc (~> 1.4)
+ parallel (1.23.0)
+ parser (3.2.2.3)
+ ast (~> 2.4.1)
+ racc
+ power_assert (2.0.3)
+ puma (6.3.0)
+ nio4r (~> 2.0)
+ racc (1.7.1)
+ rack (2.2.7)
rack-mobile-detect (0.4.0)
rack
- rack-protection (1.5.5)
+ rack-protection (3.0.6)
rack
rack-ssl (1.4.1)
rack
- rack-test (0.6.3)
- rack (>= 1.0)
- rake (12.3.3)
- rspec (2.99.0)
- rspec-core (~> 2.99.0)
- rspec-expectations (~> 2.99.0)
- rspec-mocks (~> 2.99.0)
- rspec-core (2.99.2)
- rspec-expectations (2.99.2)
- diff-lcs (>= 1.1.3, < 2.0)
- rspec-mocks (2.99.4)
- sanitize (5.2.1)
+ rack-test (2.1.0)
+ rack (>= 1.3)
+ rainbow (3.1.1)
+ rake (13.0.6)
+ regexp_parser (2.8.1)
+ rexml (3.2.5)
+ rspec (3.12.0)
+ rspec-core (~> 3.12.0)
+ rspec-expectations (~> 3.12.0)
+ rspec-mocks (~> 3.12.0)
+ rspec-core (3.12.2)
+ rspec-support (~> 3.12.0)
+ rspec-expectations (3.12.3)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-mocks (3.12.5)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-support (3.12.1)
+ rubocop (1.54.0)
+ json (~> 2.3)
+ language_server-protocol (>= 3.17.0)
+ parallel (~> 1.10)
+ parser (>= 3.2.2.3)
+ rainbow (>= 2.2.2, < 4.0)
+ regexp_parser (>= 1.8, < 3.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.28.0, < 2.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (>= 2.4.0, < 3.0)
+ rubocop-ast (1.29.0)
+ parser (>= 3.2.1.0)
+ rubocop-capybara (2.18.0)
+ rubocop (~> 1.41)
+ rubocop-factory_bot (2.23.1)
+ rubocop (~> 1.33)
+ rubocop-performance (1.18.0)
+ rubocop (>= 1.7.0, < 2.0)
+ rubocop-ast (>= 0.4.0)
+ rubocop-rspec (2.22.0)
+ rubocop (~> 1.33)
+ rubocop-capybara (~> 2.17)
+ rubocop-factory_bot (~> 2.22)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ sanitize (6.0.1)
crass (~> 1.0.2)
- nokogiri (>= 1.8.0)
- nokogumbo (~> 2.0)
- sinatra (1.4.8)
- rack (~> 1.5)
- rack-protection (~> 1.4)
- tilt (>= 1.3, < 3)
- sinatra-contrib (1.4.7)
- backports (>= 2.0)
+ nokogiri (>= 1.12.0)
+ simplecov (0.22.0)
+ docile (~> 1.1)
+ simplecov-html (~> 0.11)
+ simplecov_json_formatter (~> 0.1)
+ simplecov-console (0.9.1)
+ ansi
+ simplecov
+ terminal-table
+ simplecov-html (0.12.3)
+ simplecov_json_formatter (0.1.4)
+ sinatra (3.0.6)
+ mustermann (~> 3.0)
+ rack (~> 2.2, >= 2.2.4)
+ rack-protection (= 3.0.6)
+ tilt (~> 2.0)
+ sinatra-contrib (3.0.6)
multi_json
- rack-protection
- rack-test
- sinatra (~> 1.4.0)
- tilt (>= 1.3, < 3)
- test-unit (3.2.3)
+ mustermann (~> 3.0)
+ rack-protection (= 3.0.6)
+ sinatra (= 3.0.6)
+ tilt (~> 2.0)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ test-unit (3.6.1)
power_assert
- thor (0.19.1)
- tilt (2.0.8)
+ tilt (2.2.0)
+ unicode-display_width (2.4.2)
PLATFORMS
- ruby
+ x86_64-linux
DEPENDENCIES
foreman
hashr
- puma (~> 3.12.4)
+ puma (~> 6)
rack-mobile-detect
- rack-protection (~> 1.4)
+ rack-protection (~> 3.0)
rack-ssl (~> 1.4)
+ rack-test
rake
- rspec (~> 2.11)
+ rspec (~> 3.12)
+ rubocop
+ rubocop-performance
+ rubocop-rspec
sanitize
+ simplecov
+ simplecov-console
sinatra
sinatra-contrib
test-unit
travis-web!
RUBY VERSION
- ruby 2.4.2p198
+ ruby 3.2.2p53
BUNDLED WITH
- 1.17.3
+ 2.4.14
diff --git a/MODULE_REPORT.md b/MODULE_REPORT.md
index 11f809a925..d5e8ccb61f 100644
--- a/MODULE_REPORT.md
+++ b/MODULE_REPORT.md
@@ -17,11 +17,11 @@ if (!Ember.testing) {
**Global**: `Ember.Handlebars`
-**Location**: `app/helpers/github-commit-link.js` at line 7
+**Location**: `app/helpers/commit-link.js` at line 7
```js
+import formatCommit from 'travis/utils/format-commit';
-import Ember from 'ember';
const { escapeExpression: escape } = Ember.Handlebars.Utils;
export default Helper.extend({
@@ -31,7 +31,7 @@ export default Helper.extend({
**Global**: `Ember.testing`
-**Location**: `app/controllers/build.js` at line 32
+**Location**: `app/controllers/build.js` at line 31
```js
init() {
@@ -59,7 +59,7 @@ export default Helper.extend({
**Global**: `Ember.testing`
-**Location**: `app/controllers/repo.js` at line 63
+**Location**: `app/controllers/repo.js` at line 66
```js
init() {
@@ -78,7 +78,7 @@ export default Helper.extend({
```js
// Acceptance tests will wait for the promise to resolve, so skip in tests
- if (this.get('isOpen') && !Ember.testing) {
+ if (this.isOpen && !Ember.testing) {
yield new EmberPromise(resolve => later(resolve, 10000));
```
@@ -87,7 +87,7 @@ export default Helper.extend({
**Global**: `Ember.Handlebars`
-**Location**: `app/components/build-message.js` at line 7
+**Location**: `app/components/build-message.js` at line 10
```js
import Ember from 'ember';
@@ -101,13 +101,13 @@ export default Component.extend({
**Global**: `Ember.testing`
-**Location**: `app/components/dashboard-row.js` at line 26
+**Location**: `app/components/queued-jobs.js` at line 16
```js
- this.toggleProperty('dropupIsOpen');
-
+ init() {
+ this._super(...arguments);
if (!Ember.testing) {
- later((() => { this.set('dropupIsOpen', false); }), 4000);
+ return Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
}
```
@@ -115,21 +115,21 @@ export default Component.extend({
**Global**: `Ember.testing`
-**Location**: `app/components/queued-jobs.js` at line 13
+**Location**: `app/components/repository-sidebar.js` at line 45
```js
- init() {
- this._super(...arguments);
- if (!Ember.testing) {
- return Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
}
+
+ if (!Ember.testing) {
+ Visibility.every(config.intervals.updateTimes, () => {
+ const callback = (record) => record.get('currentBuild');
```
### Unknown Global
**Global**: `Ember.testing`
-**Location**: `app/components/running-jobs.js` at line 18
+**Location**: `app/components/running-jobs.js` at line 16
```js
init() {
@@ -143,49 +143,21 @@ export default Component.extend({
**Global**: `Ember.testing`
-**Location**: `app/components/repository-sidebar.js` at line 46
-
-```js
- }
-
- if (!Ember.testing) {
- Visibility.every(config.intervals.updateTimes, () => {
- const callback = (record) => record.get('currentBuild');
-```
-
-### Unknown Global
-
-**Global**: `Ember.testing`
-
-**Location**: `app/components/top-bar.js` at line 38
+**Location**: `app/components/top-bar.js` at line 82
```js
didInsertElement() {
if (Ember.testing) {
- this._super(...arguments);
+ super.didInsertElement(...arguments);
return;
```
### Unknown Global
-**Global**: `Ember.testing`
-
-**Location**: `app/controllers/dashboard/builds.js` at line 12
-
-```js
- init() {
- this._super(...arguments);
- if (!Ember.testing) {
- Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
- }
-```
-
-### Unknown Global
-
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 14
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 16
```js
@@ -199,7 +171,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 15
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 17
```js
hooks.beforeEach(function () {
@@ -213,7 +185,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 16
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 18
```js
adapterException = Ember.Test.adapter.exception;
@@ -227,7 +199,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 17
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 19
```js
loggerError = Ember.Logger.error;
@@ -241,7 +213,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 21
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 23
```js
@@ -255,7 +227,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 22
+**Location**: `tests/acceptance/job/invalid-log-test.js` at line 24
```js
hooks.afterEach(function () {
@@ -269,7 +241,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 22
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 24
```js
// Ignore promise rejection.
@@ -283,7 +255,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 23
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 25
```js
// Original exception will fail test on promise rejection.
@@ -297,7 +269,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 24
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 26
```js
adapterException = Ember.Test.adapter.exception;
@@ -311,7 +283,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 25
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 27
```js
loggerError = Ember.Logger.error;
@@ -325,7 +297,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 29
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 31
```js
@@ -339,7 +311,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/owner/not-found-test.js` at line 30
+**Location**: `tests/acceptance/owner/not-found-test.js` at line 32
```js
hooks.afterEach(function () {
@@ -353,27 +325,27 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 14
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 21
```js
-
- hooks.beforeEach(function () {
+ // Ignore promise rejection.
+ // Original exception will fail test on promise rejection.
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => {};
+ Ember.Test.adapter.exception = () => null;
```
### Unknown Global
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 15
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 22
```js
- hooks.beforeEach(function () {
+ // Original exception will fail test on promise rejection.
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => {};
+ Ember.Test.adapter.exception = () => null;
Ember.Logger.error = () => null;
```
@@ -381,12 +353,12 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 16
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 23
```js
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => {};
+ Ember.Test.adapter.exception = () => null;
Ember.Logger.error = () => null;
});
```
@@ -395,11 +367,11 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 17
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 24
```js
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => {};
+ Ember.Test.adapter.exception = () => null;
Ember.Logger.error = () => null;
});
@@ -409,7 +381,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 21
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 28
```js
@@ -423,7 +395,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/job/invalid-log-test.js` at line 22
+**Location**: `tests/acceptance/repo/not-found-test.js` at line 29
```js
hooks.afterEach(function () {
@@ -435,29 +407,71 @@ export default Component.extend({
### Unknown Global
+**Global**: `Ember.testing`
+
+**Location**: `app/services/animation.js` at line 6
+
+```js
+import fade from 'ember-animated/transitions/fade';
+
+const isTest = Ember.testing;
+
+export const DURATION_NAMES = {
+```
+
+### Unknown Global
+
+**Global**: `Ember.testing`
+
+**Location**: `app/services/animation.js` at line 6
+
+```js
+import fade from 'ember-animated/transitions/fade';
+
+const isTest = Ember.testing;
+
+export const DURATION_NAMES = {
+```
+
+### Unknown Global
+
+**Global**: `Ember.testing`
+
+**Location**: `app/controllers/dashboard/builds.js` at line 12
+
+```js
+ init() {
+ this._super(...arguments);
+ if (!Ember.testing) {
+ Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
+ }
+```
+
+### Unknown Global
+
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 19
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 16
```js
- // Ignore promise rejection.
- // Original exception will fail test on promise rejection.
+
+ hooks.beforeEach(function () {
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => null;
+ Ember.Test.adapter.exception = () => {};
```
### Unknown Global
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 20
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 17
```js
- // Original exception will fail test on promise rejection.
+ hooks.beforeEach(function () {
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => null;
+ Ember.Test.adapter.exception = () => {};
Ember.Logger.error = () => null;
```
@@ -465,12 +479,12 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 21
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 18
```js
adapterException = Ember.Test.adapter.exception;
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => null;
+ Ember.Test.adapter.exception = () => {};
Ember.Logger.error = () => null;
});
```
@@ -479,11 +493,11 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 22
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 19
```js
loggerError = Ember.Logger.error;
- Ember.Test.adapter.exception = () => null;
+ Ember.Test.adapter.exception = () => {};
Ember.Logger.error = () => null;
});
@@ -493,7 +507,7 @@ export default Component.extend({
**Global**: `Ember.Test`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 26
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 23
```js
@@ -507,7 +521,7 @@ export default Component.extend({
**Global**: `Ember.Logger`
-**Location**: `tests/acceptance/repo/not-found-test.js` at line 27
+**Location**: `tests/acceptance/builds/invalid-build-test.js` at line 24
```js
hooks.afterEach(function () {
diff --git a/README.md b/README.md
index 5b0627469c..50b0f59c0d 100644
--- a/README.md
+++ b/README.md
@@ -90,9 +90,8 @@ You can also start an interactive test runner for easier development:
### Linting
-* `npm run lint:hbs`
-* `npm run lint:js`
-* `npm run lint:js -- --fix`
+* `npm run lint`
+* `npm run lint:fix`
### Feature Flags
diff --git a/app/.eslintrc.js b/app/.eslintrc.js
index 0fc7e8c190..2b8da3fd31 100644
--- a/app/.eslintrc.js
+++ b/app/.eslintrc.js
@@ -6,7 +6,7 @@ module.exports = {
ecmaVersion: 6,
sourceType: 'module'
},
- parser: 'babel-eslint',
+ parser: '@babel/eslint-parser',
extends: 'eslint:recommended',
env: {
'browser': true,
diff --git a/app/adapters/build.js b/app/adapters/build.js
index 77d7719318..9cd44c79d7 100644
--- a/app/adapters/build.js
+++ b/app/adapters/build.js
@@ -4,7 +4,7 @@ import Ember from 'ember';
let includes = 'build.commit,build.branch,build.request,build.created_by';
// TODO this is a workaround for an infinite loop in Mirage serialising 😞
-if (!Ember.testing) {
+if (Ember.testing) {
includes += ',build.repository';
}
diff --git a/app/adapters/v3.js b/app/adapters/v3.js
index b5951a79b8..15ad7ad0b7 100644
--- a/app/adapters/v3.js
+++ b/app/adapters/v3.js
@@ -1,8 +1,7 @@
-import { assign } from '@ember/polyfills';
import { underscore } from '@ember/string';
import { pluralize } from 'ember-inflector';
import config from 'travis/config/environment';
-import RESTAdapter from 'ember-data/adapters/rest';
+import RESTAdapter from '@ember-data/adapter/rest';
import { inject as service } from '@ember/service';
export default RESTAdapter.extend({
@@ -22,7 +21,7 @@ export default RESTAdapter.extend({
ajaxOptions: function (url, type = 'GET', options) {
options = options || {};
options.data = options.data || {};
- options.data = assign({}, options.data); // clone
+ options.data = Object.assign({}, options.data); // clone
for (let key in options.data) {
let value = options.data[key];
diff --git a/app/app.js b/app/app.js
index 51e7680014..c6a4432e26 100644
--- a/app/app.js
+++ b/app/app.js
@@ -1,10 +1,9 @@
/* global Travis */
import Evented from '@ember/object/evented';
-
import Application from '@ember/application';
import Resolver from './resolver';
import loadInitializers from 'ember-load-initializers';
-import config from './config/environment';
+import config from 'travis/config/environment';
// This can be set per environment in config/environment.js
const debuggingEnabled = config.featureFlags['debug-logging'];
diff --git a/app/components/add-cron-job.js b/app/components/add-cron-job.js
index 3a320487d5..724ddc5b53 100644
--- a/app/components/add-cron-job.js
+++ b/app/components/add-cron-job.js
@@ -5,6 +5,7 @@ import BranchSearching from 'travis/mixins/branch-searching';
export default Component.extend(BranchSearching, {
store: service(),
+ flashes: service(),
classNames: ['form--cron'],
@@ -41,7 +42,7 @@ export default Component.extend(BranchSearching, {
const cron = this.store.createRecord('cron', {
branch: this.selectedBranch,
interval: this.selectedInterval.toLowerCase(),
- dont_run_if_recent_build_exists: this.selectedOption.value
+ dont_run_if_recent_build_exists: this.selectedOption ? this.selectedOption.value : null
});
try {
yield cron.save();
diff --git a/app/components/add-env-var.js b/app/components/add-env-var.js
index d423a2340a..f656c6ca53 100644
--- a/app/components/add-env-var.js
+++ b/app/components/add-env-var.js
@@ -32,8 +32,8 @@ export default Component.extend(BranchSearching, {
save: task(function* () {
const envVar = this.store.createRecord('env_var', {
- name: this.name.trim(),
- value: this.value.trim(),
+ name: (this.name || "").trim(),
+ value: (this.value || "").trim(),
'public': this.public,
repo: this.repo,
branch: this.branch
diff --git a/app/components/billing-manual.js b/app/components/billing-manual.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/billing-manual.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/billing-summary-status.js b/app/components/billing-summary-status.js
index 827d1ebb7e..2e236941a9 100644
--- a/app/components/billing-summary-status.js
+++ b/app/components/billing-summary-status.js
@@ -5,12 +5,12 @@ import { isEmpty } from '@ember/utils';
export default Component.extend({
account: null,
- subcription: null,
+ subscription: null,
trial: reads('account.trial'),
isGithubTrial: and('subscription.isGithub', 'trial.hasActiveTrial'),
hasGithubTrialEnded: and('subscription.isGithub', 'trial.isEnded'),
noSubscription: empty('subscription'),
isDefaultEducationView: computed('subscription', 'account.education', 'subscription.plan_name', function () {
- return this.get('subscription') && !isEmpty(this.get('subscription')) && this.get('account.education');
- })
+ return this.subscription && !isEmpty(this.subscription) && this.get('account.education');
+ }),
});
diff --git a/app/components/billing/account.js b/app/components/billing/account.js
index ba11a7c456..b68e927bf6 100644
--- a/app/components/billing/account.js
+++ b/app/components/billing/account.js
@@ -1,47 +1,57 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
-import { computed } from '@ember/object';
+import { computed, action } from '@ember/object';
import { reads, empty, bool, not, and, or } from '@ember/object/computed';
+import {tracked} from "@glimmer/tracking";
-export default Component.extend({
- store: service(),
- accounts: service(),
+export default class MyComponent extends Component {
+ @service store;
+ @service accounts;
- account: null,
+ @tracked account = null;
- subscription: reads('account.subscription'),
- v2subscription: reads('account.v2subscription'),
- isV2SubscriptionEmpty: empty('v2subscription'),
- isSubscriptionEmpty: empty('subscription'),
- isSubscriptionsEmpty: and('isSubscriptionEmpty', 'isV2SubscriptionEmpty'),
- hasV2Subscription: not('isV2SubscriptionEmpty'),
- trial: reads('account.trial'),
- isEducationalAccount: bool('account.education'),
- isNotEducationalAccount: not('isEducationalAccount'),
+ @reads('account.subscription') subscription;
+ @reads('account.v2subscription') v2subscription;
+ @empty('v2subscription') isV2SubscriptionEmpty;
+ @empty('subscription') isSubscriptionEmpty;
+ @and('isSubscriptionEmpty', 'isV2SubscriptionEmpty') isSubscriptionsEmpty;
+ @not('isV2SubscriptionEmpty') hasV2Subscription;
+ @reads('account.trial') trial;
+ @bool('account.education') isEducationalAccount;
+ @not('isEducationalAccount') isNotEducationalAccount;
- isTrial: and('isSubscriptionsEmpty', 'isNotEducationalAccount'),
- isManual: bool('subscription.isManual'),
- isManaged: bool('subscription.managedSubscription'),
- isEducation: and('isSubscriptionsEmpty', 'isEducationalAccount'),
- isSubscription: computed('isManaged', 'hasV2Subscription', 'isTrialProcessCompleted', 'isEduProcessCompleted', function () {
+ @and('isSubscriptionsEmpty', 'isNotEducationalAccount') isTrial;
+ @bool('subscription.isManual') isManual;
+ @bool('subscription.managedSubscription') isManaged;
+ @and('isSubscriptionsEmpty', 'isEducationalAccount') isEducation;
+
+ @computed('isManaged', 'hasV2Subscription', 'isTrialProcessCompleted', 'isEduProcessCompleted')
+ get isSubscription() {
return (this.isManaged || this.hasV2Subscription) && this.isTrialProcessCompleted && this.isEduProcessCompleted;
- }),
- showInvoices: computed('showPlansSelector', 'showAddonsSelector', function () {
+ }
+
+ @computed('showPlansSelector', 'showAddonsSelector')
+ get showInvoices() {
return !this.showPlansSelector && !this.showAddonsSelector && this.invoices;
- }),
+ }
- isLoading: or('accounts.fetchSubscriptions.isRunning', 'accounts.fetchV2Subscriptions.isRunning'),
+ @or('accounts.fetchSubscriptions.isRunning', 'accounts.fetchV2Subscriptions.isRunning') isLoading;
- showPlansSelector: false,
- showAddonsSelector: false,
- isTrialProcessCompleted: computed(function () {
+ showPlansSelector = false;
+ showAddonsSelector = false;
+
+ @computed('isTrial')
+ get isTrialProcessCompleted() {
return !this.isTrial;
- }),
- isEduProcessCompleted: computed(function () {
+ }
+
+ @computed('isEducation')
+ get isEduProcessCompleted() {
return !this.isEducation;
- }),
+ }
- newV2Subscription: computed(function () {
+ @computed('store')
+ get newV2Subscription() {
const plan = this.store.createRecord('v2-plan-config');
const billingInfo = this.store.createRecord('v2-billing-info');
const creditCardInfo = this.store.createRecord('v2-credit-card-info');
@@ -63,9 +73,10 @@ export default Component.extend({
plan,
creditCardInfo,
});
- }),
+ }
- invoices: computed('subscription.id', 'v2subscription.id', function () {
+ @computed('subscription.id', 'v2subscription.id')
+ get invoices() {
const subscriptionId = this.isV2SubscriptionEmpty ? this.get('subscription.id') : this.get('v2subscription.id');
const type = this.isV2SubscriptionEmpty ? 1 : 2;
if (subscriptionId) {
@@ -73,5 +84,5 @@ export default Component.extend({
} else {
return [];
}
- }),
-});
+ }
+}
diff --git a/app/components/billing/address.js b/app/components/billing/address.js
index ad663ce6ae..26b1ea0c15 100644
--- a/app/components/billing/address.js
+++ b/app/components/billing/address.js
@@ -2,7 +2,10 @@ import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
import { observer } from '@ember/object';
-import { countries, nonZeroVatThresholdCountries } from 'travis/utils/countries';
+import {
+ countries,
+ nonZeroVatThresholdCountries
+} from 'travis/utils/countries';
export default Component.extend({
countries,
diff --git a/app/components/billing/authorization.js b/app/components/billing/authorization.js
index 39519fd4e9..7dc933b210 100644
--- a/app/components/billing/authorization.js
+++ b/app/components/billing/authorization.js
@@ -23,7 +23,6 @@ export default Component.extend({
requiresSource: equal('subscription.paymentIntent.status', 'requires_source'),
lastPaymentIntentError: reads('subscription.paymentIntent.last_payment_error'),
retryAuthorizationClientSecret: reads('subscription.paymentIntent.client_secret'),
- hasSubscriptionPermissions: reads('account.hasSubscriptionPermissions'),
notChargeInvoiceSubscription: not('subscription.chargeUnpaidInvoices.lastSuccessful.value'),
freeV2Plan: equal('subscription.plan.startingPrice', 0),
isSubscribed: reads('subscription.isSubscribed'),
@@ -32,6 +31,10 @@ export default Component.extend({
canCancelSubscription: computed('isSubscribed', 'hasSubscriptionPermissions', 'freeV2Plan', 'isTrial', function () {
return this.isSubscribed && this.hasSubscriptionPermissions && !this.freeV2Plan && !this.isTrial;
}),
+
+ hasSubscriptionPermissions: computed('account.hasSubscriptionPermissions', 'account.permissions', function () {
+ return this.account.hasSubscriptionPermissions && (!this.account.isOrganization || this.account.permissions.plan_create);
+ }),
cancelSubscriptionLoading: reads('subscription.cancelSubscription.isRunning'),
isTrial: reads('subscription.plan.isTrial'),
isLoading: or('accounts.fetchSubscriptions.isRunning', 'accounts.fetchV2Subscriptions.isRunning',
@@ -67,7 +70,7 @@ export default Component.extend({
});
const { client_secret: clientSecret } = yield this.subscription.chargeUnpaidInvoices.perform();
yield this.stripe.handleStripePayment.perform(clientSecret);
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}
} catch (error) {
this.flashes.error('An error occurred when creating your subscription. Please try again.');
@@ -77,7 +80,7 @@ export default Component.extend({
editPlan: task(function* () {
yield this.subscription.changePlan.perform(this.selectedPlan.id);
yield this.accounts.fetchSubscriptions.perform();
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
yield this.retryAuthorization.perform();
}).drop(),
@@ -87,7 +90,7 @@ export default Component.extend({
yield this.stripe.handleStripePayment.perform(result.payment_intent.client_secret);
} else {
yield this.accounts.fetchSubscriptions.perform();
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}
}).drop(),
diff --git a/app/components/billing/billing-details.js b/app/components/billing/billing-details.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/billing/billing-details.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/billing/credits-calculator.js b/app/components/billing/credits-calculator.js
index 7ee9a6e69a..d250d16ee5 100644
--- a/app/components/billing/credits-calculator.js
+++ b/app/components/billing/credits-calculator.js
@@ -18,12 +18,12 @@ export default Component.extend({
selectPlan: null,
bestPlan: computed('totalCredits', 'plans.[]', function () {
- return this.get('plans').find(item => item.annual === this.get('isAnnual') && item.get('privateCredits') > this.get('totalCredits'));
+ return this.plans.find(item => item.annual === this.isAnnual && item.get('privateCredits') > this.totalCredits);
}),
totalPrice: computed('configurations.[]', function () {
let sum = 0;
- for (const configuration of this.get('configurations')) {
+ for (const configuration of this.configurations) {
sum += configuration.price;
}
@@ -32,7 +32,7 @@ export default Component.extend({
totalCredits: computed('configurations.[]', function () {
let sum = 0;
- for (const configuration of this.get('configurations')) {
+ for (const configuration of this.configurations) {
sum += configuration.credits;
}
@@ -44,7 +44,7 @@ export default Component.extend({
users: this.users,
executions: []
};
- for (const build of this.get('builds')) {
+ for (const build of this.builds) {
if (build.os.value !== undefined && parseInt(build.minutes) > 0) {
let execution = {
os: build.os.value,
@@ -65,7 +65,7 @@ export default Component.extend({
this.api.post('/credits_calculator', { data })
.then((result) => {
- this.get('configurations').clear();
+ this.configurations.clear();
for (const creditResult of result.credits_results) {
let config = {
credits: creditResult.credits,
@@ -97,7 +97,7 @@ export default Component.extend({
}
}
- this.get('configurations').pushObject(config);
+ this.configurations.pushObject(config);
}
});
},
@@ -121,23 +121,23 @@ export default Component.extend({
}
}
- this.get('builds').clear();
- this.get('builds').pushObject({ os: selectedOs, vmSize: selectedVmSize, minutes: result.minutes });
+ this.builds.clear();
+ this.builds.pushObject({ os: selectedOs, vmSize: selectedVmSize, minutes: result.minutes });
this.set('users', result.users);
this.calculate();
});
},
addBuild() {
- this.get('builds').pushObject({ os: {}, vmSize: {}, minutes: '' });
+ this.builds.pushObject({ os: {}, vmSize: {}, minutes: '' });
},
close() {
this.hideCalculator();
- this.get('builds').clear();
+ this.builds.clear();
this.addBuild();
this.set('users', '');
- this.get('configurations').clear();
+ this.configurations.clear();
},
actions: {
@@ -160,7 +160,7 @@ export default Component.extend({
},
selectPlan() {
- this.set('selectedPlan', this.get('bestPlan'));
+ this.set('selectedPlan', this.bestPlan);
this.close();
this.selectPlan(this.form);
},
diff --git a/app/components/billing/first-plan.js b/app/components/billing/first-plan.js
index b9d5e96fc2..e235807c23 100644
--- a/app/components/billing/first-plan.js
+++ b/app/components/billing/first-plan.js
@@ -4,7 +4,14 @@ import { inject as service } from '@ember/service';
import { not, reads, filterBy, alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import config from 'travis/config/environment';
-import { countries, states, zeroVatThresholdCountries, nonZeroVatThresholdCountries, stateCountries } from 'travis/utils/countries';
+import { A } from '@ember/array';
+import {
+ countries,
+ states,
+ zeroVatThresholdCountries,
+ nonZeroVatThresholdCountries,
+ stateCountries
+} from 'travis/utils/countries';
export default Component.extend({
stripe: service(),
@@ -36,7 +43,11 @@ export default Component.extend({
displayedPlans: reads('availablePlans'),
- selectedPlan: computed('displayedPlans.[].id', 'defaultPlanId', function () {
+ selectedPlanOverride: null,
+ selectedPlan: computed('selectedPlanOverride','displayedPlans.[].id', 'defaultPlanId', function () {
+ if (this.selectedPlanOverride !== null)
+ return this.selectedPlanOverride;
+
let plan = this.storage.selectedPlanId;
if (plan == null) {
plan = this.defaultPlanId;
@@ -168,7 +179,7 @@ export default Component.extend({
});
yield this.subscription.save();
yield this.subscription.changePlan.perform(selectedPlan.id, this.couponId);
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
yield this.retryAuthorization.perform();
}
this.metrics.trackEvent({ button: 'pay-button' });
@@ -176,13 +187,13 @@ export default Component.extend({
this.storage.clearSelectedPlanId();
this.storage.wizardStep = 2;
this.wizard.update.perform(2);
- yield this.accounts.fetchV2Subscriptions.perform().then(() => {
+ this.accounts.fetchV2Subscriptions.perform().then(() => {
this.router.transitionTo('/account/repositories');
});
}
this.flashes.success('Your account has been successfully activated');
} catch (error) {
- yield this.accounts.fetchV2Subscriptions.perform().then(() => {
+ this.accounts.fetchV2Subscriptions.perform().then(() => {
if (this.accounts.user.subscription || this.accounts.user.v2subscription) {
this.storage.clearBillingData();
this.storage.clearSelectedPlanId();
diff --git a/app/components/billing/information.js b/app/components/billing/information.js
index 0992308027..eeaf639643 100644
--- a/app/components/billing/information.js
+++ b/app/components/billing/information.js
@@ -7,8 +7,9 @@ export default Component.extend({
billingInfo: reads('subscription.billingInfo'),
actions: {
- updateEmails(values) {
+ updateEmails(values)
+ {
this.billingInfo.set('billingEmail', values.join(','));
- },
+ }
}
});
diff --git a/app/components/billing/invoices.js b/app/components/billing/invoices.js
index fe56841df3..634a9135e5 100644
--- a/app/components/billing/invoices.js
+++ b/app/components/billing/invoices.js
@@ -1,10 +1,11 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
+import { A } from '@ember/array'
export default Component.extend({
- invoices: null,
+ invoices: A([]),
invoiceYears: computed('invoices.@each.createdAt', function () {
return this.invoices.mapBy('year').uniq().sort((a, b) => b - a);
diff --git a/app/components/billing/payment-details-tab.js b/app/components/billing/payment-details-tab.js
index 8128a620fb..76bac4c1e5 100644
--- a/app/components/billing/payment-details-tab.js
+++ b/app/components/billing/payment-details-tab.js
@@ -5,7 +5,13 @@ import { empty, not, reads, and } from '@ember/object/computed';
import { computed } from '@ember/object';
import config from 'travis/config/environment';
import { underscore } from '@ember/string';
-import { countries, states, stateCountries, nonZeroVatThresholdCountries, zeroVatThresholdCountries } from 'travis/utils/countries';
+import {
+ countries,
+ states,
+ stateCountries,
+ nonZeroVatThresholdCountries,
+ zeroVatThresholdCountries
+} from 'travis/utils/countries';
export default Component.extend({
api: service(),
@@ -15,6 +21,8 @@ export default Component.extend({
metrics: service(),
countries,
+
+ model: reads('activeModel'),
states: computed('country', function () {
const { country } = this;
@@ -26,7 +34,7 @@ export default Component.extend({
couponId: null,
options: computed('disableForm', function () {
let configStripe = config.stripeOptions;
- configStripe['disabled'] = this.get('disableForm');
+ configStripe['disabled'] = this.disableForm;
return configStripe;
}),
showSwitchToFreeModal: false,
@@ -37,9 +45,15 @@ export default Component.extend({
isV2SubscriptionEmpty: empty('v2subscription'),
isSubscriptionEmpty: empty('v1subscription'),
isSubscriptionsEmpty: and('isSubscriptionEmpty', 'isV2SubscriptionEmpty'),
+ canViewBilling: computed('account.isOrganization', 'account.permissions.billing_view', function () {
+ return !this.account.isOrganization || this.account.permissions.billing_view;
+ }),
+ canEditBilling: computed('account.isOrganization', 'account.permissions.billing_update', function () {
+ return !this.account.isOrganization || this.account.permissions.billing_update;
+ }),
hasV2Subscription: not('isV2SubscriptionEmpty'),
subscription: computed('v1subscription', 'v2subscription', function () {
- return this.isV2SubscriptionEmpty ? this.get('v1subscription') : this.get('v2subscription');
+ return this.isV2SubscriptionEmpty ? this.v1subscription : this.v2subscription;
}),
invoices: computed('v1subscription.id', 'v2subscription.id', function () {
const subscriptionId = this.isV2SubscriptionEmpty ? this.get('v1subscription.id') : this.get('v2subscription.id');
@@ -52,8 +66,8 @@ export default Component.extend({
}),
disableForm: computed('account.allowance.paymentChangesBlockCredit', 'account.allowance.paymentChangesBlockCaptcha', function () {
- const paymentChangesBlockCredit = this.account.allowance.get('paymentChangesBlockCredit');
- const paymentChangesBlockCaptcha = this.account.allowance.get('paymentChangesBlockCaptcha');
+ const paymentChangesBlockCredit = this.account.allowance.paymentChangesBlockCredit;
+ const paymentChangesBlockCaptcha = this.account.allowance.paymentChangesBlockCaptcha;
return paymentChangesBlockCaptcha || paymentChangesBlockCredit;
}),
diff --git a/app/components/billing/payment.js b/app/components/billing/payment.js
index 11cdd6035b..69c5b10eda 100644
--- a/app/components/billing/payment.js
+++ b/app/components/billing/payment.js
@@ -110,7 +110,7 @@ export default Component.extend({
v1SubscriptionId: this.v1SubscriptionId,
});
const { clientSecret } = yield subscription.save();
- yield this.stripe.handleStripePayment.perform(clientSecret);
+ this.stripe.handleStripePayment.linked().perform(clientSecret);
} else {
this.metrics.trackEvent({
action: 'Change Plan Pay Button Clicked',
@@ -119,12 +119,12 @@ export default Component.extend({
yield this.subscription.changePlan.perform(this.selectedPlan.id, this.couponId);
}
}
- yield this.accounts.fetchV2Subscriptions.perform();
- yield this.retryAuthorization.perform();
+ this.accounts.fetchV2Subscriptions.linked().perform();
+ yield this.retryAuthorization.linked().perform();
this.storage.clearBillingData();
this.set('showPlansSelector', false);
this.set('showAddonsSelector', false);
- this.set('isProcessCompleted', true);
+ this.set('hasV2Subscription', true);
}
}).drop(),
@@ -144,10 +144,10 @@ export default Component.extend({
v1SubscriptionId: this.v1SubscriptionId,
});
yield subscription.save();
- yield this.accounts.fetchV2Subscriptions.perform();
+ this.accounts.fetchV2Subscriptions.linked().perform();
this.storage.clearBillingData();
this.set('showPlansSelector', false);
- this.set('isProcessCompleted', true);
+ this.set('hasV2Subscription', true);
} catch (error) {
this.handleError(error);
}
@@ -180,7 +180,8 @@ export default Component.extend({
coupon: this.couponId
});
const { clientSecret } = yield subscription.save();
- yield this.stripe.handleStripePayment.perform(clientSecret);
+
+ this.stripe.handleStripePayment.linked().perform(clientSecret);
} else {
yield this.subscription.creditCardInfo.updateToken.perform({
subscriptionId: this.subscription.id,
@@ -189,13 +190,13 @@ export default Component.extend({
});
yield subscription.save();
yield subscription.changePlan.perform(selectedPlan.id, this.couponId);
- yield this.accounts.fetchV2Subscriptions.perform();
+ this.accounts.fetchV2Subscriptions.linked().perform();
yield this.retryAuthorization.perform();
}
this.metrics.trackEvent({ button: 'pay-button' });
this.storage.clearBillingData();
this.set('showPlansSelector', false);
- this.set('isProcessCompleted', true);
+ this.set('hasV2Subscription', true);
}
} catch (error) {
this.handleError(error);
@@ -225,7 +226,7 @@ export default Component.extend({
this.set('showSwitchToFreeModal', false);
this.storage.clearBillingData();
this.set('showPlansSelector', false);
- this.set('isProcessCompleted', true);
+ this.set('hasV2Subscription', true);
},
closePlanSwitchWarning: function () {
diff --git a/app/components/billing/postal-address.js b/app/components/billing/postal-address.js
index 0b44ba7f0b..2b1185f855 100644
--- a/app/components/billing/postal-address.js
+++ b/app/components/billing/postal-address.js
@@ -1,7 +1,13 @@
import Component from '@ember/component';
import { reads } from '@ember/object/computed';
import { computed } from '@ember/object';
-import { countries, states, zeroVatThresholdCountries, nonZeroVatThresholdCountries, stateCountries } from 'travis/utils/countries';
+import {
+ countries,
+ states,
+ zeroVatThresholdCountries,
+ nonZeroVatThresholdCountries,
+ stateCountries
+} from 'travis/utils/countries';
export default Component.extend({
countries,
diff --git a/app/components/billing/price-v2.js b/app/components/billing/price-v2.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/billing/price-v2.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/billing/process.js b/app/components/billing/process.js
index b2a3aba844..fbf844dcd9 100644
--- a/app/components/billing/process.js
+++ b/app/components/billing/process.js
@@ -19,7 +19,11 @@ export default Component.extend({
showCancelButton: false,
- currentStep: computed(function () {
+ currentStepOverride: null,
+
+ currentStep: computed('currentStepOverride', 'storage.billingStep', function () {
+ if (this.currentStepOverride !== null)
+ return this.currentStepOverride;
return this.storage.billingStep || STEPS.ONE;
}),
@@ -65,7 +69,7 @@ export default Component.extend({
actions: {
goToFirstStep() {
- this.set('currentStep', STEPS.ONE);
+ this.set('currentStepOverride', STEPS.ONE);
this.persistBillingData(STEPS.ONE);
this.updateBillingQueryParams(STEPS.ONE);
},
@@ -78,11 +82,11 @@ export default Component.extend({
const nextIndex = Math.min(lastIndex, currentIndex + 1);
if ((this.billingInfoExists && this.currentStep === STEPS.ONE) || this.selectedPlan.startingPrice === 0) {
const currentStep = STEPS.THREE;
- this.set('currentStep', currentStep);
+ this.set('currentStepOverride', currentStep);
this.set('billingInfo', this.existingBillingInfo);
} else {
const currentStep = this.steps[nextIndex];
- this.set('currentStep', currentStep);
+ this.set('currentStepOverride', currentStep);
}
this.updateBillingQueryParams(this.currentStep);
this.persistBillingData(this.currentStep);
@@ -93,13 +97,13 @@ export default Component.extend({
const currentIndex = this.steps.indexOf(this.currentStep);
const prevIndex = Math.max(0, currentIndex - 1);
const currentStep = this.steps[prevIndex];
- this.set('currentStep', currentStep);
+ this.set('currentStepOverride', currentStep);
this.updateBillingQueryParams(currentStep);
this.persistBillingData(currentStep);
},
cancel() {
- this.set('currentStep', STEPS.ONE);
+ this.set('currentStepOverride', STEPS.ONE);
this.updateBillingQueryParams(STEPS.ONE);
},
diff --git a/app/components/billing/select-plan.js b/app/components/billing/select-plan.js
index d12358cd82..0719157cde 100644
--- a/app/components/billing/select-plan.js
+++ b/app/components/billing/select-plan.js
@@ -4,6 +4,7 @@ import { task } from 'ember-concurrency';
import { computed } from '@ember/object';
import { later } from '@ember/runloop';
import { or, reads, filterBy } from '@ember/object/computed';
+import { A } from '@ember/array';
export default Component.extend({
accounts: service(),
@@ -20,7 +21,11 @@ export default Component.extend({
displayedPlans: reads('availablePlans'),
- selectedPlan: computed('displayedPlans.[].name', 'defaultPlanName', function () {
+ selectedPlanOverride: null,
+ selectedPlan: computed('selectedPlanOverride', 'displayedPlans.[].name', 'defaultPlanName', function () {
+ if (this.selectedPlanOverride !== null)
+ return this.selectedPlanOverride;
+
return this.displayedPlans.findBy('name', this.defaultPlanName);
}),
@@ -31,6 +36,9 @@ export default Component.extend({
return false;
}
}),
+ hasPlanChangePermission: computed('account', function () {
+ return !this.account.isOrganization || this.account.permissions.plan_create;
+ }),
save: task(function* () {
if (this.next.perform) {
@@ -41,13 +49,13 @@ export default Component.extend({
}).drop(),
reactivatePlan(plan, form) {
- this.set('selectedPlan', plan);
+ this.set('selectedPlanOverride', plan);
this.set('isReactivation', true);
later(form.submit, 500);
},
selectAndSubmit(plan, form) {
- this.set('selectedPlan', plan);
+ this.set('selectedPlanOverride', plan);
later(form.submit, 500);
},
@@ -78,6 +86,6 @@ export default Component.extend({
hideCalculator() {
this.set('showCalculator', false);
- }
+ },
}
});
diff --git a/app/components/billing/summary-v2.js b/app/components/billing/summary-v2.js
index d4d5579366..23dee3a5ac 100644
--- a/app/components/billing/summary-v2.js
+++ b/app/components/billing/summary-v2.js
@@ -13,7 +13,12 @@ export default Component.extend({
isIncomplete: reads('subscription.isIncomplete'),
isComplete: not('isIncomplete'),
authenticationNotRequired: not('subscription.clientSecret'),
- isPending: and('subscription.isPending', 'authenticationNotRequired'),
+ isPendingOverride: null,
+ isPending: computed('subscription.isPending', 'authenticationNotRequired', 'isPendingOverride', function() {
+ if (this.isPendingOverride !== null)
+ return this.isPendingOverride
+ return this.authenticationNotRequired && this.subscription.isPending;
+ }),
isNotCanceled: not('isCanceled'),
isNotPending: not('isPending'),
hasNotExpired: not('isExpired'),
diff --git a/app/components/billing/summary.js b/app/components/billing/summary.js
index cace58387c..bb4577bddd 100644
--- a/app/components/billing/summary.js
+++ b/app/components/billing/summary.js
@@ -9,13 +9,25 @@ export default Component.extend({
subscription: null,
account: null,
- selectedPlan: reads('subscription.plan'),
+ selectedPlanOverride: null,
+
+ selectedPlan: computed('selectedPlanOverride', 'subscription.plan', function () {
+ if (this.selectedPlanOverride !== null)
+ return this.selectedPlanOverride;
+
+ return this.subscription.plan
+ }),
isEditPlanLoading: reads('subscription.changePlan.isLoading'),
isIncomplete: reads('subscription.isIncomplete'),
isComplete: not('isIncomplete'),
authenticationNotRequired: not('subscription.clientSecret'),
- isPending: and('subscription.isPending', 'authenticationNotRequired'),
+ isPendingOverride: null,
+ isPending: computed('subscription.isPending', 'authenticationNotRequired', 'isPendingOverride', function() {
+ if (this.isPendingOverride !== null)
+ return this.isPendingOverride
+ return this.authenticationNotRequired && this.subscription.isPending;
+ }),
isNotCanceled: not('isCanceled'),
isNotPending: not('isPending'),
hasNotExpired: not('isExpired'),
diff --git a/app/components/billing/user-usage.js b/app/components/billing/user-usage.js
index 79fbf98c42..2585cf3f9f 100644
--- a/app/components/billing/user-usage.js
+++ b/app/components/billing/user-usage.js
@@ -13,7 +13,7 @@ export default Component.extend({
usersUsageReceived: reads('account.allowance.isFulfilled'),
usersUsageRejected: reads('account.allowance.isRejected'),
usersUsage: computed('account.allowance.userUsage', 'addonUsage', function () {
- const userUsage = this.get('account').get('allowance').get('userUsage');
+ const userUsage = this.account?.allowance?.userUsage;
if (userUsage === undefined) {
return true;
}
diff --git a/app/components/branch-row.js b/app/components/branch-row.js
index 804dd211c5..e714de4ee1 100644
--- a/app/components/branch-row.js
+++ b/app/components/branch-row.js
@@ -22,18 +22,22 @@ export default Component.extend({
commitUrl: computed('branch.repository.slug', 'branch.last_build.commit.sha', 'vcsType', function () {
const [owner, repo] = this.get('branch.repository.slug').split('/');
- const vcsType = this.get('vcsType');
+ const vcsType = this.vcsType;
const commit = this.get('branch.last_build.commit.sha');
return this.externalLinks.commitUrl(vcsType, { owner, repo, commit });
}),
+ vcsTypeOverride: null,
vcsType: computed('branch.repository.id', function () {
+ if (this.vcsTypeOverride)
+ return this.vcsTypeOverride;
+
const repository = this.store.peekRecord('repo', this.get('branch.repository.id'));
return repository.vcsType;
}),
provider: computed('vcsType', function () {
- return this.get('vcsType') && this.get('vcsType').toLowerCase().replace('repository', '');
+ return this.vcsType && this.vcsType.toLowerCase().replace('repository', '');
}),
rawCreatedBy: alias('branch.last_build.created_by'),
@@ -108,7 +112,7 @@ export default Component.extend({
}
run(() => {
- lastBuilds.set('count', response['@pagination'].count);
+ lastBuilds.set('count', response['@pagination']?.count || 0);
lastBuilds.set('content', array);
lastBuilds.set('isLoading', false);
});
diff --git a/app/components/build-header.js b/app/components/build-header.js
index 59d72ba1f7..3dc5f03270 100644
--- a/app/components/build-header.js
+++ b/app/components/build-header.js
@@ -4,6 +4,7 @@ import jobConfigArch from 'travis/utils/job-config-arch';
import jobConfigLanguage from 'travis/utils/job-config-language';
import { reads, not } from '@ember/object/computed';
import { inject as service } from '@ember/service';
+import { capitalize } from '@ember/string';
const commitMessageLimit = 72;
@@ -110,7 +111,7 @@ export default Component.extend({
if (serverType === 'svn') {
return 'SVN';
} else {
- return serverType.capitalize();
+ return capitalize(serverType);
}
}),
diff --git a/app/components/build-message.js b/app/components/build-message.js
index bc9175e716..55b417b8e9 100644
--- a/app/components/build-message.js
+++ b/app/components/build-message.js
@@ -2,12 +2,10 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { and, notEmpty } from '@ember/object/computed';
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { typeOf } from '@ember/utils';
import { codeblockName } from 'travis/utils/format-config';
-import Ember from 'ember';
-
-const { escapeExpression: escape } = Ember.Handlebars.Utils;
+import { escape } from 'travis/helpers/format-message';
export default Component.extend({
tagName: '',
@@ -16,7 +14,7 @@ export default Component.extend({
const { code, key, args } = this.message;
if (this[code]) {
- return htmlSafe(this[code](key, args));
+ return htmlSafe(`${this[code](key, args)}`);
} else {
return htmlSafe(`unrecognised message code ${format(code)}`);
}
diff --git a/app/components/build-messages-list.js b/app/components/build-messages-list.js
index 15e5da6633..3a39f49956 100644
--- a/app/components/build-messages-list.js
+++ b/app/components/build-messages-list.js
@@ -21,7 +21,7 @@ export default Component.extend(WithConfigValidation, {
messages: reads('request.messages'),
toggleStatusClass: computed('isExpanded', function () {
- return this.get('isExpanded') ? 'expanded' : 'collapsed';
+ return this.isExpanded ? 'expanded' : 'collapsed';
}),
sortedMessages: sort('request.messages', (lft, rgt) =>
@@ -33,11 +33,11 @@ export default Component.extend(WithConfigValidation, {
}),
iconClass: computed('maxLevel', function () {
- return `icon icon-${this.get('maxLevel')}`;
+ return `icon icon-${this.maxLevel}`;
}),
summary: computed('sortedMessages', function () {
- let counts = countBy(this.get('sortedMessages'), 'level');
+ let counts = countBy(this.sortedMessages, 'level');
if (Object.entries(counts).length > 0) {
return Object.entries(counts).map((entry) => formatLevel(...entry)).join(', ');
}
diff --git a/app/components/caches-item.js b/app/components/caches-item.js
index 9e421cd9f0..1864703798 100644
--- a/app/components/caches-item.js
+++ b/app/components/caches-item.js
@@ -10,6 +10,7 @@ export default Component.extend({
tagName: 'li',
classNames: ['cache-item'],
classNameBindings: ['cache.type'],
+ injectedFunction: null,
delete: task(function* () {
if (config.skipConfirmations || confirm('Are you sure?')) {
@@ -20,7 +21,8 @@ export default Component.extend({
try {
yield this.api.delete(url);
- this.caches.removeObject(this.cache);
+ const caches = this.caches.filter(item => item !== this.cache);
+ this.injectedFunction(caches, this.component);
} catch (e) {
this.flashes.error('Could not delete the cache');
}
diff --git a/app/components/dashboard-row.js b/app/components/dashboard-row.js
index 3d340d0549..0e6530a75c 100644
--- a/app/components/dashboard-row.js
+++ b/app/components/dashboard-row.js
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
import { alias, reads } from '@ember/object/computed';
import { task, timeout } from 'ember-concurrency';
import config from 'travis/config/environment';
+import { capitalize } from '@ember/string';
export default Component.extend({
permissionsService: service('permissions'),
@@ -28,7 +29,7 @@ export default Component.extend({
displayMenuTofu: alias('repo.permissions.create_request'),
repositoryProvider: computed('repo.provider', function () {
- return this.repo.provider.capitalize();
+ return capitalize(this.repo.provider);
}),
repositoryType: computed('repo.serverType', function () {
diff --git a/app/components/dialogs/migrate-beta.js b/app/components/dialogs/migrate-beta.js
index 8b54cc937b..1d9e370c9a 100644
--- a/app/components/dialogs/migrate-beta.js
+++ b/app/components/dialogs/migrate-beta.js
@@ -15,7 +15,7 @@ export default Component.extend({
selectableAccounts: computed('accounts.organizations.[]', 'user', function () {
const accountOrgs = this.accounts.organizations || [];
- const organizations = accountOrgs.toArray() || [];
+ const organizations = accountOrgs || [];
return [this.user, ...organizations]; // user account must be first item, so that it couldn't be removed from selected options
}),
selectableOptions: map('selectableAccounts', makeOptionFromAccount),
@@ -24,7 +24,7 @@ export default Component.extend({
register: task(function* () {
try {
- yield this.user.joinMigrateBeta(this.selectedAccounts.without(this.user).toArray());
+ yield this.user.joinMigrateBeta(this.selectedAccounts.without(this.user));
this.onClose();
this.flashes.clear();
this.flashes.success('You have successfully joined the beta!');
diff --git a/app/components/dialogs/plan-switch-warning.js b/app/components/dialogs/plan-switch-warning.js
index 8b54cc937b..1d9e370c9a 100644
--- a/app/components/dialogs/plan-switch-warning.js
+++ b/app/components/dialogs/plan-switch-warning.js
@@ -15,7 +15,7 @@ export default Component.extend({
selectableAccounts: computed('accounts.organizations.[]', 'user', function () {
const accountOrgs = this.accounts.organizations || [];
- const organizations = accountOrgs.toArray() || [];
+ const organizations = accountOrgs || [];
return [this.user, ...organizations]; // user account must be first item, so that it couldn't be removed from selected options
}),
selectableOptions: map('selectableAccounts', makeOptionFromAccount),
@@ -24,7 +24,7 @@ export default Component.extend({
register: task(function* () {
try {
- yield this.user.joinMigrateBeta(this.selectedAccounts.without(this.user).toArray());
+ yield this.user.joinMigrateBeta(this.selectedAccounts.without(this.user));
this.onClose();
this.flashes.clear();
this.flashes.success('You have successfully joined the beta!');
diff --git a/app/components/dialogs/user-management-modal.js b/app/components/dialogs/user-management-modal.js
index dc03b284a7..8404a0d725 100644
--- a/app/components/dialogs/user-management-modal.js
+++ b/app/components/dialogs/user-management-modal.js
@@ -55,7 +55,7 @@ export default Component.extend({
}),
isAllSelected: computed('selectedUserIds', 'buildPermissionsToShow', function () {
- const selectedUserIds = this.get('selectedUserIds');
+ const selectedUserIds = this.selectedUserIds;
if (Object.keys(selectedUserIds).length === 0) {
return false;
}
diff --git a/app/components/enterprise-banner.js b/app/components/enterprise-banner.js
index 35aad7f44f..d97be9d03d 100644
--- a/app/components/enterprise-banner.js
+++ b/app/components/enterprise-banner.js
@@ -3,7 +3,7 @@ import Component from '@ember/component';
import { computed } from '@ember/object';
import { and, alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { task } from 'ember-concurrency';
import timeAgoInWords from 'travis/utils/time-ago-in-words';
@@ -69,7 +69,8 @@ export default Component.extend({
expirationTimeFromNow: computed('expirationTime', function () {
let expirationTime = this.expirationTime;
- return new htmlSafe(timeAgoInWords(expirationTime) || '-');
+ let timeText = timeAgoInWords(expirationTime) || '-';
+ return new htmlSafe(`${timeText}`);
}),
expiring: computed('daysUntilExpiry', function () {
diff --git a/app/components/error-page-layout.js b/app/components/error-page-layout.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/error-page-layout.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/negative-balance-private-and-public.js b/app/components/flashes/negative-balance-private-and-public.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/negative-balance-private-and-public.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/negative-balance-private.js b/app/components/flashes/negative-balance-private.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/negative-balance-private.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/negative-balance-public.js b/app/components/flashes/negative-balance-public.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/negative-balance-public.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/payment-details-edit-lock.js b/app/components/flashes/payment-details-edit-lock.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/payment-details-edit-lock.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/pending-user-licenses.js b/app/components/flashes/pending-user-licenses.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/pending-user-licenses.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/read-only-mode.js b/app/components/flashes/read-only-mode.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/read-only-mode.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/scheduled-plan-change.js b/app/components/flashes/scheduled-plan-change.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/scheduled-plan-change.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/flashes/users-limit-exceeded.js b/app/components/flashes/users-limit-exceeded.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/flashes/users-limit-exceeded.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/forms/form-field.js b/app/components/forms/form-field.js
index 009761fada..7b328597f4 100644
--- a/app/components/forms/form-field.js
+++ b/app/components/forms/form-field.js
@@ -43,13 +43,18 @@ export default Component.extend({
multipleInputsValue: null,
validator: null,
- required: equal('validator.kind', presense),
+ requiredOverride: null,
+ required: computed('requiredOverride', 'validator.kind', function() {
+ if (this.requiredOverride !== null) {
+ return this.requiredOverride;
+ }
+ return this.validator && this.validator.kind === presense
+ }),
autoValidate: true,
errorMessage: '',
isFocused: false,
-
isDefault: equal('state', FIELD_STATE.DEFAULT),
isValid: equal('state', FIELD_STATE.VALID),
isError: equal('state', FIELD_STATE.ERROR),
@@ -59,7 +64,14 @@ export default Component.extend({
showClear: and('allowClear', 'value'),
showIcon: notEmpty('icon'),
showFrame: not('disableFrame'),
- showValidationStatusIcons: and('enableValidationStatusIcons', 'requiresValidation'),
+ showValidationStatusIconsOverride: null,
+ showValidationStatusIcons: computed('showValidationStatusIconsOverride', 'enableValidationStatusIcons', 'requiresValidation', function() {
+ if (this.showValidationStatusIconsOverride !== null) {
+ return this.showValidationStatusIconsOverride;
+ }
+
+ return this.enableValidationStatusIcon && this.requiresValidation;
+ }),
showValidationStatusMessage: and('enableValidationStatusMessage', 'requiresValidation'),
selectComponent: computed('multiple', function () {
@@ -67,6 +79,7 @@ export default Component.extend({
}),
validate(value, isFormValidation = false) {
+
if (!this.validateOnField && !isFormValidation) return true;
let validator = this.validator;
diff --git a/app/components/forms/form-select-multiple.js b/app/components/forms/form-select-multiple.js
index 92b48505c9..e2716d7dff 100644
--- a/app/components/forms/form-select-multiple.js
+++ b/app/components/forms/form-select-multiple.js
@@ -1,5 +1,34 @@
import EmberPowerSelectMultiple from 'ember-power-select/components/power-select-multiple';
-import FormSelectMixin from 'travis/mixins/components/form-select';
+import { computed } from '@ember/object';
-export default EmberPowerSelectMultiple.extend(FormSelectMixin, {
-});
+const OPTIONS_FOR_SEARCH = 5;
+
+const CSS_CLASSES = {
+ DISABLED: 'travis-form__field-component--disabled',
+ FIELD_COMPONENT: 'travis-form__field-component',
+ FIELD_SELECT: 'travis-form__field-select'
+};
+export default class extends EmberPowerSelectMultiple {
+ disabled = false;
+ placeholder = '';
+
+ get onChange() {}
+ get searchEnabled() {
+ return this.options.length >= OPTIONS_FOR_SEARCH || !!this.search;
+ }
+
+ searchPlaceholder = 'Type to filter options...';
+
+ allowClear = false;
+ horizontalPosition = 'auto';
+ verticalPosition = 'below';
+
+ @computed('disabled')
+ get triggerClass() {
+ const classes = [CSS_CLASSES.FIELD_COMPONENT, CSS_CLASSES.FIELD_SELECT];
+ if (this.disabled) {
+ classes.push(CSS_CLASSES.DISABLED);
+ }
+ return classes.join(' ');
+ }
+}
diff --git a/app/components/forms/form-select.js b/app/components/forms/form-select.js
index 4fac43e36d..9d23dc1d13 100644
--- a/app/components/forms/form-select.js
+++ b/app/components/forms/form-select.js
@@ -1,5 +1,34 @@
import EmberPowerSelect from 'ember-power-select/components/power-select';
-import FormSelectMixin from 'travis/mixins/components/form-select';
+import { computed } from '@ember/object';
-export default EmberPowerSelect.extend(FormSelectMixin, {
-});
+const OPTIONS_FOR_SEARCH = 5;
+
+const CSS_CLASSES = {
+ DISABLED: 'travis-form__field-component--disabled',
+ FIELD_COMPONENT: 'travis-form__field-component',
+ FIELD_SELECT: 'travis-form__field-select'
+};
+export default class extends EmberPowerSelect {
+ disabled = false;
+ placeholder = '';
+
+ get onChange() {}
+ get searchEnabled() {
+ return this.options.length >= OPTIONS_FOR_SEARCH || !!this.search;
+ }
+
+ searchPlaceholder = 'Type to filter options...';
+
+ allowClear = false;
+ horizontalPosition = 'auto';
+ verticalPosition = 'below';
+
+ @computed('disabled')
+ get triggerClass() {
+ const classes = [CSS_CLASSES.FIELD_COMPONENT, CSS_CLASSES.FIELD_SELECT];
+ if (this.disabled) {
+ classes.push(CSS_CLASSES.DISABLED);
+ }
+ return classes.join(' ');
+ }
+}
diff --git a/app/components/forms/multiple-inputs-field.js b/app/components/forms/multiple-inputs-field.js
index 0e6b60079e..cb75948819 100644
--- a/app/components/forms/multiple-inputs-field.js
+++ b/app/components/forms/multiple-inputs-field.js
@@ -8,11 +8,24 @@ export default Component.extend({
initialValue: '',
value: reads('initialValue'),
- fields: computed('value', {
+ fieldsValue: computed('value', {
get() {
- return (this.value || '').split(this.delimeter).map(value => ({ value }));
+ return (this.value || '').split(this.delimiter).map(value => ({ value }));
},
set(_, value) {
+ // Handle the setter logic if needed.
+ // For example, you can parse the 'value' and update it.
+ this.set('value', value.join(this.delimiter));
+ return value;
+ }
+ }),
+
+ fields: computed('fieldsValue', {
+ get() {
+ return this.fieldsValue;
+ },
+ set(_, value) {
+ this.set('fieldsValue', value);
return value;
}
}),
@@ -35,13 +48,14 @@ export default Component.extend({
this.updateValues(values);
},
+
actions: {
handleBlur() {
const values = this.fields.map(input => input.value);
this.handleValidation(values);
const value = values.join(this.delimeter);
- this.set('value', value);
+ this.set('valueValue', value);
},
handleChange(index, { target }) {
@@ -50,7 +64,7 @@ export default Component.extend({
fields[index] = { value };
const values = fields.map(input => input.value);
this.handleValidation(values);
- this.set('fields', fields);
+ this.set('fieldsValue', fields);
},
removeInput(inputIndex, e) {
@@ -58,12 +72,12 @@ export default Component.extend({
const filteredFields = this.fields.filter((_, index) => index !== inputIndex);
const values = filteredFields.map(input => input.value);
this.handleValidation(values);
- this.set('fields', filteredFields);
+ this.set('fieldsValue', filteredFields);
},
addInput(e) {
e.preventDefault();
- this.set('fields', [...this.fields, { value: '' }]);
+ this.set('fieldsValue', [...this.fields, { value: '' }]);
},
}
});
diff --git a/app/components/github-apps-repository.js b/app/components/github-apps-repository.js
index dac327d63e..e8c484f146 100644
--- a/app/components/github-apps-repository.js
+++ b/app/components/github-apps-repository.js
@@ -9,6 +9,7 @@ import {
import hasErrorWithStatus from 'travis/utils/api-errors';
import { task } from 'ember-concurrency';
import { vcsLinks } from 'travis/services/external-links';
+import { capitalize } from '@ember/string';
export default Component.extend({
accounts: service(),
@@ -23,7 +24,7 @@ export default Component.extend({
isNotMatchGithub: not('isMatchGithub'),
repositoryProvider: computed('repository.provider', function () {
- return this.repository.provider.capitalize();
+ return capitalize(this.repository.provider);
}),
repositoryType: computed('repository.serverType', function () {
@@ -41,9 +42,18 @@ export default Component.extend({
return this.user && vcsLinks.accessSettingsUrl(this.user.vcsType, { owner: this.user.login });
}),
+ hasActivatePermission: computed('permissions.all', 'repository', function () {
+ let repo = this.repository;
+ let forRepo = (repo.owner.id == this.user.id && repo.ownerType == 'user') ||
+ ((repo.shared || repo.ownerType != 'user') && repo.permissions?.activate);
+ return forRepo;
+ }),
+
hasSettingsPermission: computed('permissions.all', 'repository', function () {
let repo = this.repository;
- return this.permissions.hasPushPermission(repo);
+ let forRepo = (repo.owner.id == this.user.id && repo.ownerType == 'user') ||
+ ((repo.shared || repo.ownerType != 'user') && repo.permissions?.settings_read);
+ return forRepo &&this.permissions.hasPushPermission(repo);
}),
hasEmailSubscription: computed('repository', 'repository.emailSubscribed', function () {
@@ -73,7 +83,7 @@ export default Component.extend({
try {
yield repository.toggle();
yield repository.reload();
- this.pusher.subscribe(`repo-${repository.id}`);
+ Travis.pusher.subscribe(`repo-${repository.id}`);
} catch (error) {
this.set('apiError', error);
}
diff --git a/app/components/header-links.js b/app/components/header-links.js
index e30a3737cd..54cc49d24b 100644
--- a/app/components/header-links.js
+++ b/app/components/header-links.js
@@ -2,7 +2,7 @@
import { VERSION } from '@ember/version';
import Component from '@ember/component';
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import config from 'travis/config/environment';
diff --git a/app/components/insights-glance.js b/app/components/insights-glance.js
index 8d5d95c829..0a639ee23c 100644
--- a/app/components/insights-glance.js
+++ b/app/components/insights-glance.js
@@ -18,15 +18,15 @@ export default Component.extend({
deltaTitle: '',
deltaText: '',
- labels: computed(() => []),
- values: computed(() => []),
+ labels: [],
+ values: [],
datasetTitle: 'Data',
centerline: null,
showPlaceholder: or('isLoading', 'isEmpty'),
// Chart component data
- data: computed('values.[]', 'labels.[]', 'datasetTitle', function () {
+ data: computed('values', 'labels', 'datasetTitle', function () {
return {
type: 'spline',
x: 'x',
diff --git a/app/components/insights-tabs.js b/app/components/insights-tabs.js
index fdfdbf6686..d67966f72b 100644
--- a/app/components/insights-tabs.js
+++ b/app/components/insights-tabs.js
@@ -1,7 +1,8 @@
import Component from '@ember/component';
import { INSIGHTS_INTERVALS } from 'travis/services/insights';
+import { capitalize } from '@ember/string';
-export const INSIGHTS_TABS = Object.values(INSIGHTS_INTERVALS).map(slug => ({ slug, title: slug.capitalize() }));
+export const INSIGHTS_TABS = Object.values(INSIGHTS_INTERVALS).map(slug => ({ slug, title: capitalize(slug) }));
export default Component.extend({
tagName: 'ul',
diff --git a/app/components/job-not-found.js b/app/components/job-not-found.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/job-not-found.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/jobs-item.js b/app/components/jobs-item.js
index 7d483e0e58..94f8ec0603 100644
--- a/app/components/jobs-item.js
+++ b/app/components/jobs-item.js
@@ -3,6 +3,7 @@ import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
import jobConfigArch from 'travis/utils/job-config-arch';
import jobConfigLanguage from 'travis/utils/job-config-language';
+import { capitalize } from '@ember/string';
export default Component.extend({
tagName: 'li',
@@ -69,7 +70,7 @@ export default Component.extend({
if (serverType === 'svn') {
return 'SVN';
} else {
- return serverType.capitalize();
+ return capitalize(serverType);
}
}),
});
diff --git a/app/components/jobs-list.js b/app/components/jobs-list.js
index 8b6824820b..be43b2c865 100644
--- a/app/components/jobs-list.js
+++ b/app/components/jobs-list.js
@@ -1,6 +1,7 @@
import { get, computed } from '@ember/object';
import Component from '@ember/component';
import { alias, mapBy } from '@ember/object/computed';
+import { A } from '@ember/array'
export default Component.extend({
tagName: 'section',
@@ -66,10 +67,10 @@ export default Component.extend({
}
const jobsAllowedToFail = filteredJobs.filterBy('allowFailure');
- const relevantJobs = jobsAllowedToFail.filterBy('isFinished').rejectBy('state', 'passed');
+ const relevantJobs = A(jobsAllowedToFail.filterBy('isFinished')).rejectBy('state', 'passed');
- const failedJobsNotAllowedToFail = this.filteredJobs.rejectBy('allowFailure')
- .filterBy('isFinished').rejectBy('state', 'passed');
+ const failedJobsNotAllowedToFail = A(A(this.filteredJobs).rejectBy('allowFailure')
+ .filterBy('isFinished')).rejectBy('state', 'passed');
if (relevantJobs.length > 0) {
let jobList;
diff --git a/app/components/log-content.js b/app/components/log-content.js
index c72db2c278..5f394d90b1 100644
--- a/app/components/log-content.js
+++ b/app/components/log-content.js
@@ -76,6 +76,7 @@ export default Component.extend({
externalLinks: service(),
router: service(),
scroller: service(),
+ logToLogContent: service(),
classNameBindings: ['logIsVisible:is-open'],
logIsVisible: false,
@@ -85,6 +86,8 @@ export default Component.extend({
isShowingRemoveLogModal: false,
didInsertElement() {
+ this.logToLogContent.setLogContent(this);
+ this.logToLogContent.setLog(this.log);
if (this.get('features.debugLogging')) {
// eslint-disable-next-line
console.log('log view: did insert');
@@ -105,10 +108,6 @@ export default Component.extend({
let parts, ref;
if (log || (log = this.log)) {
parts = log.get('parts');
- parts.removeArrayObserver(this, {
- didChange: 'partsDidChange',
- willChange: 'noop'
- });
parts.destroy();
log.notifyPropertyChange('parts');
if ((ref = this.lineSelector) != null) {
@@ -176,10 +175,6 @@ export default Component.extend({
let parts;
if (log || (log = this.log)) {
parts = log.get('parts');
- parts.addArrayObserver(this, {
- didChange: 'partsDidChange',
- willChange: 'noop'
- });
parts = parts.slice(0);
this.partsDidChange(parts, 0, null, parts.length);
}
@@ -220,12 +215,13 @@ export default Component.extend({
return this.permissions.hasPermission(repo);
}),
- canRemoveLog: computed('job', 'job.canRemoveLog', 'hasPermission', function () {
+ canRemoveLog: computed('job', 'job.canRemoveLog', 'hasPermission', 'currentUser', function () {
let job = this.job;
let canRemoveLog = this.get('job.canRemoveLog');
let hasPermission = this.hasPermission;
+ let access = this.currentUser && this.currentUser.hasPermissionToRepo(this.get('job.repo'), 'log_delete');
if (job) {
- return canRemoveLog && hasPermission;
+ return canRemoveLog && hasPermission && access;
}
}),
@@ -247,7 +243,14 @@ export default Component.extend({
},
toggleLog() {
- this.toggleProperty('logIsVisible');
+ let access = this.currentUser && this.currentUser.hasPermissionToRepo(this.get('job.repo'), 'log_view');
+ if (access) {
+ this.toggleProperty('logIsVisible');
+ } else {
+ if (this.logIsVisible) {
+ this.toggleProperty('logIsVisible');
+ }
+ }
},
toggleRemoveLogModal() {
diff --git a/app/components/manage-subscription-button.js b/app/components/manage-subscription-button.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/manage-subscription-button.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/missing-notice.js b/app/components/missing-notice.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/missing-notice.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/no-account.js b/app/components/no-account.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/no-account.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/no-builds.js b/app/components/no-builds.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/no-builds.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/not-active.js b/app/components/not-active.js
index 0d4433b02c..2ab6ce59b9 100644
--- a/app/components/not-active.js
+++ b/app/components/not-active.js
@@ -64,7 +64,7 @@ export default Component.extend({
const response = yield this.api.post(`/repo/${repoId}/activate`);
if (response.active) {
- this.pusher.subscribe(`repo-${repoId}`);
+ Travis.pusher.subscribe(`repo-${repoId}`);
this.repo.set('active', true);
this.flashes.success('Repository has been successfully activated.');
diff --git a/app/components/notice-banner.js b/app/components/notice-banner.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/notice-banner.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/owner/migrate.js b/app/components/owner/migrate.js
index 0ccb10f2fe..e2bf3b5b3e 100644
--- a/app/components/owner/migrate.js
+++ b/app/components/owner/migrate.js
@@ -70,9 +70,9 @@ export default Component.extend({
const { isAllSelected, selectableRepositories, selectedRepositories } = this;
if (isAllSelected) {
- selectedRepositories.removeObjects(selectableRepositories.toArray());
+ selectedRepositories.removeObjects(selectableRepositories);
} else {
- selectedRepositories.addObjects(selectableRepositories.toArray());
+ selectedRepositories.addObjects(selectableRepositories);
}
},
diff --git a/app/components/owner/wizard.js b/app/components/owner/wizard.js
index 85064117dd..66b416c682 100644
--- a/app/components/owner/wizard.js
+++ b/app/components/owner/wizard.js
@@ -30,7 +30,7 @@ export default Component.extend({
actions: {
nextStep() {
this.updateStep.perform(1);
- if (this.wizardStep > 3) this.get('onClose')();
+ if (this.wizardStep > 3) this.onClose();
},
previousStep() {
this.updateStep.perform(-1);
diff --git a/app/components/plan-usage.js b/app/components/plan-usage.js
index 7ff2bcf338..ba66a69301 100644
--- a/app/components/plan-usage.js
+++ b/app/components/plan-usage.js
@@ -169,7 +169,7 @@ export default Component.extend({
await this.owner.fetchExecutions.perform(moment(this.dateRange.start).format('YYYY-MM-DD'),
moment(this.dateRange.end || this.dateRange.start).format('YYYY-MM-DD'));
const header = ['Job Id', 'Started at', 'Finished at', 'OS', 'Credits consumed', 'Minutes consumed', 'Repository', 'Owner', 'Sender'];
- const data = this.get('executionsDataForCsv');
+ const data = this.executionsDataForCsv;
this.download.asCSV(fileName, header, data);
},
@@ -182,7 +182,7 @@ export default Component.extend({
await this.owner.fetchExecutions.perform(moment(this.dateRange.start).format('YYYY-MM-DD'),
moment(this.dateRange.end || this.dateRange.start).format('YYYY-MM-DD'));
const header = ['Job Id', 'Sender', 'Credits consumed', 'Date'];
- const data = await this.get('userLicenseExecutionsDataForCsv');
+ const data = await this.userLicenseExecutionsDataForCsv;
this.download.asCSV(fileName, header, data);
},
diff --git a/app/components/profile-nav.js b/app/components/profile-nav.js
index c0e2c13c7d..46930e262c 100644
--- a/app/components/profile-nav.js
+++ b/app/components/profile-nav.js
@@ -64,24 +64,47 @@ export default Component.extend({
isOrganization: reads('model.isOrganization'),
hasAdminPermissions: reads('model.permissions.admin'),
+ hasPlanViewPermissions: reads('model.permissions.plan_view'),
+ hasPlanUsagePermissions: reads('model.permissions.plan_usage'),
+ hasPlanCreatePermissions: reads('model.permissions.plan_create'),
+ hasBillingViewPermissions: reads('model.permissions.billing_view'),
+ hasInvoicesViewPermissions: reads('model.permissions.plan_invoices'),
+ hasSettingsReadPermissions: reads('model.permissions.settings_read'),
isOrganizationAdmin: and('isOrganization', 'hasAdminPermissions'),
- showOrganizationSettings: and('isOrganizationAdmin', 'isProVersion'),
-
- showSubscriptionTab: computed('features.enterpriseVersion', 'model.isAssembla', 'model.isUser', function () {
- const isAssemblaUser = this.model.isUser && this.model.isAssembla;
- const isEnterprise = this.features.get('enterpriseVersion');
- return !isEnterprise && !isAssemblaUser && !!billingEndpoint;
+ showOrganizationSettings: computed('isOrganizationAdmin', 'isProVersion', 'hasSettingsReadPermissions', function () {
+ const forOrganization = !this.isOrganization || this.hasSettingsReadPermissions;
+ return this.isOrganizationAdmin && this.isProVersion && forOrganization;
}),
- showPaymentDetailsTab: computed('showSubscriptionTab', 'isOrganization', 'isOrganizationAdmin', 'model.isNotGithubOrManual', function () {
- if (this.isOrganization) {
- return this.showSubscriptionTab && this.isOrganizationAdmin && this.model.get('isNotGithubOrManual');
- } else {
- return this.showSubscriptionTab && this.model.get('isNotGithubOrManual');
- }
+
+ showSubscriptionTab: computed('features.enterpriseVersion', 'hasPlanViewPermissions',
+ 'hasPlanCreatePermissions', 'model.isAssembla', 'model.isUser',
+ 'isOrganization', function () {
+ const forOrganization = !this.isOrganization ||
+ ((this.model.hasSubscription || this.model.hasV2Subscription) && !!this.hasPlanViewPermissions) ||
+ !!this.hasPlanCreatePermissions;
+
+ const isAssemblaUser = this.model.isUser && this.model.isAssembla;
+ const isEnterprise = this.features.get('enterpriseVersion');
+ return !isEnterprise && !isAssemblaUser && !!billingEndpoint && !!forOrganization;
+ }),
+ showPaymentDetailsTab: computed('showSubscriptionTab', 'isOrganization', 'isOrganizationAdmin',
+ 'hasBillingViewPermissions', 'hasInvoicesViewPermissions', 'model.isNotGithubOrManual', function () {
+ if (this.isOrganization) {
+ const forOrganization = !this.isOrganization || this.hasBillingViewPermissions || this.hasInvoicesViewPermissions;
+
+ return this.showSubscriptionTab && this.model.get('isNotGithubOrManual') && (this.isOrganizationAdmin || forOrganization);
+ } else {
+ return this.showSubscriptionTab && this.model.get('isNotGithubOrManual');
+ }
+ }),
+ showPlanUsageTab: computed('showSubscriptionTab', 'model.hasCredits', 'hasPlanUsagePermissions', function () {
+ const forOrganization = !this.isOrganization || this.hasPlanUsagePermissions;
+ return this.showSubscriptionTab && this.model.hasCredits && forOrganization;
}),
- showPlanUsageTab: and('showSubscriptionTab', 'model.hasCredits'),
- usersUsage: computed('account.allowance.userUsage', 'addonUsage', function () {
- const userUsage = this.model.allowance.get('userUsage');
+
+ usersUsage: computed('account.allowance.userUsage', 'addonUsage', 'hasPlanUsagePermissions', function () {
+ // const forOrganization = !this.isOrganization || this.hasPlanUsagePermissions;
+ const userUsage = this.model.allowance.userUsage;
if (userUsage === undefined) {
return true;
}
@@ -106,26 +129,26 @@ export default Component.extend({
return;
}
- if (allowance.get('paymentChangesBlockCredit') || allowance.get('paymentChangesBlockCaptcha')) {
+ if (allowance.paymentChangesBlockCredit || allowance.paymentChangesBlockCaptcha) {
let time;
- if (allowance.get('paymentChangesBlockCaptcha')) time = allowance.get('captchaBlockDuration');
- if (allowance.get('paymentChangesBlockCredit')) time = allowance.get('creditCardBlockDuration');
+ if (allowance.paymentChangesBlockCaptcha) time = allowance.captchaBlockDuration;
+ if (allowance.paymentChangesBlockCredit) time = allowance.creditCardBlockDuration;
this.flashes.custom('flashes/payment-details-edit-lock', { owner: this.model, isUser: this.model.isUser, time: time}, 'warning');
}
- if (allowance.get('subscriptionType') !== 2) {
+ if (allowance.subscriptionType !== 2) {
return;
}
- if (!allowance.get('privateRepos') && !allowance.get('publicRepos') && (this.isOrganizationAdmin || this.model.isUser)) {
+ if (!allowance.privateRepos && !allowance.publicRepos && (this.isOrganizationAdmin || this.model.isUser)) {
this.flashes.custom('flashes/negative-balance-private-and-public', { owner: this.model, isUser: this.model.isUser }, 'warning');
- } else if (!allowance.get('privateRepos') && (this.isOrganizationAdmin || this.model.isUser)) {
+ } else if (!allowance.privateRepos && (this.isOrganizationAdmin || this.model.isUser)) {
this.flashes.custom('flashes/negative-balance-private', { owner: this.model, isUser: this.model.isUser }, 'warning');
- } else if (!allowance.get('publicRepos') && (this.isOrganizationAdmin || this.model.isUser)) {
+ } else if (!allowance.publicRepos && (this.isOrganizationAdmin || this.model.isUser)) {
this.flashes.custom('flashes/negative-balance-public', { owner: this.model, isUser: this.model.isUser }, 'warning');
}
- if (allowance.get('pendingUserLicenses')) {
+ if (allowance.pendingUserLicenses) {
this.flashes.custom('flashes/pending-user-licenses', { owner: this.model, isUser: this.model.isUser }, 'warning');
} else if (!this.usersUsage) {
this.flashes.custom('flashes/users-limit-exceeded', { owner: this.model, isUser: this.model.isUser }, 'warning');
@@ -135,7 +158,7 @@ export default Component.extend({
willDestroyElement() {
const allowance = this.model.allowance;
- if (allowance && allowance.get('subscriptionType') === 2) {
+ if (allowance && allowance.subscriptionType === 2) {
this.flashes.removeCustomsByClassName('warning');
}
},
diff --git a/app/components/raw-config.js b/app/components/raw-config.js
index e827b9c28a..4c4d660731 100644
--- a/app/components/raw-config.js
+++ b/app/components/raw-config.js
@@ -33,7 +33,7 @@ export default Component.extend({
try {
return JSON.stringify(JSON.parse(config), null, 2);
} catch (e) {
- return config;
+ return config || "{}";
}
}),
diff --git a/app/components/repo-actions.js b/app/components/repo-actions.js
index 586e45c753..02803a77dd 100644
--- a/app/components/repo-actions.js
+++ b/app/components/repo-actions.js
@@ -4,6 +4,7 @@ import { computed } from '@ember/object';
import { alias, and, not, or, reads } from '@ember/object/computed';
import eventually from 'travis/utils/eventually';
import { task, taskGroup } from 'ember-concurrency';
+import { capitalize } from "@ember/string";
export default Component.extend({
flashes: service(),
@@ -38,6 +39,7 @@ export default Component.extend({
userHasPermissionForRepo: computed('repo.id', 'user', 'user.permissions.[]', function () {
let repo = this.repo;
let user = this.user;
+
if (user && repo) {
return user.hasAccessToRepo(repo);
}
@@ -58,6 +60,27 @@ export default Component.extend({
return user.hasPushAccessToRepo(repo);
}
}),
+ userHasCancelPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_cancel');
+ }
+ }),
+ userHasRestartPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_restart');
+ }
+ }),
+ userHasDebugPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_debug');
+ }
+ }),
canOwnerBuild: reads('repo.canOwnerBuild'),
ownerRoMode: reads('repo.owner.ro_mode'),
@@ -68,9 +91,9 @@ export default Component.extend({
showPriority: true,
showPrioritizeBuildModal: false,
- canCancel: and('userHasPullPermissionForRepo', 'item.canCancel'),
- canRestart: and('userHasPullPermissionForRepo', 'item.canRestart'),
- canDebug: and('userHasPushPermissionForRepo', 'item.canDebug'),
+ canCancel: and('userHasCancelPermissionForRepo', 'item.canCancel'),
+ canRestart: and('userHasRestartPermissionForRepo', 'item.canRestart'),
+ canDebug: and('userHasDebugPermissionForRepo', 'item.canDebug'),
isHighPriority: or('item.priority', 'item.build.priority'),
isNotAlreadyHighPriority: not('isHighPriority'),
hasPrioritizePermission: or('item.permissions.prioritize', 'item.build.permissions.prioritize'),
@@ -83,7 +106,7 @@ export default Component.extend({
yield eventually(this.item, (record) => {
record.cancel().then(() => {
- this.flashes.success(`${type.capitalize()} has been successfully cancelled.`);
+ this.flashes.success(`${capitalize(type)} has been successfully cancelled.`);
}, (xhr) => {
this.displayFlashError(xhr.status, 'cancel');
});
diff --git a/app/components/repo-show-tools.js b/app/components/repo-show-tools.js
index 9272a08d4c..a349b9d0fe 100644
--- a/app/components/repo-show-tools.js
+++ b/app/components/repo-show-tools.js
@@ -27,12 +27,16 @@ export default Component.extend({
displaySettingsLink: computed('permissions.all', 'repo', function () {
let repo = this.repo;
- return this.permissions.hasPushPermission(repo);
+ const forRepo = repo.permissions?.settings_read;
+
+ return forRepo && this.permissions.hasPushPermission(repo);
}),
displayCachesLink: computed('permissions.all', 'repo', function () {
let repo = this.repo;
- return this.permissions.hasPushPermission(repo) && config.endpoints.caches;
+ const forRepo = repo.permissions?.cache_view;
+
+ return forRepo && this.permissions.hasPushPermission(repo) && config.endpoints.caches;
}),
displayStatusImages: computed('permissions.all', 'repo', function () {
@@ -49,11 +53,12 @@ export default Component.extend({
let canTriggerBuild = this.get('repo.permissions.create_request');
let enterprise = this.get('features.enterpriseVersion');
let pro = this.get('features.proVersion');
+ const forRepo = this.repo.permissions?.build_create;
if (enterprise || pro) {
- return canTriggerBuild;
+ return canTriggerBuild && forRepo;
}
- return canTriggerBuild && migrationStatus !== 'migrated';
+ return canTriggerBuild && migrationStatus !== 'migrated' && forRepo;
}
),
diff --git a/app/components/repository-filter.js b/app/components/repository-filter.js
index 254fd89b10..963272f925 100644
--- a/app/components/repository-filter.js
+++ b/app/components/repository-filter.js
@@ -4,7 +4,7 @@ import config from 'travis/config/environment';
import { inject as service } from '@ember/service';
import { or, notEmpty } from '@ember/object/computed';
import { isPresent } from '@ember/utils';
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import fuzzyMatch from 'travis/utils/fuzzy-match';
export default Component.extend({
@@ -25,7 +25,8 @@ export default Component.extend({
}).restartable(),
computeName(name, query) {
- return isPresent(query) ? htmlSafe(fuzzyMatch(name, query)) : name;
+ const fuzzyMatchConst = fuzzyMatch(name, query)
+ return isPresent(query) ? htmlSafe(`${fuzzyMatchConst}`) : name;
},
didReceiveAttrs() {
diff --git a/app/components/repository-layout.js b/app/components/repository-layout.js
index 8a98668ef5..037e918101 100644
--- a/app/components/repository-layout.js
+++ b/app/components/repository-layout.js
@@ -2,12 +2,14 @@ import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { alias, reads } from '@ember/object/computed';
+import { capitalize } from "@ember/string";
export default Component.extend({
auth: service(),
externalLinks: service(),
features: service(),
flashes: service(),
+ router: service(),
isProVersion: reads('features.proVersion'),
isShowingTriggerBuildModal: false,
isShowingStatusBadgeModal: false,
@@ -16,7 +18,7 @@ export default Component.extend({
scansEnabled: reads('features.logScanner'),
repositoryProvider: computed('repo.provider', function () {
- return this.repo.provider.capitalize();
+ return capitalize(this.repo.provider);
}),
repositoryType: computed('repo.serverType', function () {
@@ -30,6 +32,10 @@ export default Component.extend({
}
}),
+ currentRouteName: computed('router.currentRouteName', function () {
+ return this.router.currentRouteName;
+ }),
+
repoUrl: computed('repo.{ownerName,vcsName,vcsType}', function () {
const owner = this.get('repo.ownerName');
const repo = this.get('repo.vcsName');
@@ -57,11 +63,13 @@ export default Component.extend({
},
toggleTriggerBuildModal() {
this.toggleProperty('isShowingTriggerBuildModal');
- }
+ },
+
+
},
didRender() {
- const repo = this.get('repo');
+ const repo = this.repo;
if (repo.hasBuildBackups === undefined) {
repo.fetchInitialBuildBackups.perform();
@@ -77,9 +85,9 @@ export default Component.extend({
} else {
this.flashes.custom('flashes/negative-balance-public', { owner: repo.owner, isUser: isUser }, 'warning');
}
- if (allowance.get('pendingUserLicenses')) {
+ if (allowance.pendingUserLicenses) {
this.flashes.custom('flashes/pending-user-licenses', { owner: repo.owner, isUser: isUser }, 'warning');
- } else if (!allowance.get('userUsage')) {
+ } else if (allowance && !allowance.userUsage) {
this.flashes.custom('flashes/users-limit-exceeded', { owner: repo.owner, isUser: isUser }, 'warning');
}
} else if (this.userRoMode && ownerRoMode) {
diff --git a/app/components/repository-sidebar.js b/app/components/repository-sidebar.js
index e10502b416..fa06ec8f1e 100644
--- a/app/components/repository-sidebar.js
+++ b/app/components/repository-sidebar.js
@@ -61,6 +61,8 @@ export default Component.extend({
},
onQueryChange(query) {
+ if (query.target) // might be KeyboardEvent
+ query = query.target.value;
if (query === '' || query === this.get('repositories.searchQuery')) { return; }
this.set('repositories.searchQuery', query);
this.get('repositories.showSearchResults').perform();
diff --git a/app/components/repository-status-toggle.js b/app/components/repository-status-toggle.js
index d8c7cd7100..5372e8d67f 100644
--- a/app/components/repository-status-toggle.js
+++ b/app/components/repository-status-toggle.js
@@ -74,7 +74,7 @@ export default Component.extend({
try {
yield repository.toggle();
yield repository.reload();
- this.pusher.subscribe(`repo-${repository.id}`);
+ Travis.pusher.subscribe(`repo-${repository.id}`);
} catch (error) {
this.set('apiError', error);
}
diff --git a/app/components/repository-visibility-icon.js b/app/components/repository-visibility-icon.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/repository-visibility-icon.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/request-config.js b/app/components/request-config.js
index 7dbb4fdf74..11a3e4ba92 100644
--- a/app/components/request-config.js
+++ b/app/components/request-config.js
@@ -15,11 +15,11 @@ export default Component.extend({
}),
formattedConfig: computed('config', 'slug', function () {
- const config = this.get('config');
+ const config = this.config;
try {
return JSON.stringify(config, null, 2);
} catch (e) {
- return config;
+ return config ? config : "{}";
}
}),
diff --git a/app/components/requests-item.js b/app/components/requests-item.js
index d4094cf438..9d04bd6884 100644
--- a/app/components/requests-item.js
+++ b/app/components/requests-item.js
@@ -1,6 +1,7 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
+import { capitalize } from "@ember/string";
export default Component.extend({
classNames: ['request-item'],
@@ -24,7 +25,7 @@ export default Component.extend({
status: computed('request.result', function () {
let result = this.get('request.result');
- return result.capitalize();
+ return capitalize(result);
}),
message: computed('features.proVersion', 'request.message', function () {
diff --git a/app/components/resubscribe-button.js b/app/components/resubscribe-button.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/resubscribe-button.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/scan-result-item.js b/app/components/scan-result-item.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/scan-result-item.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/status-images.js b/app/components/status-images.js
index 002ab34207..630e2d6248 100644
--- a/app/components/status-images.js
+++ b/app/components/status-images.js
@@ -2,7 +2,10 @@ import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
-import { bindKeyboardShortcuts, unbindKeyboardShortcuts } from 'ember-keyboard-shortcuts';
+import {
+ bindKeyboardShortcuts,
+ unbindKeyboardShortcuts
+} from 'ember-keyboard-shortcuts';
import { task } from 'ember-concurrency';
import { IMAGE_FORMATS } from 'travis/services/status-images';
import BranchSearching from 'travis/mixins/branch-searching';
diff --git a/app/components/top-bar.js b/app/components/top-bar.js
index 3b1bf4ac1f..d6962a6ac5 100644
--- a/app/components/top-bar.js
+++ b/app/components/top-bar.js
@@ -1,87 +1,113 @@
import { scheduleOnce } from '@ember/runloop';
import Component from '@ember/component';
import Ember from 'ember';
-import { computed, setProperties, set } from '@ember/object';
+import {
+ computed,
+ setProperties,
+ set,
+ action
+} from '@ember/object';
import { reads } from '@ember/object/computed';
import { inject as service } from '@ember/service';
-import InViewportMixin from 'ember-in-viewport';
-
-export default Component.extend(InViewportMixin, {
- auth: service(),
- store: service(),
- externalLinks: service(),
- features: service(),
- flashes: service(),
- router: service(),
- storage: service(),
-
- tagName: 'header',
- classNames: ['top'],
- classNameBindings: ['isWhite:top--white'],
- isWhite: false,
- landingPage: false,
- isNavigationOpen: false,
- isActivation: false,
-
- activeModel: null,
- model: reads('activeModel'),
-
- user: reads('auth.currentUser'),
- isUnconfirmed: computed('user.confirmedAt', function () {
+export default class TopBar extends Component {
+ @service auth;
+ @service store;
+ @service externalLinks;
+ @service features;
+ @service flashes;
+ @service router;
+ @service storage;
+ @service inViewport
+
+ tagName = 'header';
+ classNames = ['top'];
+ classNameBindings = ['isWhite:top--white'];
+ isWhite = false;
+ landingPage = false;
+ isNavigationOpen = false;
+ isActivation = false;
+ viewportTolerance = { top: 0, bottom: 0, left: 0, right: 0 };
+ activeModel = null;
+ @reads('activeModel') model;
+
+ @reads('auth.currentUser') user;
+
+ @computed('user.confirmedAt')
+ get isUnconfirmed() {
if (!this.user ||
- (this.storage.wizardStep > 0 && this.storage.wizardStep <= 1) ||
- this.router.currentRouteName == 'first_sync' ||
- this.router.currentRouteName == 'github_apps_installation')
+ (this.storage.wizardStep > 0 && this.storage.wizardStep <= 1) ||
+ this.router.currentRouteName == 'first_sync' ||
+ this.router.currentRouteName == 'github_apps_installation') {
return false;
+ }
return !this.user.confirmedAt;
- }),
+ }
- userName: computed('user.{login,name}', function () {
- let login = this.get('user.login');
- let name = this.get('user.name');
+ @computed('user.{login,name}')
+ get userName() {
+ let login = this.user.login;
+ let name = this.user.name;
return name || login;
- }),
+ }
- showCta: computed('auth.signedIn', 'landingPage', 'features.landingPageCta', function () {
- let signedIn = this.get('auth.signedIn');
+ @computed('auth.signedIn', 'landingPage', 'features.landingPageCta')
+ get showCta() {
+ let signedIn = this.auth.signedIn;
let landingPage = this.landingPage;
- let ctaEnabled = this.get('features.landingPageCta');
+ let ctaEnabled = this.features.landingPageCta;
return !signedIn && !landingPage && ctaEnabled;
- }),
+ }
- hasNoPlan: computed('model.allowance.subscriptionType', 'model.hasV2Subscription', 'model.subscription', function () {
- return !this.get('model.hasV2Subscription') && this.get('model.subscription') === undefined && this.get('model.allowance.subscriptionType') === 3;
- }),
+ @computed('model.allowance.subscriptionType', 'model.hasV2Subscription', 'model.subscription')
+ get hasNoPlan() {
+ if(!this.model) return false; // logged out
+ return !this.model.hasV2Subscription && this.model.subscription === undefined && this.model.allowance && this.model.allowance.subscriptionType === 3;
+ }
+
+ @action
+ setupInViewport() {
+ const loader = document.getElementById('loader');
+ const viewportTolerance = { bottom: 200 };
+ const { onEnter, _onExit } = this.inViewport.watchElement(loader, { viewportTolerance });
+ // pass the bound method to `onEnter` or `onExit`
+ onEnter(this.didEnterViewport.bind(this));
+ }
+
+ willDestroy() {
+ // need to manage cache yourself if you don't use the mixin
+ const loader = document.getElementById('loader');
+ this.inViewport.stopWatching(loader);
+
+ super.willDestroy(...arguments);
+ }
didInsertElement() {
if (Ember.testing) {
- this._super(...arguments);
+ super.didInsertElement(...arguments);
return;
}
- setProperties(this, {
- viewportSpy: true
- });
- this._super(...arguments);
+
+ set(this, 'viewportSpy', true);
+ super.didInsertElement(...arguments);
scheduleOnce('afterRender', this, () => {
const { clientHeight = 76 } = this.element;
set(this, 'viewportTolerance.top', clientHeight);
});
- },
+ }
didEnterViewport() {
this.flashes.set('topBarVisible', true);
- },
+ }
didExitViewport() {
this.flashes.set('topBarVisible', false);
- },
+ }
- actions: {
- toggleNavigation() {
- this.toggleProperty('isNavigationOpen');
- }
+ @action
+ toggleNavigation() {
+ this.toggleProperty('isNavigationOpen');
}
-});
+}
diff --git a/app/components/travis-form.js b/app/components/travis-form.js
index dbc01993d8..0ee088a94b 100644
--- a/app/components/travis-form.js
+++ b/app/components/travis-form.js
@@ -17,7 +17,7 @@ export default Component.extend({
onSubmit() {},
registerField(field) {
- this.fields.addObject(field);
+ this.fields.push(field);
},
unregisterField(field) {
diff --git a/app/components/trigger-custom-build.js b/app/components/trigger-custom-build.js
index 8cf2ce42a9..e7d65f1963 100644
--- a/app/components/trigger-custom-build.js
+++ b/app/components/trigger-custom-build.js
@@ -1,6 +1,6 @@
import Component from '@ember/component';
import { task, timeout } from 'ember-concurrency';
-import YAML from 'yamljs';
+import yaml from 'js-yaml';
import config from 'travis/config/environment';
import { inject as service } from '@ember/service';
import {
@@ -91,7 +91,7 @@ export default Component.extend(BranchSearching, {
return {
request: {
branch,
- config: YAML.parse(triggerBuildConfig),
+ config: yaml.load(triggerBuildConfig),
message: message || undefined
}
};
diff --git a/app/components/ui-kit/button-signin.js b/app/components/ui-kit/button-signin.js
index 8fa797baef..83497165be 100644
--- a/app/components/ui-kit/button-signin.js
+++ b/app/components/ui-kit/button-signin.js
@@ -2,6 +2,7 @@ import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { or, reads } from '@ember/object/computed';
+import { capitalize } from "@ember/string";
export default Component.extend({
tagName: '',
@@ -13,7 +14,8 @@ export default Component.extend({
account: null,
isSignup: false,
- provider: or('account.provider', 'multiVcs.primaryProvider'),
+ overriddenProvider: null,
+ provider: or('overriddenProvider', 'account.provider', 'multiVcs.primaryProvider'),
isLogoVisible: true,
isLogoSeparatorVisible: true,
isBetaBadgeVisible: reads('isBetaProvider'),
@@ -22,7 +24,7 @@ export default Component.extend({
isLoading: false,
vcsType: computed('provider', function () {
- return `${this.provider.replace('-', '').capitalize()}User`;
+ return `${capitalize(this.provider.replace('-', ''))}User`;
}),
isPrimaryProvider: computed('provider', function () {
diff --git a/app/components/ui-kit/button.js b/app/components/ui-kit/button.js
index cf9f932ae1..1e9c330f4a 100644
--- a/app/components/ui-kit/button.js
+++ b/app/components/ui-kit/button.js
@@ -59,11 +59,14 @@ export default Component.extend({
width: DEFAULT_WIDTH,
invert: false,
disabled: false,
+ customBgColor: null,
onClick() {},
// Private
- bgColor: computed('color', 'disabled', 'invert', function () {
+ bgColor: computed('customBgColor', 'color', 'disabled', 'invert', function () {
+ if (this.customBgColor)
+ return this.customBgColor;
return this.invert
? BG_COLORS['invert']
: this.disabled
diff --git a/app/components/ui-kit/link.js b/app/components/ui-kit/link.js
index c53bb5a69a..3a45d59215 100644
--- a/app/components/ui-kit/link.js
+++ b/app/components/ui-kit/link.js
@@ -1,6 +1,10 @@
import Component from '@ember/component';
import { checkColor } from 'travis/utils/ui-kit/assertions';
-import { COLORS, TEXT_COLORS, DEFAULT_TEXT_COLOR } from 'travis/components/ui-kit/text';
+import {
+ COLORS,
+ TEXT_COLORS,
+ DEFAULT_TEXT_COLOR
+} from 'travis/components/ui-kit/text';
import prefix from 'travis/utils/ui-kit/prefix';
import concat from 'travis/utils/ui-kit/concat';
diff --git a/app/components/unconfirmed-user-banner.js b/app/components/unconfirmed-user-banner.js
new file mode 100644
index 0000000000..bb93d73f35
--- /dev/null
+++ b/app/components/unconfirmed-user-banner.js
@@ -0,0 +1,4 @@
+import Component from '@ember/component';
+
+export default Component.extend({
+});
diff --git a/app/components/visibility-setting-list.js b/app/components/visibility-setting-list.js
index 4fd653635c..e2e863833f 100644
--- a/app/components/visibility-setting-list.js
+++ b/app/components/visibility-setting-list.js
@@ -13,6 +13,7 @@ import {
bindKeyboardShortcuts,
unbindKeyboardShortcuts
} from 'ember-keyboard-shortcuts';
+import { A } from '@ember/array';
export default Component.extend({
classNames: ['visibility-setting-list'],
@@ -33,7 +34,7 @@ export default Component.extend({
// `displayValue` is used to generate text for the modal
// `description` is for the label next to the radio button
// `modalText` can be used to override the generated modal text
- options: computed(() => []),
+ options: [],
isEmpty: empty('options'),
isVisible: not('isEmpty'),
diff --git a/app/controllers/account/settings.js b/app/controllers/account/settings.js
index eed5cdf1d4..006492c82b 100644
--- a/app/controllers/account/settings.js
+++ b/app/controllers/account/settings.js
@@ -33,6 +33,7 @@ export default Controller.extend({
auth: service(),
preferences: service(),
flashes: service(),
+ store: service(),
queryParams: ['section'],
section: SECTION.NONE,
@@ -116,11 +117,11 @@ export default Controller.extend({
this.toggleProperty('isShowingAddKeyModal');
},
customKeyDeleted(key) {
- const keys = this.get('customKeysLoaded');
+ const keys = this.customKeysLoaded;
this.set('customKeysLoaded', keys.filter(obj => obj.id !== key.id));
},
customKeyAdded(key) {
- this.get('customKeysLoaded').pushObject(key);
+ this.customKeysLoaded.pushObject(key);
}
},
diff --git a/app/controllers/branches.js b/app/controllers/branches.js
index 59233cb07c..9429f4b987 100644
--- a/app/controllers/branches.js
+++ b/app/controllers/branches.js
@@ -2,6 +2,7 @@ import { isNone } from '@ember/utils';
import { get, computed } from '@ember/object';
import Controller, { inject as controller } from '@ember/controller';
import { alias, notEmpty, filter } from '@ember/object/computed';
+import {A} from '@ember/array';
export default Controller.extend({
repoController: controller('repo'),
@@ -34,8 +35,7 @@ export default Controller.extend({
return isNone(finishedAt);
});
- const sortedFinished = branches
- .filterBy('last_build.finished_at')
+ const sortedFinished = A(branches.filterBy('last_build.finished_at'))
.sortBy('last_build.finished_at')
.reverse();
diff --git a/app/controllers/build.js b/app/controllers/build.js
index 1cce3aae3b..0af9928ea3 100644
--- a/app/controllers/build.js
+++ b/app/controllers/build.js
@@ -13,6 +13,8 @@ export default Controller.extend(Polling, {
updateTimesService: service('updateTimes'),
repoController: controller('repo'),
+ queryParams: ['currentTab'],
+ currentTab: null,
config,
diff --git a/app/controllers/builds.js b/app/controllers/builds.js
index c31a36e3bb..d0c8fb49cd 100644
--- a/app/controllers/builds.js
+++ b/app/controllers/builds.js
@@ -13,9 +13,13 @@ export default Controller.extend(...mixins, {
externalLinks: service(),
permissions: service(),
- buildsSorting: ['number:desc'],
- builds: sort('model', 'buildsSorting'),
oldBuilds: [],
+ newElements: [],
+
+ builds: computed('model.content', 'newElements', function() {
+ console.log(this.model);
+ return (this.model.content.concat(this.newElements)).sort((e) => e.number);
+ }),
repoController: controller('repo'),
repo: alias('repoController.repo'),
@@ -35,7 +39,7 @@ export default Controller.extend(...mixins, {
hasBuildBackups: reads('repo.hasBuildBackups'),
displayShowMoreButton: computed('tab', 'loadMoreBuilds.isRunning', 'builds', function () {
- const builds = this.get('builds');
+ const builds = this.builds;
let tab = this.tab;
if (this.oldBuilds.length === builds.length) {
diff --git a/app/controllers/caches.js b/app/controllers/caches.js
index 147abf708b..f6d53cd11d 100644
--- a/app/controllers/caches.js
+++ b/app/controllers/caches.js
@@ -2,33 +2,47 @@ import EmberObject, { computed } from '@ember/object';
import Controller from '@ember/controller';
import config from 'travis/config/environment';
import { inject as service } from '@ember/service';
-import { alias } from '@ember/object/computed';
+import { alias, reads } from '@ember/object/computed';
import { task } from 'ember-concurrency';
+import {tracked} from "@glimmer/tracking";
-export default Controller.extend({
- api: service(),
- flashes: service(),
+export default class extends Controller {
+ @service api;
+ @service flashes;
- repo: alias('model.repo'),
+ config = config;
- config,
+ @tracked pushes;
+ @tracked pullRequests;
+ @tracked repo;
- cachesExist: computed('model.pushes.[]', 'model.pullRequests.[]', function () {
- let pushes = this.get('model.pushes');
- let pullRequests = this.get('model.pullRequests');
- if (pushes || pullRequests) {
- return pushes.length || pullRequests.length;
- }
- }),
+ constructor() {
+ super(...arguments);
+ }
+
+ @computed('pushes.[]', 'pullRequests.[]')
+ get cachesExist() {
+ return this.pushes?.length || this.pullRequests?.length;
+ }
- deleteRepoCache: task(function* () {
+ @task(function* () {
if (config.skipConfirmations || confirm('Are you sure?')) {
try {
- yield this.api.delete(`/repo/${this.get('repo.id')}/caches`);
- this.set('model', EmberObject.create());
+ yield this.api.delete(`/repo/${this.repo.id}/caches`);
+ this.set('pullRequests', EmberObject.create());
+ this.set('pushes', EmberObject.create());
} catch (e) {
this.flashes.error('Could not delete the caches');
}
}
}).drop()
-});
+ deleteRepoCache;
+
+ reassignPullRequests(val, component) {
+ component.set('pullRequests', val);
+ }
+
+ reassignPushes(val, component) {
+ component.set('pushes', val);
+ }
+}
diff --git a/app/controllers/dashboard.js b/app/controllers/dashboard.js
index ccc1d37e8d..bd39e460a0 100644
--- a/app/controllers/dashboard.js
+++ b/app/controllers/dashboard.js
@@ -40,7 +40,7 @@ export default Controller.extend({
'model.starredRepos.@each.currentBuildFinishedAt',
function () {
let repositories = this.get('model.starredRepos');
- return repositories.toArray().sort(dashboardRepositoriesSort);
+ return (repositories.content || []).sort(dashboardRepositoriesSort);
}
)
});
diff --git a/app/controllers/dashboard/repositories.js b/app/controllers/dashboard/repositories.js
index 9508bc561f..df3405b953 100644
--- a/app/controllers/dashboard/repositories.js
+++ b/app/controllers/dashboard/repositories.js
@@ -18,7 +18,7 @@ export default Controller.extend({
'model.starredRepos.@each.currentBuildFinishedAt',
function () {
let repositories = this.get('model.starredRepos');
- return repositories.toArray().sort(dashboardRepositoriesSort);
+ return (repositories.content || []).sort(dashboardRepositoriesSort);
}
),
diff --git a/app/controllers/index.js b/app/controllers/index.js
index fd9d6999e5..bd4005b699 100644
--- a/app/controllers/index.js
+++ b/app/controllers/index.js
@@ -19,7 +19,7 @@ export default Controller.extend({
init() {
this._super(...arguments);
if (!Ember.testing) {
- return Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
+ return Visibility.every(config.intervals.updateTimes, this.updateTimes.bind(this));
}
},
diff --git a/app/controllers/organization/settings.js b/app/controllers/organization/settings.js
index 28567531f4..d827970eda 100644
--- a/app/controllers/organization/settings.js
+++ b/app/controllers/organization/settings.js
@@ -81,7 +81,7 @@ export default Controller.extend({
},
customKeyDeleted(key) {
- const keys = this.get('customKeys');
+ const keys = this.customKeys;
this.set('model.organization.customKeys', keys.filter(obj => obj.id !== key.id));
},
diff --git a/app/controllers/repo.js b/app/controllers/repo.js
index ebc33cb8b4..617a336253 100644
--- a/app/controllers/repo.js
+++ b/app/controllers/repo.js
@@ -19,6 +19,7 @@ export default Controller.extend({
queryParams: ['migrationStatus', 'serverType'],
serverType: null,
migrationStatus: null,
+ observing: false,
jobController: controller('job'),
buildController: controller('build'),
@@ -111,11 +112,15 @@ export default Controller.extend({
},
stopObservingLastBuild() {
+ if (!this.observing)
+ return;
+ this.set('observing', false);
return this.removeObserver('repo.currentBuild', this, 'currentBuildDidChange');
},
observeLastBuild() {
this.currentBuildDidChange();
+ this.set('observing', true);
return this.addObserver('repo.currentBuild', this, 'currentBuildDidChange');
}
});
diff --git a/app/controllers/repo/index.js b/app/controllers/repo/index.js
index a2b253b71e..6a015dddf8 100644
--- a/app/controllers/repo/index.js
+++ b/app/controllers/repo/index.js
@@ -2,6 +2,7 @@ import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { reads, and, or, not } from '@ember/object/computed';
import { inject as service } from '@ember/service';
+import { A } from '@ember/array';
export default Controller.extend({
auth: service(),
diff --git a/app/helpers/commit-link.js b/app/helpers/commit-link.js
index 0ff5fe5039..43326ce816 100644
--- a/app/helpers/commit-link.js
+++ b/app/helpers/commit-link.js
@@ -1,11 +1,9 @@
-import Ember from 'ember';
-import { htmlSafe } from '@ember/string';
+import { escape } from 'travis/helpers/format-message';
+import { htmlSafe } from '@ember/template';
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import formatCommit from 'travis/utils/format-commit';
-const { escapeExpression: escape } = Ember.Handlebars.Utils;
-
export default Helper.extend({
externalLinks: service(),
@@ -24,6 +22,6 @@ export default Helper.extend({
const commitUrl = this.externalLinks.commitUrl(vcsType, { owner, repo, commit });
const url = escape(commitUrl);
const string = `${commit}`;
- return new htmlSafe(string);
+ return new htmlSafe(`${string}`);
}
});
diff --git a/app/helpers/format-commit.js b/app/helpers/format-commit.js
index b84d519277..0ee96b671b 100644
--- a/app/helpers/format-commit.js
+++ b/app/helpers/format-commit.js
@@ -1,10 +1,12 @@
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';
import formatCommit from 'travis/utils/format-commit';
export default helper((params) => {
const [commit] = params;
if (commit) {
- return new htmlSafe(formatCommit(commit.get('sha'), commit.get('branch')));
+
+ const theHtml = formatCommit(commit.get('sha'), commit.get('branch'));
+ return new htmlSafe(`${theHtml}`);
}
});
diff --git a/app/helpers/format-duration.js b/app/helpers/format-duration.js
index 726a3a76bd..763eda86f0 100644
--- a/app/helpers/format-duration.js
+++ b/app/helpers/format-duration.js
@@ -1,9 +1,9 @@
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';
import timeInWords from 'travis/utils/time-in-words';
export default helper((params) => {
const [time] = params;
const timeText = timeInWords(time);
- return new htmlSafe(timeText);
+ return new htmlSafe(`${timeText}`);
});
diff --git a/app/helpers/format-message.js b/app/helpers/format-message.js
index fcead42df9..c0efd16aba 100644
--- a/app/helpers/format-message.js
+++ b/app/helpers/format-message.js
@@ -1,5 +1,5 @@
import { helper } from '@ember/component/helper';
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { get } from '@ember/object';
import EmojiConvertor from 'emoji-js';
@@ -12,8 +12,8 @@ emojiConvertor.img_sets.apple.path = `${config.emojiPrepend}/images/emoji/`;
emojiConvertor.include_title = true;
emojiConvertor.allow_native = false;
-function escape(text) {
- return text
+export function escape(text) {
+ return text.toString()
.replace(/&/g, '&')
.replace(//g, '>');
@@ -68,6 +68,7 @@ function formatMessage(message, options) {
message = handleEventType(message, options.eventType);
message = emojiConvertor.replace_colons(message);
+ console.log(message);
return message;
}
@@ -130,5 +131,5 @@ export default helper((params, options) => {
const message = params[0] || '';
const formattedMessage = formatMessage(message, options);
- return new htmlSafe(formattedMessage);
+ return new htmlSafe(`${formattedMessage}`);
});
diff --git a/app/helpers/format-sha.js b/app/helpers/format-sha.js
index 157ad312eb..7198d32b15 100644
--- a/app/helpers/format-sha.js
+++ b/app/helpers/format-sha.js
@@ -1,4 +1,4 @@
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';
import formatSha from 'travis/utils/format-sha';
@@ -6,5 +6,6 @@ export default helper((params) => {
let [sha] = params;
if (sha && sha.includes('@')) sha = sha.split('@')[1];
const formattedSha = formatSha(sha);
- return new htmlSafe(formattedSha);
+
+ return new htmlSafe(`${formattedSha}`);
});
diff --git a/app/helpers/format-time.js b/app/helpers/format-time.js
index d6f2829d42..aa1c049e29 100644
--- a/app/helpers/format-time.js
+++ b/app/helpers/format-time.js
@@ -1,9 +1,10 @@
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';
import timeAgoInWords from 'travis/utils/time-ago-in-words';
export default helper((params) => {
const [time] = params;
const timeText = timeAgoInWords(time) || '-';
- return new htmlSafe(timeText);
+
+ return new htmlSafe(`${timeText}`);
});
diff --git a/app/helpers/pretty-date.js b/app/helpers/pretty-date.js
index 0ec5f6234c..5811a19f2f 100644
--- a/app/helpers/pretty-date.js
+++ b/app/helpers/pretty-date.js
@@ -1,4 +1,4 @@
-import { htmlSafe } from '@ember/string';
+import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';
@@ -6,7 +6,9 @@ import moment from 'moment';
export function prettyDate(params) {
let date = new Date(params[0]);
- return new htmlSafe(moment(date).format('MMMM D, YYYY H:mm:ss') || '-');
+ const theMoment = moment(date).format('MMMM D, YYYY H:mm:ss') || '-';
+
+ return new htmlSafe(`${theMoment}`);
}
export default helper(prettyDate);
diff --git a/app/index.html b/app/index.html
index 54cfe0c9ca..f6be457f69 100644
--- a/app/index.html
+++ b/app/index.html
@@ -12,6 +12,9 @@
{{content-for "head"}}
+
+
+
diff --git a/app/initializers/array.js b/app/initializers/array.js
new file mode 100644
index 0000000000..b05adf327f
--- /dev/null
+++ b/app/initializers/array.js
@@ -0,0 +1,160 @@
+import { A } from '@ember/array'
+
+export function initialize() {
+ if (!Array.prototype.compact) {
+ Array.prototype.compact = function(...params) {
+ return A(this).compact(...params)
+ };
+ }
+
+ if (!Array.prototype.removeObject) {
+ Array.prototype.removeObject = function(item) {
+ const index = this.indexOf(item);
+ if (index !== -1) {
+ this.splice(index, 1);
+ }
+ };
+ }
+
+ if (!Array.prototype.firstObject) {
+ Object.defineProperty(Array.prototype, 'firstObject', {
+ get: function() {
+ return this[0];
+ }
+ });
+ }
+
+ if (!Array.prototype.lastObject) {
+ Object.defineProperty(Array.prototype, 'lastObject', {
+ get: function() {
+ return this[this.length - 1];
+ }
+ });
+ }
+
+ if (!Array.prototype.pushObject) {
+ Array.prototype.pushObject = function(item) {
+ this.push(item);
+ return this.length;
+ };
+ }
+
+ if (!Array.prototype.uniq) {
+ Array.prototype.uniq = function() {
+ return Array.from(new Set(this));
+ };
+ }
+
+ if (!Array.prototype.addObject) {
+ Array.prototype.addObject = function (item) {
+ if (this.indexOf(item) === -1) {
+ this.push(item);
+ }
+ return this;
+ };
+ }
+
+ if (!Array.prototype.any) {
+ Array.prototype.any = function(...args) {
+ return this.some(...args);
+ }
+ }
+
+
+ if (!Array.prototype.mapBy) {
+ Array.prototype.mapBy = function(property) {
+ return this.map(item => item[property]);
+ };
+ }
+
+ if (!Array.prototype.addObjects) {
+ Array.prototype.addObjects = function(items) {
+ items.forEach(item => {
+ this.addObject(item);
+ });
+ return this;
+ };
+ }
+
+ if (!Array.prototype.filterBy) {
+ Array.prototype.filterBy = function(...params) {
+ return A(this).filterBy(...params);
+ }
+ }
+
+ if (!Array.prototype.get) {
+ Array.prototype.get = function (what) {
+ let properties = what.split('.');
+ let result = A(this);
+
+ for (let i = 0; i < properties.length; i++) {
+ // 5 is my own limit so can be .get[aa.bb.cc.dd.ee] - max nesting of 5
+ if (result === undefined || result === null || i === 5) {
+ return undefined;
+ }
+ result = result[properties[i]];
+ }
+
+ return result;
+ }
+ }
+
+ if (!Array.prototype.isAny) {
+ Array.prototype.isAny = function(...params) {
+ return A(this).isAny(...params)
+ }
+ }
+
+ if (!Array.prototype.findBy) {
+ Array.prototype.findBy = function(...params) {
+ return A(this).findBy(...params)
+ }
+ }
+
+ if (!Array.prototype.uniqBy) {
+ Array.prototype.uniqBy = function(...params) {
+ return A(this).uniqBy(...params)
+ }
+ }
+
+ if (!Array.prototype.unshiftObject) {
+ Array.prototype.unshiftObject = function(...params) {
+ return A(this).unshiftObject(...params)
+ }
+ }
+
+ if (!Array.prototype.pushObjects) {
+ Array.prototype.pushObjects = function(...objects) {
+ this.push(...objects);
+ }
+
+ return this.length;
+ }
+
+ if (!Array.prototype.without) {
+ Array.prototype.without = function(...params) {
+ return A(this).without(...params)
+ }
+ }
+
+
+ Array.prototype.sort = function(...params) {
+ return A(this).sort(...params)
+ }
+
+ if (!Array.prototype.rejectBy) {
+ Array.prototype.rejectBy = function(...params) {
+ return A(this).rejectBy(...params)
+ }
+ }
+
+ if (!Array.prototype.sortBy) {
+ Array.prototype.sortBy = function(...params) {
+ return A(this).sortBy(...params)
+ }
+ }
+}
+
+export default {
+ initialize
+};
diff --git a/app/initializers/pretender.js b/app/initializers/pretender.js
new file mode 100644
index 0000000000..08afb46385
--- /dev/null
+++ b/app/initializers/pretender.js
@@ -0,0 +1,25 @@
+import Pretender from 'pretender';
+import config from 'travis/config/environment';
+
+const { validAuthToken } = config;
+
+const pretender = {
+ name: 'pretender',
+
+ initialize: function () {
+ const originalHandlerFor = Pretender.prototype._handlerFor;
+
+ Pretender.prototype._handlerFor = function (verb, path, request) {
+ const authHeader = request.requestHeaders.Authorization;
+ if (authHeader && authHeader !== `token ${validAuthToken}`) {
+ // Handle unauthorized case
+ return originalHandlerFor.call(this, 'GET', '/unauthorized', request);
+ }
+
+ // Proceed with original behavior
+ return originalHandlerFor.apply(this, arguments);
+ };
+ }
+}
+
+export default pretender;
diff --git a/app/initializers/store.js b/app/initializers/store.js
new file mode 100644
index 0000000000..47aff8911b
--- /dev/null
+++ b/app/initializers/store.js
@@ -0,0 +1,10 @@
+// app/initializers/store-initializer.js
+import ExtendedStore from 'travis/services/store'; // Adjust the path to your ExtendedStore
+
+export function initialize(application) {
+ // application.register('service:store', ExtendedStore, { singleton: true, instantiate: true });
+}
+
+export default {
+ initialize
+};
diff --git a/app/instance-initializers/pusher.js b/app/instance-initializers/pusher.js
index a662711b71..fe2ffb8543 100644
--- a/app/instance-initializers/pusher.js
+++ b/app/instance-initializers/pusher.js
@@ -10,8 +10,6 @@ export function initialize(applicationInstance) {
instantiate: false
});
}
- app.inject('route', 'pusher', 'pusher:main');
- app.inject('component', 'pusher', 'pusher:main');
app.pusher.store = applicationInstance.lookup('service:store');
app.pusher.pusherService = applicationInstance.lookup('service:pusher');
}
diff --git a/app/mixins/builds/load-more.js b/app/mixins/builds/load-more.js
index 73cd02aa56..4605e0ced6 100644
--- a/app/mixins/builds/load-more.js
+++ b/app/mixins/builds/load-more.js
@@ -4,6 +4,7 @@ import { task } from 'ember-concurrency';
export default Mixin.create({
tabStates: service(),
+ store: service(),
loadMoreBuilds: task(function* () {
let number = this.get('builds.lastObject.number');
@@ -30,7 +31,7 @@ export default Mixin.create({
const singularTab = tabName.substr(0, tabName.length - 1);
const type = tabName === 'builds' ? 'push' : singularTab;
const options = this._constructOptions(type);
- yield this.store.query('build', options);
+ this.set('newElements', this.newElements.concat(yield this.store.query('build', options)));
}).drop(),
_constructOptions(type) {
diff --git a/app/mixins/components/with-config-validation.js b/app/mixins/components/with-config-validation.js
index e4b710bf3b..bd79eb8e86 100644
--- a/app/mixins/components/with-config-validation.js
+++ b/app/mixins/components/with-config-validation.js
@@ -2,6 +2,7 @@ import Mixin from '@ember/object/mixin';
import { and, gt, reads } from '@ember/object/computed';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
+import { A } from '@ember/array';
export default Mixin.create({
auth: service(),
@@ -17,7 +18,7 @@ export default Mixin.create({
messagesMaxLevel: computed('messages.@each.level', function () {
if (this.hasMessages) {
- return this.messages.sortBy('level').lastObject.level;
+ return A(this.messages).sortBy('level').lastObject.level;
}
}),
diff --git a/app/models/allowance.js b/app/models/allowance.js
index 664f7ca7ee..2e892686c5 100644
--- a/app/models/allowance.js
+++ b/app/models/allowance.js
@@ -12,5 +12,16 @@ export default Model.extend({
creditCardBlockDuration: attr('number'),
captchaBlockDuration: attr('number'),
- owner: belongsTo('owner')
+ owner: {
+ name: 'owner',
+ type: 'owner',
+ kind: 'belongsTo',
+ options: {
+ as: 'allowance',
+ async: true,
+ polymorphic: true,
+ inverse: 'allowance'
+ }
+ },
+ organisation: belongsTo('organization', { inverse: 'allowances' })
});
diff --git a/app/models/beta-feature.js b/app/models/beta-feature.js
index 602adb1a64..b3e0725840 100644
--- a/app/models/beta-feature.js
+++ b/app/models/beta-feature.js
@@ -1,5 +1,5 @@
import Model, { attr } from '@ember-data/model';
-import { dasherize } from '@ember/string';
+import { dasherize, capitalize } from '@ember/string';
import { computed } from '@ember/object';
export default Model.extend({
@@ -15,7 +15,7 @@ export default Model.extend({
displayName: computed('dasherizedName', function () {
return this.dasherizedName
.split('-')
- .map(x => x.capitalize())
+ .map(x => capitalize(x))
.join(' ');
})
});
diff --git a/app/models/build.js b/app/models/build.js
index b52e616ca3..56ab1bd34a 100644
--- a/app/models/build.js
+++ b/app/models/build.js
@@ -134,7 +134,9 @@ export default Model.extend(DurationCalculations, {
return !isEmpty(jobs.filterBy('canCancel'));
}),
- canRestart: alias('isFinished'),
+ canRestart: computed('isFinished', function () {
+ return this.isFinished;
+ }),
cancel() {
const url = `/build/${this.id}/cancel`;
diff --git a/app/models/commit.js b/app/models/commit.js
index 5f06f74a3a..0a1c5e2684 100644
--- a/app/models/commit.js
+++ b/app/models/commit.js
@@ -53,7 +53,7 @@ export default Model.extend({
const owner = this.get('build.repo.ownerName');
const repo = this.get('build.repo.vcsName');
const vcsType = this.get('build.repo.vcsType');
- const commit = this.get('sha');
+ const commit = this.sha;
return this.externalLinks.commitUrl(vcsType, { owner, repo, commit });
}),
diff --git a/app/models/job.js b/app/models/job.js
index 4d07b6fe1e..3a770b6b1a 100644
--- a/app/models/job.js
+++ b/app/models/job.js
@@ -2,7 +2,7 @@
import Model, { attr, belongsTo } from '@ember-data/model';
import { observer, computed } from '@ember/object';
-import { alias, and, equal, not, reads } from '@ember/object/computed';
+import { alias, and, equal, reads } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { isEqual } from '@ember/utils';
import { getOwner } from '@ember/application';
@@ -154,11 +154,13 @@ export default Model.extend(DurationCalculations, DurationAttributes, {
canCancel: computed('isFinished', 'state', function () {
let isFinished = this.isFinished;
let state = this.state;
- // not(isFinished) is insufficient since it will be true when state is undefined.
return !isFinished && !!state;
}),
- canRestart: alias('isFinished'),
+ canRestart: computed('isFinished', function () {
+ let isFinished = this.isFinished;
+ return isFinished;
+ }),
canDebug: and('isFinished', 'repo.private'),
cancel() {
@@ -234,16 +236,14 @@ export default Model.extend(DurationCalculations, DurationAttributes, {
}
}),
- canRemoveLog: not('log.removed'),
+ canRemoveLog: computed('log.removed', function () {
+ let removed = !!this.log.removed;
+ return !removed;
+ }),
slug: computed('repo.slug', 'number', function () {
let slug = this.get('repo.slug');
let number = this.number;
return `${slug} #${number}`;
}),
-
- didLoad() {
- if (this.number)
- this.set('jobIdNumber', this.number);
- }
});
diff --git a/app/models/log.js b/app/models/log.js
index a9b4f752af..066802ad01 100644
--- a/app/models/log.js
+++ b/app/models/log.js
@@ -9,6 +9,7 @@ export default EmberObject.extend({
features: service(),
auth: service(),
storage: service(),
+ logToLogContent: service(),
version: 0,
length: 0,
@@ -68,7 +69,11 @@ export default EmberObject.extend({
this.noRendering) {
return;
}
- return this.parts.pushObject(part);
+
+
+ const pushed = this.parts.pushObject(part);
+ this.logToLogContent.logContent.partsDidChange(this.parts, this.parts.length -1, null, this.parts.length)
+ return pushed;
},
loadParts(parts) {
diff --git a/app/models/organization.js b/app/models/organization.js
index 97d393f719..267a9e8e4f 100644
--- a/app/models/organization.js
+++ b/app/models/organization.js
@@ -1,13 +1,24 @@
import Owner from 'travis/models/owner';
-import { attr } from '@ember-data/model';
import { reads } from '@ember/object/computed';
import { task } from 'ember-concurrency';
import { computed } from '@ember/object';
+import { attr, hasMany } from '@ember-data/model';
export default Owner.extend({
type: 'organization',
allowMigration: attr('boolean'),
customKeys: attr(),
+ allowance: {
+ name: 'allowance',
+ type: 'allowance',
+ kind: 'belongsTo',
+ options: {
+ as: 'owner',
+ async: true,
+ polymorphic: false,
+ inverse: 'owner'
+ }
+ },
buildPermissions: reads('fetchBuildPermissions.lastSuccessful.value'),
diff --git a/app/models/owner.js b/app/models/owner.js
index 99164198d5..d3ab44864d 100644
--- a/app/models/owner.js
+++ b/app/models/owner.js
@@ -131,7 +131,8 @@ export default VcsEntity.extend({
},
migrationBetaRequests: computed('tasks.fetchBetaMigrationRequestsTask.lastSuccessful.value.[]', 'login', function () {
- const requests = this.tasks.fetchBetaMigrationRequestsTask.get('lastSuccessful.value') || [];
+ const lastSuccessful = this.tasks.fetchBetaMigrationRequestsTask.lastSuccessful;
+ const requests = lastSuccessful && lastSuccessful.value || [];
return requests.filter(request =>
this.isUser && request.ownerName == this.login || request.organizations.mapBy('login').includes(this.login)
);
diff --git a/app/models/plan.js b/app/models/plan.js
index db6812e41d..58cca82f4b 100644
--- a/app/models/plan.js
+++ b/app/models/plan.js
@@ -1,4 +1,4 @@
-import Model, { attr } from '@ember-data/model';
+import Model, { attr, hasMany } from '@ember-data/model';
import { equal } from '@ember/object/computed';
export default Model.extend({
@@ -13,5 +13,6 @@ export default Model.extend({
isEnabled: attr('boolean'),
isDefault: attr('boolean'),
isAnnual: attr('boolean'),
- isFree: equal('price', 0)
+ isFree: equal('price', 0),
+ subscriptions: hasMany('subscription')
});
diff --git a/app/models/repo.js b/app/models/repo.js
index 41bb6d98f2..2b5fa1d41f 100644
--- a/app/models/repo.js
+++ b/app/models/repo.js
@@ -31,6 +31,7 @@ const Repo = VcsEntity.extend({
auth: service(),
features: service(),
store: service(),
+ tasks: service(),
permissions: attr(),
slug: attr('string'),
@@ -53,7 +54,7 @@ const Repo = VcsEntity.extend({
serverType: attr('string', { defaultValue: 'git' }),
currentScan: computed('scanFailedAt', function () {
- let scanFailedAt = this.get('scanFailedAt');
+ let scanFailedAt = this.scanFailedAt;
return {
icon: scanFailedAt ? 'errored' : 'passed',
state: scanFailedAt ? 'issue' : 'passed'
@@ -91,14 +92,7 @@ const Repo = VcsEntity.extend({
return this.repoOwnerAllowance;
}),
- repoOwnerAllowance: reads('fetchRepoOwnerAllowance.lastSuccessful.value'),
-
- fetchRepoOwnerAllowance: task(function* () {
- const allowance = this.store.peekRecord('allowance', this.owner.id);
- if (allowance)
- return allowance;
- return yield this.store.queryRecord('allowance', { login: this.owner.login, provider: this.provider });
- }).drop(),
+ repoOwnerAllowance: reads('tasks.fetchRepoOwnerAllowance.lastSuccessful.value'),
buildPermissions: reads('fetchBuildPermissions.lastSuccessful.value'),
@@ -132,7 +126,7 @@ const Repo = VcsEntity.extend({
return false;
const isPro = this.get('features.proVersion');
const enterprise = !!this.get('features.enterpriseVersion');
- const roMode = this.get('owner').ro_mode || false;
+ const roMode = this.owner.ro_mode || false;
if (!isPro || enterprise) {
return !roMode;
@@ -182,7 +176,7 @@ const Repo = VcsEntity.extend({
urlOwnerName: computed('slug', function () {
const { slug = '', ownerName } = this;
- return slug.split('/').firstObject || ownerName;
+ return slug.split('/') && slug.split('/').firstObject || ownerName;
}),
formattedSlug: computed('owner.login', 'name', function () {
@@ -193,7 +187,7 @@ const Repo = VcsEntity.extend({
sshKey: function () {
this.store.find('ssh_key', this.id);
- return this.store.recordForId('ssh_key', this.id);
+ return this.store.findRecord('ssh_key', this.id);
},
envVars: computed('id', function () {
@@ -211,6 +205,9 @@ const Repo = VcsEntity.extend({
fetchSettings: task(function* () {
if (!this.auth.signedIn) return {};
+
+ const hasPermissions = this.permissions.settings_read;
+ if (hasPermissions === false) return {};
try {
const response = yield this.api.get(`/repo/${this.id}/settings`);
return this._convertV3SettingsToV2(response.settings);
@@ -240,7 +237,7 @@ const Repo = VcsEntity.extend({
}, (b) => {
let eventTypes = ['push', 'api', 'cron'];
return this._buildRepoMatches(b, id) && eventTypes.includes(b.get('eventType'));
- });
+ }, [''], true);
return this._buildObservableArray(builds);
}),
@@ -277,9 +274,9 @@ const Repo = VcsEntity.extend({
}),
cronJobs: computed('id', 'fetchCronJobs.lastSuccessful.value', function () {
- const crons = this.fetchCronJobs.get('lastSuccessful.value');
+ const crons = this.fetchCronJobs.lastSuccessful && this.fetchCronJobs.lastSuccessful.value;
if (!crons) {
- this.get('fetchCronJobs').perform();
+ this.fetchCronJobs.perform();
}
return crons || [];
}),
@@ -434,7 +431,7 @@ Repo.reopenClass({
if (!isEmpty(loadedRepos)) {
return EmberPromise.resolve(loadedRepos.firstObject);
}
- return store.queryRecord('repo', { slug, provider, serverType });
+ return store.smartQueryRecord('repo', { slug, provider, serverType });
},
});
diff --git a/app/models/request.js b/app/models/request.js
index 63b4b59733..ca724e122b 100644
--- a/app/models/request.js
+++ b/app/models/request.js
@@ -38,6 +38,7 @@ export default Model.extend({
build: belongsTo('build', { async: true }),
api: service(),
+ tasks: service(),
isAccepted: computed('result', 'build.id', function () {
// For some reason some of the requests have a null result beside the fact that
@@ -57,22 +58,13 @@ export default Model.extend({
isDraft: equal('pullRequestMergeable', PULL_REQUEST_MERGEABLE.DRAFT),
- messages: computed('repo.id', 'build.request.id', 'fetchMessages.last.value', function () {
- const messages = this.fetchMessages.get('lastSuccessful.value');
+ messages: computed('repo.id', 'build.request.id', 'tasks.fetchMessages.last.value', function () {
+ const messages = this.tasks.fetchMessages.lastSuccessful.value;
if (!messages) {
- this.fetchMessages.perform();
+ this.tasks.fetchMessages.perform();
}
return messages || [];
}),
- fetchMessages: task(function* () {
- const repoId = this.get('repo.id');
- const requestId = this.get('build.request.id');
- if (repoId && requestId) {
- const response = yield this.api.get(`/repo/${repoId}/request/${requestId}/messages`) || {};
- return response.messages;
- }
- }).drop(),
-
hasMessages: gt('messages.length', 0),
});
diff --git a/app/models/subscription.js b/app/models/subscription.js
index 241233fde6..573fb76e36 100644
--- a/app/models/subscription.js
+++ b/app/models/subscription.js
@@ -15,6 +15,7 @@ let sourceToWords = {
export default Model.extend({
api: service(),
accounts: service(),
+ store: service(),
source: attr(),
status: attr(),
@@ -32,7 +33,7 @@ export default Model.extend({
creditCardInfo: belongsTo('credit-card-info', { async: false }),
invoices: hasMany('invoice'),
owner: belongsTo('owner', { polymorphic: true }),
- plan: belongsTo(),
+ plan: belongsTo('plan'),
isSubscribed: equal('status', 'subscribed'),
isCanceled: equal('status', 'canceled'),
diff --git a/app/models/user.js b/app/models/user.js
index 91c7f31402..a63d1fc283 100644
--- a/app/models/user.js
+++ b/app/models/user.js
@@ -12,6 +12,7 @@ export default Owner.extend({
accounts: service(),
permissionsService: service('permissions'),
wizardStateService: service('wizardState'),
+ store: service(),
email: attr('string'),
emails: attr(), // list of all known user emails
@@ -73,6 +74,13 @@ export default Owner.extend({
}
},
+ hasPermissionToRepo(repo, permission) {
+ let permissions = repo.get ? repo.get('permissions') : null;
+ if (permissions) {
+ return permissions[permission] || false;
+ }
+ },
+
sync(isOrganization) {
this.set('isSyncing', true);
this.set('applyFilterRepos', !isOrganization);
@@ -114,7 +122,8 @@ export default Owner.extend({
reload(options = {}) {
const { authToken } = this;
- return this.store.queryRecord('user', Object.assign({}, options, { current: true, authToken }));
+ const queryParams = Object.assign({}, options, { current: true, authToken });
+ return this.store.smartQueryRecord('user', queryParams);
},
applyReposFilter() {
diff --git a/app/models/v2-billing-info.js b/app/models/v2-billing-info.js
index 4089457796..d56f97118b 100644
--- a/app/models/v2-billing-info.js
+++ b/app/models/v2-billing-info.js
@@ -12,7 +12,13 @@ export default Model.extend({
country: attr('string'),
vatId: attr('string'),
billingEmail: attr('string'),
+ billingEmailRead: attr('string'),
+
hasLocalRegistration: attr('boolean'),
- subscription: belongsTo('v2-subscription')
+ subscription: belongsTo('v2-subscription'),
+
+ didUpdate() {
+ this.setAttribute('billingEmailRead', this.billingEmail)
+ }
});
diff --git a/app/models/v2-plan-config.js b/app/models/v2-plan-config.js
index a7d762a7d9..ddca7cd6ae 100644
--- a/app/models/v2-plan-config.js
+++ b/app/models/v2-plan-config.js
@@ -31,13 +31,14 @@ export default Model.extend({
addonConfigs: attr(),
hasCreditAddons: computed('addonConfigs', 'addonConfigs.@each.type', function () {
- return this.addonConfigs.filter(addon => addon.type === 'credit_private').length > 0;
+
+ return (this.addonConfigs || []).filter(addon => addon.type === 'credit_private').length > 0;
}),
hasOSSCreditAddons: computed('addonConfigs', 'addonConfigs.@each.type', function () {
- return this.addonConfigs.filter(addon => addon.type === 'credit_public').length > 0;
+ return (this.addonConfigs || []).filter(addon => addon.type === 'credit_public').length > 0;
}),
hasUserLicenseAddons: computed('addonConfigs', 'addonConfigs.@each.type', function () {
- return this.addonConfigs.filter(addon => addon.type === 'user_license').length > 0;
+ return (this.addonConfigs || []).filter(addon => addon.type === 'user_license').length > 0;
}),
hasCredits: or('hasCreditAddons', 'hasOSSCreditAddons'),
diff --git a/app/models/v2-subscription.js b/app/models/v2-subscription.js
index 4742eb4de5..c11a9fa384 100644
--- a/app/models/v2-subscription.js
+++ b/app/models/v2-subscription.js
@@ -14,6 +14,7 @@ let sourceToWords = {
export default Model.extend({
api: service(),
accounts: service(),
+ store: service(),
source: attr('string'),
status: attr('string'),
@@ -200,25 +201,25 @@ export default Model.extend({
yield this.api.patch(`/v2_subscription/${this.id}/changetofree`, {
data: { reason, reason_details: details }
});
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
changePlan: task(function* (plan, coupon) {
const data = { plan, coupon };
yield this.api.patch(`/v2_subscription/${this.id}/plan`, { data });
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
buyAddon: task(function* (addon) {
yield this.api.post(`/v2_subscription/${this.id}/addon/${addon.id}`);
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
autoRefillToggle: task(function* (ownerId, value) {
const data = { enabled: value };
yield this.api.patch(`/v2_subscription/${this.id}/auto_refill`, { data });
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
autoRefillAddonId: reads('auto_refill.addon_id'),
autoRefillEnabled: reads('auto_refill.enabled'),
@@ -230,13 +231,13 @@ export default Model.extend({
const data = { addon_id: this.autoRefillAddonId, threshold: parseInt(threshold), amount: parseInt(amount) };
yield this.api.patch(`/v2_subscription/${this.id}/update_auto_refill`, { data });
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
cancelSubscription: task(function* (data) {
yield this.api.post(`/v2_subscription/${this.id}/cancel`, {
data
});
- yield this.accounts.fetchV2Subscriptions.perform();
+ this.accounts.fetchV2Subscriptions.perform();
}).drop(),
});
diff --git a/app/router.js b/app/router.js
index 23a17a84f1..9f655041cf 100644
--- a/app/router.js
+++ b/app/router.js
@@ -1,5 +1,5 @@
import EmberRouter from '@ember/routing/router';
-import config from './config/environment';
+import config from 'travis/config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
@@ -63,6 +63,10 @@ Router.map(function () {
});
this.route('repo', { path: '/:provider/:owner/:name' }, function () {
+ this.route('active-on-org');
+ this.route('not-active');
+ this.route('no-build');
+
this.route('index', { path: '/' });
this.route('branches', { path: '/branches', resetNamespace: true });
this.route('builds', { path: '/builds', resetNamespace: true });
diff --git a/app/routes/account/billing.js b/app/routes/account/billing.js
index 5095052323..afa115e9ec 100644
--- a/app/routes/account/billing.js
+++ b/app/routes/account/billing.js
@@ -7,5 +7,5 @@ export default TravisRoute.extend(AccountBillingMixin, {
return hash({
account: this.modelFor('account'),
});
- }
+ },
});
diff --git a/app/routes/application.js b/app/routes/application.js
index 32f906b9aa..d3b8211343 100644
--- a/app/routes/application.js
+++ b/app/routes/application.js
@@ -8,8 +8,10 @@ import {
bindKeyboardShortcuts,
unbindKeyboardShortcuts
} from 'ember-keyboard-shortcuts';
+import {asObservableArray} from "travis/utils/observable_array";
export default TravisRoute.extend(BuildFaviconMixin, {
+ store: service(),
auth: service(),
features: service(),
featureFlags: service(),
@@ -43,7 +45,7 @@ export default TravisRoute.extend(BuildFaviconMixin, {
}
if (this.auth.signedIn) {
this.wizard.fetch.perform().then(() => { this.storage.wizardStep = this.wizard.state; });
- return this.get('featureFlags.fetchTask').perform();
+ return this.get('featureFlags.fetchTask').unlinked().perform();
}
},
@@ -62,19 +64,26 @@ export default TravisRoute.extend(BuildFaviconMixin, {
// visitor is subscribed to all of the public repos in the store as long as
// they're not a collaborator. It also sets up an observer to subscribe to any
// new repo that enters the store.
+
setupRepoSubscriptions() {
- this.store.filter('repo', null,
- (repo) => !repo.get('private') && !repo.get('isCurrentUserACollaborator'),
- ['private', 'isCurrentUserACollaborator']
- ).then((repos) => {
- repos.forEach(repo => this.subscribeToRepo(repo));
- repos.addArrayObserver(this, {
+ this.store.filter('repo', null, (repo) => {
+ return !repo.get('private') && !repo.get('isCurrentUserACollaborator');
+ }).then((repos) => {
+ let plainRepos = []
+ repos.forEach(repo => {
+ this.subscribeToRepo(repo)
+ plainRepos.push(repo)
+ });
+ plainRepos = asObservableArray(plainRepos);
+ this.set('repos', plainRepos);
+ plainRepos.addArrayObserver(this, {
willChange: 'reposWillChange',
didChange: 'reposDidChange'
});
});
},
+
reposWillChange(array, start, removedCount, addedCount) {
let removedRepos = array.slice(start, start + removedCount);
return removedRepos.forEach(repo => this.unsubscribeFromRepo(repo));
@@ -86,14 +95,14 @@ export default TravisRoute.extend(BuildFaviconMixin, {
},
unsubscribeFromRepo: function (repo) {
- if (this.pusher && repo) {
- this.pusher.unsubscribe(`repo-${repo.get('id')}`);
+ if (Travis.pusher && repo) {
+ Travis.pusher.unsubscribe(`repo-${repo.get('id')}`);
}
},
subscribeToRepo: function (repo) {
- if (this.pusher) {
- this.pusher.subscribe(`repo-${repo.get('id')}`);
+ if (Travis.pusher) {
+ Travis.pusher.subscribe(`repo-${repo.get('id')}`);
}
},
diff --git a/app/routes/branches.js b/app/routes/branches.js
index 036da1af8e..ec650fe03e 100644
--- a/app/routes/branches.js
+++ b/app/routes/branches.js
@@ -7,9 +7,10 @@ export default TravisRoute.extend({
tabStates: service(),
api: service(),
auth: service(),
+ tasks: service(),
model() {
- const repoId = this.modelFor('repo').get('id');
+ const repoId = this.modelFor('repo').id;
let allTheBranches = ArrayProxy.create();
const path = `/repo/${repoId}/branches`;
@@ -32,7 +33,7 @@ export default TravisRoute.extend({
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
}
});
diff --git a/app/routes/build.js b/app/routes/build.js
index d6494d4b06..b5d44ab86f 100644
--- a/app/routes/build.js
+++ b/app/routes/build.js
@@ -3,6 +3,8 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
tabStates: service(),
+ store: service(),
+ tasks: service(),
titleToken(model) {
return `Build #${model.get('number')}`;
@@ -17,16 +19,15 @@ export default TravisRoute.extend({
setupController(controller, model) {
if (model && !model.get) {
- model = this.store.recordForId('build', model);
+ model = this.store.findRecord('build', model);
this.store.find('build', model);
}
+ const currentTab = controller.currentTab
+ this.tabStates.setMainTab(currentTab || 'build');
const repo = this.controllerFor('repo');
controller.set('build', model);
- return repo.activate('build');
- },
-
- activate() {
- this.set('tabStates.mainTab', 'build');
+ if (!currentTab)
+ return repo.activate('build');
},
model(params) {
@@ -34,15 +35,15 @@ export default TravisRoute.extend({
},
afterModel(model) {
- const slug = this.modelFor('repo').get('slug');
+ const slug = this.modelFor('repo').slug;
this.ensureBuildOwnership(model, slug);
- return model.get('request').then(request => request && request.fetchMessages.perform());
+ return model.get('request').then(request => request && this.tasks.fetchMessages.perform(request));
},
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
},
diff --git a/app/routes/build/config.js b/app/routes/build/config.js
index c0cdba8d57..27e3fd7834 100644
--- a/app/routes/build/config.js
+++ b/app/routes/build/config.js
@@ -1,13 +1,15 @@
import TravisRoute from 'travis/routes/basic';
+import { inject as service } from '@ember/service'
export default TravisRoute.extend({
titleToken: 'Config',
+ tasks: service(),
model() {
- return this.modelFor('build').get('request');
+ return this.modelFor('build').request;
},
afterModel(request) {
- return request.fetchMessages.perform();
+ return this.tasks.fetchMessages.perform(request);
}
});
diff --git a/app/routes/builds.js b/app/routes/builds.js
index 13e2f929db..4c16144956 100644
--- a/app/routes/builds.js
+++ b/app/routes/builds.js
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
tabStates: service(),
auth: service(),
+ tasks: service(),
activate(...args) {
this._super(args);
@@ -19,13 +20,13 @@ export default TravisRoute.extend({
},
model() {
- return this.modelFor('repo').get('builds');
+ return this.modelFor('repo').builds;
},
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
}
});
diff --git a/app/routes/caches.js b/app/routes/caches.js
index 7e08519180..6e063dc93c 100644
--- a/app/routes/caches.js
+++ b/app/routes/caches.js
@@ -6,11 +6,24 @@ export default TravisRoute.extend({
needsAuth: true,
- setupController(/* controller*/) {
+ setupController(controller, model) {
this._super(...arguments);
+ controller.pushes = model.pushes;
+ controller.pullRequests = model.pullRequests;
+ controller.repo = this.modelFor('repo');
+
return this.controllerFor('repo').activate('caches');
},
+ beforeModel() {
+ const repo = this.modelFor('repo');
+ if (!repo.permissions?.cache_view) {
+ this.transitionTo('repo.index');
+ this.flashes.error('Your permissions are insufficient to access this repository\'s cache');
+ }
+ },
+
+
model() {
const repo = this.modelFor('repo');
const url = `/repo/${repo.get('id')}/caches`;
diff --git a/app/routes/dashboard.js b/app/routes/dashboard.js
index 12097e9e72..1c0e29ee68 100644
--- a/app/routes/dashboard.js
+++ b/app/routes/dashboard.js
@@ -7,6 +7,7 @@ export default TravisRoute.extend({
features: service(),
accounts: service(),
+ store: service(),
model(params) {
return hash({
diff --git a/app/routes/dashboard/builds.js b/app/routes/dashboard/builds.js
index bce6b1b078..96b08c9af6 100644
--- a/app/routes/dashboard/builds.js
+++ b/app/routes/dashboard/builds.js
@@ -3,6 +3,7 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
auth: service(),
+ store: service(),
model(params) {
let currentUserId = this.get('auth.currentUser.id');
diff --git a/app/routes/dashboard/repositories.js b/app/routes/dashboard/repositories.js
index 7586a16f05..528fe2a59d 100644
--- a/app/routes/dashboard/repositories.js
+++ b/app/routes/dashboard/repositories.js
@@ -7,6 +7,7 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
features: service(),
accounts: service(),
+ store: service(),
queryParams: {
page: {
diff --git a/app/routes/index.js b/app/routes/index.js
index 1e35582300..9e3a937e14 100644
--- a/app/routes/index.js
+++ b/app/routes/index.js
@@ -32,7 +32,7 @@ export default Route.extend({
},
activate(...args) {
- this._super(args);
+ this._super(...args);
if (this.get('auth.signedIn')) {
this.tabStates.set('sidebarTab', 'owned');
this.set('tabStates.mainTab', 'current');
diff --git a/app/routes/job.js b/app/routes/job.js
index fdc7c8c28e..795da0127c 100644
--- a/app/routes/job.js
+++ b/app/routes/job.js
@@ -1,8 +1,11 @@
import TravisRoute from 'travis/routes/basic';
import { inject as service } from '@ember/service';
+import {task} from "ember-concurrency";
export default TravisRoute.extend({
router: service(),
+ store: service(),
+ tasks: service(),
titleToken(model) {
return `Job #${model.get('number')}`;
@@ -19,7 +22,7 @@ export default TravisRoute.extend({
let buildController, repo;
if (model && !model.get) {
- model = this.store.recordForId('job', model);
+ model = this.store.findRecord('job', model);
this.store.find('job', model);
}
repo = this.controllerFor('repo');
@@ -30,7 +33,7 @@ export default TravisRoute.extend({
let buildPromise = model.get('build');
if (buildPromise) {
buildPromise.then(build => {
- build = this.store.recordForId('build', build.get('id'));
+ build = this.store.findRecord('build', build.get('id'));
return buildController.set('build', build);
});
}
@@ -46,17 +49,18 @@ export default TravisRoute.extend({
},
afterModel(job) {
- const slug = this.modelFor('repo').get('slug');
+ const slug = this.modelFor('repo').slug;
this.ensureJobOwnership(job, slug);
return job
.get('build.request')
- .then(request => request && request.fetchMessages.perform());
+ .then(request => request && this.tasks.fetchMessages.perform(request));
},
- beforeModel() {
- const repo = this.modelFor('repo');
+ beforeModel() {
+ let repo = this.modelFor('repo');
+ // move this to service to be sure it is present...
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
},
@@ -75,5 +79,5 @@ export default TravisRoute.extend({
this.controllerFor('build').set('build', null);
this.controllerFor('job').set('job', null);
return this._super(...arguments);
- }
+ },
});
diff --git a/app/routes/job/config.js b/app/routes/job/config.js
index da954849c1..1f10921a05 100644
--- a/app/routes/job/config.js
+++ b/app/routes/job/config.js
@@ -1,16 +1,20 @@
import TravisRoute from 'travis/routes/basic';
+import { inject as service } from '@ember/service';
export default TravisRoute.extend({
+ store: service(),
+ tasks: service(),
titleToken: 'Config',
model() {
- return this.modelFor('job').get('build').then(build => {
- let requestId = build.get('build.request.id') || build.belongsTo('request').id();
+ let build = this.modelFor('job').build
+ return build.then((build_) => {
+ let requestId = build_.get('build.request.id') || build_.belongsTo('request').id();
return this.store.findRecord('request', requestId);
});
},
afterModel(request) {
- return request.fetchMessages.perform();
+ return this.tasks.fetchMessages.perform(request);
}
});
diff --git a/app/routes/legacy-repo-url.js b/app/routes/legacy-repo-url.js
index 98e7ff6fa7..68fd70547c 100644
--- a/app/routes/legacy-repo-url.js
+++ b/app/routes/legacy-repo-url.js
@@ -1,6 +1,10 @@
import Route from '@ember/routing/route';
-import { vcsConfigByUrlPrefix, defaultVcsConfig } from 'travis/utils/vcs';
+import {
+ vcsConfigByUrlPrefix,
+ defaultVcsConfig
+} from 'travis/utils/vcs';
import { isEmpty } from '@ember/utils';
+import { singularize } from 'ember-inflector';
export default Route.extend({
templateName: 'error404',
@@ -47,7 +51,7 @@ export default Route.extend({
routeName = method;
}
if (id) {
- routeName = method.singularize();
+ routeName = singularize(method);
routeModels.push(id);
}
if (view) {
diff --git a/app/routes/organization.js b/app/routes/organization.js
index dc045d71b1..30ebfd9015 100644
--- a/app/routes/organization.js
+++ b/app/routes/organization.js
@@ -1,5 +1,6 @@
import TravisRoute from 'travis/routes/basic';
import { inject as service } from '@ember/service';
+import { A } from '@ember/array';
export default TravisRoute.extend({
accounts: service(),
diff --git a/app/routes/organization/billing.js b/app/routes/organization/billing.js
index a28a8682bb..30a1ee308a 100644
--- a/app/routes/organization/billing.js
+++ b/app/routes/organization/billing.js
@@ -5,7 +5,7 @@ import AccountBillingMixin from 'travis/mixins/route/account/billing';
export default TravisRoute.extend(AccountBillingMixin, {
model() {
const organization = this.modelFor('organization');
- if (organization.permissions && organization.permissions.admin !== true) {
+ if (organization.permissions && organization.permissions.plan_view !== true) {
this.transitionTo('organization.repositories', organization);
}
return hash({
diff --git a/app/routes/organization/plan_usage.js b/app/routes/organization/plan_usage.js
index fcd1a4b612..9d99faadc2 100644
--- a/app/routes/organization/plan_usage.js
+++ b/app/routes/organization/plan_usage.js
@@ -5,7 +5,7 @@ import { hash } from 'rsvp';
export default TravisRoute.extend(AccountPlanUsageMixin, {
model() {
const organization = this.modelFor('organization');
- if (organization.permissions && organization.permissions.admin !== true) {
+ if (organization.permissions && organization.permissions.plan_usage !== true) {
this.transitionTo('organization.repositories', organization);
}
return hash({
diff --git a/app/routes/organization/settings.js b/app/routes/organization/settings.js
index 6083988bcd..c064bb0748 100644
--- a/app/routes/organization/settings.js
+++ b/app/routes/organization/settings.js
@@ -13,9 +13,6 @@ export default TravisRoute.extend({
model() {
const organization = this.modelFor('organization');
- if (organization.permissions.admin !== true) {
- this.transitionTo('organization.repositories', organization);
- }
const preferences = this.store.query('preference', { organization_id: organization.id });
return hash({ organization, preferences });
},
diff --git a/app/routes/owner.js b/app/routes/owner.js
index 739ab69b01..d41ee30303 100644
--- a/app/routes/owner.js
+++ b/app/routes/owner.js
@@ -1,8 +1,11 @@
import TravisRoute from 'travis/routes/basic';
import { inject as service } from '@ember/service';
+import { A } from '@ember/array'
export default TravisRoute.extend({
auth: service(),
+ store: service(),
+
deactivate() {
return this.controllerFor('loading').set('layoutName', null);
},
@@ -13,12 +16,12 @@ export default TravisRoute.extend({
},
model({ provider, owner }) {
- return this.store.queryRecord('owner', { provider, login: owner });
+ return this.store.smartQueryRecord('owner', { provider, login: owner });
},
actions: {
error(error, /* transition, originRoute*/) {
- const is404 = error.status === 404 || error.errors.firstObject.status === '404';
+ const is404 = error.status === 404 || A(error.errors || []).firstObject?.status === '404';
if (!is404) {
let message = 'There was an error while loading data, please try again.';
diff --git a/app/routes/owner/repositories.js b/app/routes/owner/repositories.js
index fde55bb96c..b00a56d01f 100644
--- a/app/routes/owner/repositories.js
+++ b/app/routes/owner/repositories.js
@@ -6,6 +6,7 @@ import { OWNER_TABS } from 'travis/controllers/owner/repositories';
export default TravisRoute.extend({
features: service(),
insights: service(),
+ store: service(),
needsAuth: false,
diff --git a/app/routes/plans/index.js b/app/routes/plans/index.js
index 287e93f89c..919fd0ac47 100644
--- a/app/routes/plans/index.js
+++ b/app/routes/plans/index.js
@@ -1,10 +1,12 @@
import TravisRoute from 'travis/routes/basic';
import { inject as service } from '@ember/service';
import config from 'travis/config/environment';
+import { pushPayload } from 'travis/serializers/plan'
export default TravisRoute.extend({
auth: service(),
router: service(),
+ store: service(),
beforeModel() {
if (this.auth.signedIn) {
@@ -13,7 +15,7 @@ export default TravisRoute.extend({
},
model() {
- this.store.pushPayload('plan', { '@type': 'plans', plans: config.plans });
+ pushPayload(this.store, { '@type': 'plans', plans: config.plans });
return {
plans: this.store.peekAll('plan'),
diff --git a/app/routes/provider.js b/app/routes/provider.js
index daedd06b8e..6e730ab024 100644
--- a/app/routes/provider.js
+++ b/app/routes/provider.js
@@ -1,5 +1,8 @@
import Route from '@ember/routing/route';
-import { vcsConfigByUrlPrefix, defaultVcsConfig } from 'travis/utils/vcs';
+import {
+ vcsConfigByUrlPrefix,
+ defaultVcsConfig
+} from 'travis/utils/vcs';
export default Route.extend({
beforeModel(transition) {
diff --git a/app/routes/pull-requests.js b/app/routes/pull-requests.js
index 1576d1e67a..2a14d1d010 100644
--- a/app/routes/pull-requests.js
+++ b/app/routes/pull-requests.js
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
tabStates: service(),
auth: service(),
+ tasks: service(),
activate(...args) {
this._super(args);
@@ -25,7 +26,7 @@ export default TravisRoute.extend({
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
}
});
diff --git a/app/routes/repo.js b/app/routes/repo.js
index 66166441ff..310b007cd0 100644
--- a/app/routes/repo.js
+++ b/app/routes/repo.js
@@ -1,4 +1,4 @@
-import { getWithDefault, computed } from '@ember/object';
+import { computed } from '@ember/object';
import TravisRoute from 'travis/routes/basic';
import Repo from 'travis/models/repo';
import ScrollResetMixin from 'travis/mixins/scroll-reset';
@@ -9,6 +9,7 @@ export default TravisRoute.extend(ScrollResetMixin, {
tabStates: service(),
auth: service(),
features: service(),
+ tasks: service(),
slug: null,
@@ -40,13 +41,14 @@ export default TravisRoute.extend(ScrollResetMixin, {
if (model && !model.get) {
model = this.store.find('repo', model.id);
}
+
return controller.set('repo', model);
},
serialize(repo) {
// slugs are sometimes unknown ???
- const slug = getWithDefault(repo, 'slug', 'unknown/unknown');
- const [owner, name] = slug.split('/');
+ const slug = repo ? repo.get('slug') : 'unknown/unknown';
+ const [owner, name] = (slug || 'unknown/unknown').split('/');
const provider = repo.get('vcsProvider.urlPrefix');
return { provider, owner, name };
@@ -61,7 +63,7 @@ export default TravisRoute.extend(ScrollResetMixin, {
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
},
diff --git a/app/routes/repo/index.js b/app/routes/repo/index.js
index 978659e8f5..4b08697507 100644
--- a/app/routes/repo/index.js
+++ b/app/routes/repo/index.js
@@ -4,15 +4,11 @@ import { inject as service } from '@ember/service';
export default TravisRoute.extend({
features: service(),
tabStates: service(),
-
- afterModel(repo) {
- try {
- return repo.get('currentBuild.request').then(request => request && request.fetchMessages.perform());
- } catch (error) {}
- },
-
+ tasks: service(),
+ router: service(),
setupController(controller, model) {
this._super(...arguments);
+ this.activate();
this.controllerFor('repo').activate('current');
controller.set('repo', model);
},
@@ -21,49 +17,36 @@ export default TravisRoute.extend({
this.controllerFor('build').set('build', null);
this.controllerFor('job').set('job', null);
this.controllerFor('repo').set('migrationStatus', null);
- this.stopObservingRepoStatus();
return this._super(...arguments);
},
activate() {
- this.observeRepoStatus();
- this.set('tabStates.mainTab', 'current');
+ this.tabStates.setMainTab('current');
return this._super(...arguments);
},
- observeRepoStatus() {
- let controller = this.controllerFor('repo');
- controller.addObserver('repo.active', this, 'renderTemplate');
- controller.addObserver('repo.currentBuildId', this, 'renderTemplate');
- },
-
- stopObservingRepoStatus() {
- let controller = this.controllerFor('repo');
- controller.removeObserver('repo.active', this, 'renderTemplate');
- controller.removeObserver('repo.currentBuildId', this, 'renderTemplate');
- },
-
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
},
- renderTemplate() {
- let controller = this.controllerFor('repo');
+ afterModel(repo, _transition) {
+ try {
+ repo.get('currentBuild.request').then(request => request && this.tasks.fetchMessages.perform(request));
+ } catch (error) {}
if (this.get('features.github-apps') &&
- controller.get('repo.active_on_org') &&
- controller.migrationStatus !== 'success') {
- this.render('repo/active-on-org');
- } else if (!controller.get('repo.active')) {
- this.render('repo/not-active');
- } else if (!controller.get('repo.currentBuildId')) {
- this.render('repo/no-build');
+ repo.active_on_org &&
+ repo.migrationStatus !== 'success') {
+ this.transitionTo('repo.active-on-org');
+ } else if (!repo.active) {
+ this.transitionTo('repo.not-active');
+ } else if (!repo.currentBuildId) {
+ this.transitionTo('repo.no-build');
} else {
- this.render('build');
- this.render('build/index', { into: 'build', controller: 'build' });
+ this.transitionTo('build.index', repo.currentBuildId, { queryParams: { currentTab: 'current' } });
}
}
});
diff --git a/app/routes/repo/no-build.js b/app/routes/repo/no-build.js
new file mode 100644
index 0000000000..5f0bdab421
--- /dev/null
+++ b/app/routes/repo/no-build.js
@@ -0,0 +1,15 @@
+import Route from '@ember/routing/route';
+import {inject as service} from "@ember/service";
+
+export default class NoBuild extends Route {
+ @service tabStates;
+ model() {
+ return this.modelFor('repo');
+ }
+
+ setupController(controller, model, transition) {
+ this.tabStates.setMainTab('current');
+ super.setupController(controller, model, transition);
+ controller.set('repo', model);
+ }
+}
diff --git a/app/routes/repo/not-active.js b/app/routes/repo/not-active.js
new file mode 100644
index 0000000000..d678b89dc1
--- /dev/null
+++ b/app/routes/repo/not-active.js
@@ -0,0 +1,15 @@
+import Route from '@ember/routing/route';
+import {inject as service} from "@ember/service";
+
+export default class NotActive extends Route {
+ @service tabStates;
+ model() {
+ return this.modelFor('repo');
+ }
+
+ setupController(controller, model, transition) {
+ this.tabStates.setMainTab('current');
+ super.setupController(controller, model, transition);
+ controller.set('repo', model);
+ }
+}
diff --git a/app/routes/repo/repo-active-on-org-route.js b/app/routes/repo/repo-active-on-org-route.js
new file mode 100644
index 0000000000..970dd8572b
--- /dev/null
+++ b/app/routes/repo/repo-active-on-org-route.js
@@ -0,0 +1,15 @@
+import Route from '@ember/routing/route';
+import {inject as service} from "@ember/service";
+
+export default class RepoActiveOnOrgRoute extends Route {
+ @service tabStates;
+ model() {
+ return this.modelFor('repo');
+ }
+
+ setupController(controller, model, transition) {
+ this.tabStates.setMainTab('current');
+ super.setupController(controller, model, transition);
+ controller.set('repo', model);
+ }
+}
diff --git a/app/routes/requests.js b/app/routes/requests.js
index fc3aa60143..0addc7652f 100644
--- a/app/routes/requests.js
+++ b/app/routes/requests.js
@@ -1,19 +1,21 @@
import TravisRoute from 'travis/routes/basic';
+import { inject as service } from '@ember/service';
export default TravisRoute.extend({
+ tasks: service(),
setupController() {
this._super(...arguments);
return this.controllerFor('repo').activate('requests');
},
model() {
- return this.modelFor('repo').get('requests');
+ return this.modelFor('repo').requests;
},
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
}
});
diff --git a/app/routes/scan-results.js b/app/routes/scan-results.js
index aadfdd8079..59a96a7b7a 100644
--- a/app/routes/scan-results.js
+++ b/app/routes/scan-results.js
@@ -8,6 +8,7 @@ export default TravisRoute.extend({
tabStates: service(),
auth: service(),
needsAuth: true,
+ tasks: service(),
page: 1,
@@ -50,7 +51,7 @@ export default TravisRoute.extend({
beforeModel() {
const repo = this.modelFor('repo');
if (repo && !repo.repoOwnerAllowance) {
- repo.fetchRepoOwnerAllowance.perform();
+ this.tasks.fetchRepoOwnerAllowance.perform(repo);
}
}
});
diff --git a/app/routes/settings.js b/app/routes/settings.js
index bc781cfb1a..59ff18b59f 100644
--- a/app/routes/settings.js
+++ b/app/routes/settings.js
@@ -10,6 +10,7 @@ export default TravisRoute.extend({
permissions: service(),
raven: service(),
flashes: service(),
+ store: service(),
needsAuth: true,
@@ -28,7 +29,7 @@ export default TravisRoute.extend({
fetchCustomSshKey() {
if (config.endpoints.sshKey) {
const repo = this.modelFor('repo');
- return this.store.find('ssh_key', repo.get('id')).then(((result) => {
+ return this.store.find('ssh_key', repo.id).then(((result) => {
if (!result.get('isNew')) {
return result;
}
@@ -61,28 +62,30 @@ export default TravisRoute.extend({
},
fetchRepositoryActiveFlag() {
- const repoId = this.modelFor('repo').get('id');
+ const repoId = this.modelFor('repo').id;
return this.api.get(`/repo/${repoId}`).then(response => response.active);
},
beforeModel() {
const repo = this.modelFor('repo');
- const hasPushPermission = this.permissions.hasPushPermission(repo);
- if (!hasPushPermission) {
+ if (!repo.permissions?.settings_read) {
this.transitionTo('repo.index');
this.flashes.error('Your permissions are insufficient to access this repository\'s settings');
}
},
- model() {
+ model(params) {
const repo = this.modelFor('repo');
+ let sshKey;
+ if (params.ssh_key_id)
+ sshKey = this.store.findRecord('ssh_key', params.ssh_key_id);
return hash({
settings: repo.fetchSettings.perform(),
repository: repo,
envVars: this.fetchEnvVars(),
sshKey: this.fetchSshKey(),
- customSshKey: this.fetchCustomSshKey(),
+ customSshKey: this.fetchCustomSshKey() || sshKey,
hasPushAccess: this.permissions.hasPushPermission(repo),
repositoryActive: this.fetchRepositoryActiveFlag()
});
diff --git a/app/serializers/beta-migration-request.js b/app/serializers/beta-migration-request.js
index d835f5633b..437dfa109e 100644
--- a/app/serializers/beta-migration-request.js
+++ b/app/serializers/beta-migration-request.js
@@ -2,12 +2,6 @@ import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
- pushPayload(store, payload) {
- const modelClass = store.modelFor('beta-migration-request');
- const json = this.normalizeArrayResponse(store, modelClass, payload);
- store.push(json);
- },
-
normalize(modelClass, payload = {}) {
if (payload.organizations) {
payload['organization_ids'] = payload.organizations;
diff --git a/app/serializers/build_v2_fallback.js b/app/serializers/build_v2_fallback.js
index 6eaea966b2..6c2a826f2a 100644
--- a/app/serializers/build_v2_fallback.js
+++ b/app/serializers/build_v2_fallback.js
@@ -1,4 +1,5 @@
import V2FallbackSerializer from 'travis/serializers/v2_fallback';
+import { A } from '@ember/array';
export default V2FallbackSerializer.extend({
normalizeSingleResponse: function (store, primaryModelClass, payload/* , id, requestType*/) {
diff --git a/app/serializers/job_v2_fallback.js b/app/serializers/job_v2_fallback.js
index 3f52692e1e..00e0a8a227 100644
--- a/app/serializers/job_v2_fallback.js
+++ b/app/serializers/job_v2_fallback.js
@@ -1,4 +1,5 @@
import V2FallbackSerializer from 'travis/serializers/v2_fallback';
+import { A } from '@ember/array';
export default V2FallbackSerializer.extend({
keyForV2Relationship(key/* , typeClass, method*/) {
diff --git a/app/serializers/plan.js b/app/serializers/plan.js
index 610128669e..660a1b5229 100644
--- a/app/serializers/plan.js
+++ b/app/serializers/plan.js
@@ -1,9 +1,11 @@
-import ApplicationSerializer from 'travis/serializers/application';
+import ApplicationSerializer from "./application";
export default ApplicationSerializer.extend({
- pushPayload(store, payload) {
- const modelClass = store.modelFor('plan');
- const json = this.normalizeArrayResponse(store, modelClass, payload);
- store.push(json);
- }
+
});
+
+export function pushPayload(store, payloads) {
+ const applicationSerializer = store.serializerFor('application');
+ const json = applicationSerializer.normalizeArrayResponse(store, store.modelFor('plan'), payloads);
+ store.push(json);
+}
diff --git a/app/serializers/repo.js b/app/serializers/repo.js
index 4105f32a34..e6fd65d74c 100644
--- a/app/serializers/repo.js
+++ b/app/serializers/repo.js
@@ -1,5 +1,5 @@
import RepoV2FallbackSerializer from 'travis/serializers/repo_v2_fallback';
-import EmbeddedRecordsMixin from 'ember-data/serializers/embedded-records-mixin';
+import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
let Serializer = RepoV2FallbackSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
diff --git a/app/serializers/request.js b/app/serializers/request.js
index df142a5c40..61777e3834 100644
--- a/app/serializers/request.js
+++ b/app/serializers/request.js
@@ -1,4 +1,5 @@
import V2FallbackSerializer from 'travis/serializers/v2_fallback';
+import { A } from '@ember/array';
let Serializer = V2FallbackSerializer.extend({
@@ -13,7 +14,7 @@ let Serializer = V2FallbackSerializer.extend({
normalizeArrayResponse: function (store, primaryModelClass, payload/* , id, requestType*/) {
if (payload.commits) {
payload.requests.forEach((request) => {
- let commit = commit = payload.commits.findBy('id', request.commit_id);
+ let commit = payload.commits.findBy('id', request.commit_id);
if (commit) {
request.commit = commit;
return delete request.commit_id;
diff --git a/app/serializers/v2_fallback.js b/app/serializers/v2_fallback.js
index b313b4f0ef..cda657bf98 100644
--- a/app/serializers/v2_fallback.js
+++ b/app/serializers/v2_fallback.js
@@ -1,6 +1,7 @@
import { isArray } from '@ember/array';
import V3Serializer from 'travis/serializers/v3';
import wrapWithArray from 'travis/utils/wrap-with-array';
+import { underscore } from '@ember/string';
export default V3Serializer.extend({
@@ -14,7 +15,7 @@ export default V3Serializer.extend({
// V2 API payload
let relationship = null;
let relationshipKey = this.keyForV2Relationship(key, relationshipMeta.kind, 'deserialize');
- let alternativeRelationshipKey = key.underscore();
+ let alternativeRelationshipKey = underscore(key);
let hashWithAltRelKey = resourceHash[alternativeRelationshipKey];
let hashWithRelKey = resourceHash[relationshipKey];
@@ -101,6 +102,6 @@ export default V3Serializer.extend({
if (key === 'repo') {
return 'repository';
}
- return `${key.underscore()}_id`;
+ return `${underscore(key)}_id`;
}
});
diff --git a/app/serializers/v3.js b/app/serializers/v3.js
index 6e547bd4d0..6d1a4444f3 100644
--- a/app/serializers/v3.js
+++ b/app/serializers/v3.js
@@ -2,7 +2,7 @@ import { underscore } from '@ember/string';
import { isArray } from '@ember/array';
import { assert } from '@ember/debug';
import { isNone, typeOf } from '@ember/utils';
-import JSONSerializer from 'ember-data/serializers/json';
+import JSONSerializer from '@ember-data/serializer/json';
import wrapWithArray from 'travis/utils/wrap-with-array';
import traverse from 'travis/utils/traverse-payload';
@@ -163,8 +163,8 @@ export default JSONSerializer.extend({
keyForRelationship(key/* , typeClass, method*/) {
if (key === 'repo') {
return 'repository';
- } else if (key && key.underscore) {
- return key.underscore();
+ } else if (key) {
+ return underscore(key);
} else {
return key;
}
@@ -211,10 +211,13 @@ export default JSONSerializer.extend({
if (type) {
items = payload[type];
} else {
- const plural = `${primaryModelClass.modelName.underscore()}s`;
+ const plural = `${underscore(primaryModelClass.modelName)}s`;
items = payload[plural];
}
+ if (!items)
+ return {data: []};
+
documentHash.data = items.map((item) => {
let { data, included } = this.normalize(primaryModelClass, item);
if (included) {
diff --git a/app/services/accounts.js b/app/services/accounts.js
index a1f5beb717..9329e100b1 100644
--- a/app/services/accounts.js
+++ b/app/services/accounts.js
@@ -24,7 +24,7 @@ export default Service.extend({
all: computed('user', 'organizations.@each', function () {
let user = this.user;
let organizations = this.organizations || [];
- return organizations.toArray().concat([user]);
+ return organizations.concat([user]);
}),
fetchOrganizations: task(function* () {
@@ -49,8 +49,10 @@ export default Service.extend({
fetchV2Subscriptions: task(function* () {
this.set('v2SubscriptionError', false);
+ console.log("fetchV2Subscriptions");
try {
- const subscriptions = yield this.store.findAll('v2-subscription') || [];
+ const subscriptions = yield this.store.findAll('v2-subscription') || [];
+
if (subscriptions.any(s => s.isSubscribed && !s.belongsTo('plan').id())) {
this.logMissingPlanException();
@@ -58,6 +60,7 @@ export default Service.extend({
return subscriptions;
} catch (e) {
+ console.log("any error?", e);
this.set('v2SubscriptionError', true);
}
}),
diff --git a/app/services/auth.js b/app/services/auth.js
index a1874089ca..f0a0ba3fbd 100644
--- a/app/services/auth.js
+++ b/app/services/auth.js
@@ -17,7 +17,12 @@ import {
import { getOwner } from '@ember/application';
import config from 'travis/config/environment';
import { task, didCancel } from 'ember-concurrency';
-import { availableProviders, vcsConfigByUrlPrefixOrType } from 'travis/utils/vcs';
+import {
+ availableProviders,
+ vcsConfigByUrlPrefixOrType
+} from 'travis/utils/vcs';
+import { A } from '@ember/array';
+import { debug } from '@ember/debug';
const { authEndpoint, apiEndpoint } = config;
@@ -25,7 +30,7 @@ const { authEndpoint, apiEndpoint } = config;
// and ensures the future fetches don't override previously loaded includes
let includes = ['owner.installation', 'user.emails'];
-const afterSignOutCallbacks = [];
+const afterSignOutCallbacks = A([]);
const STATE = {
SIGNED_OUT: 'signed-out',
@@ -65,8 +70,8 @@ export default Service.extend({
inactiveAccounts: computed('accounts.@each.id', 'storage.activeAccount.id', function () {
const { accounts, activeAccount } = this.storage;
- if (accounts && accounts.length > 0 && activeAccount) {
- return accounts.filter(account => account.id !== activeAccount.id);
+ if (accounts.content() && accounts.content().length > 0 && activeAccount) {
+ return accounts.content().filter(account => account.id !== activeAccount.id);
} else {
return [];
}
@@ -95,7 +100,7 @@ export default Service.extend({
return;
const { accounts } = this.storage;
const { vcsId } = this.currentUser;
- const stillLoggedIn = accounts.isAny('vcsId', vcsId);
+ const stillLoggedIn = accounts.content().isAny('vcsId', vcsId);
if (!stillLoggedIn) {
this.router.transitionTo('signin');
@@ -103,7 +108,8 @@ export default Service.extend({
},
switchAccount(id, redirectUrl) {
- this.store.unloadAll();
+ if (!this.store.isDestroyed && !this.store.isDestroying)
+ this.store.unloadAll();
const targetAccount = this.accounts.findBy('id', id);
this.storage.set('activeAccount', targetAccount);
if (redirectUrl)
@@ -129,7 +135,8 @@ export default Service.extend({
this.clearNonAuthFlashes();
runAfterSignOutCallbacks();
}
- this.store.unloadAll();
+ if (!this.store.isDestroyed && !this.store.isDestroying)
+ this.store.unloadAll();
const { currentRouteName } = this.router;
if (currentRouteName && currentRouteName !== 'signin') {
@@ -185,22 +192,28 @@ export default Service.extend({
autoSignIn() {
this.set('state', STATE.SIGNING_IN);
+
try {
const promise = this.storage.user ? this.handleNewLogin() : this.reloadCurrentUser();
return promise
- .then(() => this.permissionsService.fetchPermissions.perform())
+ .then(() => { this.permissionsService.fetchPermissions.perform()})
.then(() => {
- const { currentUser } = this;
- this.set('state', STATE.SIGNED_IN);
- Travis.trigger('user:signed_in', currentUser);
- Travis.trigger('user:refreshed', currentUser);
+ const {currentUser} = this;
+ this.set('state', STATE.SIGNED_IN);
+ Travis.trigger('user:signed_in', currentUser);
+ Travis.trigger('user:refreshed', currentUser);
})
.catch(error => {
+ console.log("Problems");
+ console.log(error.message);
if (!didCancel(error)) {
throw new Error(error);
}
});
} catch (error) {
+ console.log("General catch");
+ console.log(error.message); // for debugging purposes is better option...
+ console.log(error.stack);
this.signOut(false);
}
},
@@ -212,22 +225,20 @@ export default Service.extend({
storage.clearLoginData();
if (!user || !token) throw new Error('No login data');
-
const userData = getProperties(user, USER_FIELDS);
const installationData = getProperties(user, ['installation']);
if (installationData && installationData.installation) {
storage.set('activeAccountInstallation', installationData.installation);
}
-
this.validateUserData(userData, isBecome);
const userRecord = pushUserToStore(this.store, userData);
userRecord.set('authToken', token);
return this.reloadUser(userRecord).then(() => {
- storage.accounts.addObject(userRecord);
- storage.set('activeAccount', userRecord);
- this.reportNewUser();
- this.reportToIntercom();
+ storage.accounts.push(userRecord);
+ storage.set('activeAccount', userRecord);
+ this.reportNewUser();
+ this.reportToIntercom();
});
},
@@ -243,13 +254,12 @@ export default Service.extend({
fetchUser: task(function* (userRecord) {
try {
- return yield userRecord.reload({ included: includes.join(',') });
+ const reloadedUser = userRecord.reload({ included: includes.join(',') });
+ return yield reloadedUser;
} catch (error) {
const status = +error.status || +get(error, 'errors.firstObject.status');
- if (status === 401 || status === 403 || status === 500) {
- this.flashes.error(TOKEN_EXPIRED_MSG);
- this.signOut();
- }
+ this.flashes.error(TOKEN_EXPIRED_MSG);
+ yield this.signOut();
}
}).keepLatest(),
@@ -346,7 +356,8 @@ export default Service.extend({
});
function pushUserToStore(store, user) {
- const record = store.push(store.normalize('user', user));
+ let theUser = store.normalize('user', user);
+ const record = store.push(theUser);
const installation = store.peekAll('installation').findBy('owner.id', user.id) || null;
record.setProperties({ installation });
return record;
diff --git a/app/services/broadcasts.js b/app/services/broadcasts.js
index c6e083f714..a6ec3835ae 100644
--- a/app/services/broadcasts.js
+++ b/app/services/broadcasts.js
@@ -2,6 +2,7 @@ import { run } from '@ember/runloop';
import EmberObject, { computed } from '@ember/object';
import ArrayProxy from '@ember/array/proxy';
import Service, { inject as service } from '@ember/service';
+import { A } from '@ember/array';
export default Service.extend({
api: service(),
diff --git a/app/services/flashes.js b/app/services/flashes.js
index 846f276f43..eb3c3b1847 100644
--- a/app/services/flashes.js
+++ b/app/services/flashes.js
@@ -45,7 +45,7 @@ export default Service.extend({
let flashes = this.flashes;
let model = [];
if (flashes.length) {
- model.pushObjects(flashes.toArray());
+ model.pushObjects(...flashes.toArray());
}
return model.uniq();
}),
diff --git a/app/services/insights.js b/app/services/insights.js
index 66723a2e7a..9f5477a29b 100644
--- a/app/services/insights.js
+++ b/app/services/insights.js
@@ -1,6 +1,6 @@
import Service, { inject as service } from '@ember/service';
import moment from 'moment';
-import { assign } from '@ember/polyfills';
+
import { task } from 'ember-concurrency';
import { singularize } from 'ember-inflector';
@@ -54,7 +54,7 @@ export default Service.extend({
// deep so all we need to do is loop through each interval and do a shallow merge
const settings = Object.values(INSIGHTS_INTERVALS).reduce((settings, interval) => {
settings[interval] = {};
- assign(settings[interval], defaultIntervalSettings[interval], customIntervalSettings[interval]);
+ Object.assign(settings[interval], defaultIntervalSettings[interval], customIntervalSettings[interval]);
return settings;
}, {});
return settings;
@@ -150,7 +150,7 @@ function getMetricAPISettings(subject, func, subInterval, metricNames, owner, st
}
function mergeMetricSettings(options, func) {
- const currentOptions = assign({}, defaultOptions, options);
+ const currentOptions = Object.assign({}, defaultOptions, options);
currentOptions.aggregator = currentOptions.aggregator || func;
currentOptions.serializer = currentOptions.serializer || func;
return currentOptions;
@@ -200,7 +200,7 @@ function formatTimeKey(time, subInterval) {
function aggregateMetrics(metricNames, metrics, aggregatorName, labels, subInterval) {
const defaultData = metricNames.reduce((map, metric) => {
- map[metric] = assign({}, labels);
+ map[metric] = Object.assign({}, labels);
return map;
}, {});
diff --git a/app/services/job-config-fetcher.js b/app/services/job-config-fetcher.js
index e34375ae6a..530c94b572 100644
--- a/app/services/job-config-fetcher.js
+++ b/app/services/job-config-fetcher.js
@@ -38,7 +38,7 @@ export default Service.extend({
resolve(job._config);
} else {
const build = yield job.build;
- yield this.store.queryRecord('build', { id: build.id, include: 'build.jobs,job.config' });
+ yield this.store.smartQueryRecord('build', { id: build.id, include: 'build.jobs,job.config' });
resolve(job._config);
}
} catch (e) {
diff --git a/app/services/live-updates-record-fetcher.js b/app/services/live-updates-record-fetcher.js
index b2ccffbe34..4fc1d07df0 100644
--- a/app/services/live-updates-record-fetcher.js
+++ b/app/services/live-updates-record-fetcher.js
@@ -62,7 +62,7 @@ export default Service.extend({
if (needToFetchBuild || jobsData.length > 1) {
let index = buildIds.indexOf(buildId);
buildIds.splice(index, 1);
- this.store.queryRecord('build', { id: buildId, include: 'build.jobs' });
+ this.store.smartQueryRecord('build', { id: buildId, include: 'build.jobs' });
} else {
this.store.findRecord('job', jobsData[0], { reload: true });
}
diff --git a/app/services/log-to-log-content.js b/app/services/log-to-log-content.js
new file mode 100644
index 0000000000..277e429f4a
--- /dev/null
+++ b/app/services/log-to-log-content.js
@@ -0,0 +1,21 @@
+import Service from "@ember/service";
+
+
+export default Service.extend({
+ log: null,
+ logContent: null,
+
+
+ setLogContent(logContent) {
+ this.set('logContent', logContent);
+ },
+
+ setLog(log) {
+ this.set('log', log);
+ },
+
+ clear() {
+ this.set('log', null);
+ this.set('logContent', null);
+ }
+});
diff --git a/app/services/multi-vcs.js b/app/services/multi-vcs.js
index 9fe7a7e842..dc7f4280eb 100644
--- a/app/services/multi-vcs.js
+++ b/app/services/multi-vcs.js
@@ -1,7 +1,11 @@
import Service, { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { not, or, reads } from '@ember/object/computed';
-import { defaultVcsConfig, vcsConfig, vcsConfigByUrlPrefix } from 'travis/utils/vcs';
+import {
+ defaultVcsConfig,
+ vcsConfig,
+ vcsConfigByUrlPrefix
+} from 'travis/utils/vcs';
export default Service.extend({
auth: service(),
diff --git a/app/services/permissions.js b/app/services/permissions.js
index 6e84f68743..5d8242db2a 100644
--- a/app/services/permissions.js
+++ b/app/services/permissions.js
@@ -31,7 +31,9 @@ export default Service.extend({
let id = isNaN(repo) ? repo.get('id') : repo;
let currentUser = this.currentUser;
if (currentUser) {
- return currentUser.get(permissionsType).includes(parseInt(id));
+ const permType = currentUser.get(permissionsType) || currentUser[permissionsType];
+ if (!permType) return false;
+ return permType.includes(parseInt(id));
} else {
return false;
}
diff --git a/app/services/polling.js b/app/services/polling.js
index 278c7b5a96..ff76cb2429 100644
--- a/app/services/polling.js
+++ b/app/services/polling.js
@@ -33,7 +33,7 @@ export default Service.extend({
let sources;
sources = this.sources;
if (!sources.includes(source)) {
- return sources.pushObject(source);
+ return sources.push(source);
}
},
@@ -47,7 +47,7 @@ export default Service.extend({
let watchedModels;
watchedModels = this.watchedModels;
if (!watchedModels.includes(model)) {
- return watchedModels.pushObject(model);
+ return watchedModels.push(model);
}
},
diff --git a/app/services/pusher.js b/app/services/pusher.js
index 920ce7798c..84cd875419 100644
--- a/app/services/pusher.js
+++ b/app/services/pusher.js
@@ -76,12 +76,11 @@ export default Service.extend({
if (event === 'job:log') {
data = data.job ? data.job : data;
- job = store.recordForId('job', data.id);
- return job.appendLog({
+ return store.findRecord('job', data.id).then((job) => job.appendLog({
number: parseInt(data.number),
content: data._log,
final: data.final
- });
+ }));
} else if (data[name]) {
if (data._no_full_payload) {
// if payload is too big, travis-live will send us only the id of the
diff --git a/app/services/raven.js b/app/services/raven.js
index 58f3374baa..882b9b719f 100644
--- a/app/services/raven.js
+++ b/app/services/raven.js
@@ -1,6 +1,7 @@
import RavenLogger from 'ember-cli-sentry/services/raven';
import config from 'travis/config/environment';
import { inject as service } from '@ember/service';
+import { A } from '@ember/array';
export default RavenLogger.extend({
features: service(),
diff --git a/app/services/repositories.js b/app/services/repositories.js
index 6c2baa75a5..e5a04a839c 100644
--- a/app/services/repositories.js
+++ b/app/services/repositories.js
@@ -5,6 +5,7 @@ import config from 'travis/config/environment';
import Repo from 'travis/models/repo';
import { task, timeout } from 'ember-concurrency';
import { computed } from '@ember/object';
+import { A } from '@ember/array';
export default Service.extend({
auth: service(),
@@ -28,7 +29,7 @@ export default Service.extend({
loadingData: computed('tasks.@each.isRunning', function () {
let tasks = this.tasks;
- return tasks.any(task => task.get('isRunning'));
+ return tasks.any(task => task.isRunning);
}),
performSearchRequest: task(function* () {
@@ -87,10 +88,8 @@ export default Service.extend({
),
sortData(repos) {
- if (repos && repos.toArray) {
+ if (repos && repos.toArray)
repos = repos.toArray();
- }
-
if (repos && repos.sort) {
return repos.sort((repo1, repo2) => {
let buildId1 = repo1.get('currentBuild.id');
diff --git a/app/services/status-images.js b/app/services/status-images.js
index c4d9d4f503..677f05fd1f 100644
--- a/app/services/status-images.js
+++ b/app/services/status-images.js
@@ -26,11 +26,11 @@ export default Service.extend({
prefix = config.apiEndpoint;
}
- let slug = repo.get('slug');
+ let slug = repo.slug;
// In Enterprise you can toggle public mode, where even "public" repositories are hidden
// in which cases we need to generate a token for all images
- if (!config.publicMode || repo.get('private')) {
+ if (!config.publicMode || repo.private) {
const token = this.auth.assetToken;
return `${prefix}/${slug}.svg?token=${token}${branch ? `&branch=${branch}` : ''}`;
} else {
diff --git a/app/services/storage.js b/app/services/storage.js
index 04921811c1..39137c438f 100644
--- a/app/services/storage.js
+++ b/app/services/storage.js
@@ -3,6 +3,7 @@ import Service, { inject as service } from '@ember/service';
export default Service.extend({
auth: service('storage/auth'),
utm: service('storage/utm'),
+ store: service(),
get billingStep() {
return +this.getItem('travis.billing_step');
@@ -33,7 +34,52 @@ export default Service.extend({
return this.parseWithDefault('travis.billing_info', {});
},
set billingInfo(value) {
- this.setItem('travis.billing_info', JSON.stringify(value));
+ if(!value)
+ return this.setItem('travis.billing_info', value);
+
+ const data
+ = (({
+ address,
+ address2,
+ billingEmail,
+ billingEmailRead,
+ city,
+ company,
+ country,
+ firstName,
+ lastName,
+ hasLocalRegistration,
+ id,
+ isReloading,
+ state,
+ subscription,
+ vatId,
+ zipCode,
+ notifications}) =>
+ ({
+ address,
+ address2,
+ billingEmail,
+ billingEmailRead,
+ city,
+ company,
+ country,
+ firstName,
+ lastName,
+ hasLocalRegistration,
+ id,
+ isReloading,
+ state,
+ subscription,
+ vatId,
+ zipCode,
+ notifications
+ }))(value);
+
+ return this.dataSubscription(data).then((datum) => {
+ return this.setItem('travis.billing_info', JSON.stringify(datum));
+ });
+
},
get billingPlan() {
@@ -43,6 +89,17 @@ export default Service.extend({
this.setItem('travis.billing_plan', JSON.stringify(value));
},
+ async dataSubscription(data) {
+ data.subscription = await data.subscription;
+ const model = data.subscription;
+ const snapshot = model._createSnapshot();
+ const serializer = this.store.serializerFor('subscription');
+ const serializedData = serializer.serialize(snapshot);
+ data.subscription = serializedData;
+
+ return data;
+ },
+
clearPreferencesData() {
this.removeItem('travis.features');
},
diff --git a/app/services/storage/auth.js b/app/services/storage/auth.js
index 6e1bc0aab4..1daa8ab590 100644
--- a/app/services/storage/auth.js
+++ b/app/services/storage/auth.js
@@ -2,12 +2,39 @@ import { computed } from '@ember/object';
import { assert } from '@ember/debug';
import { parseWithDefault } from '../storage';
import Service, { inject as service } from '@ember/service';
+import { asObservableArray } from "travis/utils/observable_array";
+import { underscoreKeys } from "travis/utils/underscore-keys";
const storage = getStorage();
-export default Service.extend({
+const Auth = Service.extend({
store: service(),
+ persistAccounts(newValue) {
+ const records = (newValue || []).map(record => serializeUserRecord(record));
+ storage.setItem('travis.auth.accounts', JSON.stringify(records));
+ },
+
+ accounts: computed({
+ get() {
+ const accountsData = storage.getItem('travis.auth.accounts');
+ let accounts = parseWithDefault(accountsData, []).map(account =>
+ extractAccountRecord(this.store, account)
+ );
+ accounts = asObservableArray(accounts);
+
+ accounts.addArrayObserver(this, {
+ willChange: 'persistAccounts',
+ didChange: 'persistAccounts'
+ });
+ return accounts;
+ },
+ set(key, accounts_) {
+ this.persistAccounts(accounts_);
+ return accounts_;
+ }
+ }),
+
token: computed({
get() {
return storage.getItem('travis.token') || null;
@@ -32,8 +59,8 @@ export default Service.extend({
user: computed({
get() {
- const data = parseWithDefault(storage.getItem('travis.user'), null);
- return data && data.user || data;
+ const data = parseWithDefault(storage.getItem('travis.user'), {});
+ return underscoreKeys(data && data.user || data);
},
set(key, user) {
assert('User storage is read-only', user === null);
@@ -42,29 +69,6 @@ export default Service.extend({
}
}),
- accounts: computed({
- get() {
- const accountsData = storage.getItem('travis.auth.accounts');
- const accounts = parseWithDefault(accountsData, []).map(account =>
- extractAccountRecord(this.store, account)
- );
- accounts.addArrayObserver(this, {
- willChange: 'persistAccounts',
- didChange: 'persistAccounts'
- });
- return accounts;
- },
- set(key, accounts) {
- this.persistAccounts(accounts);
- return accounts;
- }
- }).volatile(),
-
- persistAccounts(newValue) {
- const records = (newValue || []).map(record => serializeUserRecord(record));
- storage.setItem('travis.auth.accounts', JSON.stringify(records));
- },
-
activeAccountId: computed({
get() {
return +storage.getItem('travis.auth.activeAccountId');
@@ -135,6 +139,7 @@ function getStorage() {
// primary storage for auth is the one in which auth data was updated last
const sessionStorageUpdatedAt = +sessionStorage.getItem('travis.auth.updatedAt');
const localStorageUpdatedAt = +localStorage.getItem('travis.auth.updatedAt');
+
return sessionStorageUpdatedAt > localStorageUpdatedAt ? sessionStorage : localStorage;
}
@@ -147,3 +152,4 @@ function extractAccountRecord(store, userData) {
return record || store.push(store.normalize('user', userData));
}
+export default Auth;
diff --git a/app/services/store.js b/app/services/store.js
index f082e1dd5a..2d5a5dece2 100644
--- a/app/services/store.js
+++ b/app/services/store.js
@@ -1,169 +1,193 @@
/* eslint-disable camelcase */
-import Store from 'ember-data/store';
+import Store, {CacheHandler} from '@ember-data/store';
+import RequestManager from '@ember-data/request';
+import {LegacyNetworkHandler} from '@ember-data/legacy-compat';
+
import PaginatedCollectionPromise from 'travis/utils/paginated-collection-promise';
-import { inject as service } from '@ember/service';
+import {inject as service} from '@ember/service';
import FilteredArrayManager from 'travis/utils/filtered-array-manager';
import fetchLivePaginatedCollection from 'travis/utils/fetch-live-paginated-collection';
-export default Store.extend({
- auth: service(),
-
- defaultAdapter: 'application',
- adapter: 'application',
-
- init() {
- this._super(...arguments);
- this.shouldAssertMethodCallsOnDestroyedStore = true;
- this.filteredArraysManager = FilteredArrayManager.create({ store: this });
- },
-
- // Fetch a filtered collection.
- //
- // modelName - a type of the model passed as a string, for example 'repo'
- // queryParams - params that will be passed to the store.query function when
- // fetching records on the initial call. Passing null or
- // undefined here will stop any requests from happening,
- // filtering will be based only on existing records
- // filterFunction - a function that will be called to determine wheather a
- // record should be included in the filtered collection. A
- // passed function will be called with a record as an
- // argument
- // dependencies - a list of dependencies that will trigger the re-evaluation
- // of a record. When one of the dependencies changes on any
- // of the records in the store, it may be added or removed
- // from a filtered array.
- // forceReload - if set to true, store.query will be run on each call
- // instead of only running it on the first run
- //
- // Example:
- //
- // store.filter(
- // 'repo',
- // { starred: true },
- // (repo) => repo.get('starred'),
- // ['starred'],
- // true
- // )
- //
- // Rationale for our own implementation of store.filter:
- //
- // The default implementation of filter is rather limited and misses a few
- // scenarios important to us. The problem is that when you use the default
- // store.filter implementation, it evaluates if a record should be added to a
- // filtered array only when a new record is added to the store or when a
- // property on a record itself changes. That means that we can't observe
- // computed properties that depend on anything else than defined properties.
- // Our implementation allows to pass dependencies as an optional argument,
- // which allows to pass any property as a dependency.
- //
- // One more change in relation to the default filter representation is that
- // the default store.filter implementation will always fetch new records. The
- // new implementation has an identity map built in and it will always fetch
- // the same array for the same set of arguments. Thanks to that running
- // store.filter multiple times will return immediately on the second and
- // subsequent tries.
- //
- // If you need to also fetch new results each time the function is run, you
- // can set forceReload option to true, but it will still resolve immediately
- // once a first query is already finished.
- //
- // For more info you may also see comments in FilteredArraysManager.
- filter(modelName, queryParams, filterFunction, dependencies, forceReload) {
- if (arguments.length === 0) {
- throw new Error('store.filter called with no arguments');
+export default class ExtendedStore extends Store {
+ @service auth;
+
+ defaultAdapter = 'application';
+ adapter = 'application';
+
+ constructor() {
+ super(...arguments);
+ this.shouldAssertMethodCallsOnDestroyedStore = true;
+ this.filteredArraysManager = FilteredArrayManager.create({store: this});
+ this.requestManager = new RequestManager();
+ this.requestManager.use([LegacyNetworkHandler]);
+ this.requestManager.useCache(CacheHandler);
}
- if (arguments.length === 1) {
- return this.peekAll(modelName);
+
+ // Fetch a filtered collection.
+ //
+ // modelName - a type of the model passed as a string, for example 'repo'
+ // queryParams - params that will be passed to the store.query function when
+ // fetching records on the initial call. Passing null or
+ // undefined here will stop any requests from happening,
+ // filtering will be based only on existing records
+ // filterFunction - a function that will be called to determine wheather a
+ // record should be included in the filtered collection. A
+ // passed function will be called with a record as an
+ // argument
+ // dependencies - a list of dependencies that will trigger the re-evaluation
+ // of a record. When one of the dependencies changes on any
+ // of the records in the store, it may be added or removed
+ // from a filtered array.
+ // forceReload - if set to true, store.query will be run on each call
+ // instead of only running it on the first run
+ //
+ // Example:
+ //
+ // store.filter(
+ // 'repo',
+ // { starred: true },
+ // (repo) => repo.get('starred'),
+ // ['starred'],
+ // true
+ // )
+ //
+ // Rationale for our own implementation of store.filter:
+ //
+ // The default implementation of filter is rather limited and misses a few
+ // scenarios important to us. The problem is that when you use the default
+ // store.filter implementation, it evaluates if a record should be added to a
+ // filtered array only when a new record is added to the store or when a
+ // property on a record itself changes. That means that we can't observe
+ // computed properties that depend on anything else than defined properties.
+ // Our implementation allows to pass dependencies as an optional argument,
+ // which allows to pass any property as a dependency.
+ //
+ // One more change in relation to the default filter representation is that
+ // the default store.filter implementation will always fetch new records. The
+ // new implementation has an identity map built in and it will always fetch
+ // the same array for the same set of arguments. Thanks to that running
+ // store.filter multiple times will return immediately on the second and
+ // subsequent tries.
+ //
+ // If you need to also fetch new results each time the function is run, you
+ // can set forceReload option to true, but it will still resolve immediately
+ // once a first query is already finished.
+ //
+ // For more info you may also see comments in FilteredArraysManager.
+
+ filter(modelName, queryParams, filterFunction, dependencies, forceReload) {
+ if (arguments.length === 0) {
+ throw new Error('store.filter called with no arguments');
+ }
+ if (arguments.length === 1) {
+ return this.peekAll(modelName);
+ }
+ if (arguments.length === 2) {
+ filterFunction = queryParams;
+ return this.filteredArraysManager.filter(modelName, null, filterFunction, ['']);
+ }
+
+ if (!dependencies) {
+ return this.filteredArraysManager.filter(modelName, queryParams, filterFunction, ['']);
+ } else {
+ return this.filteredArraysManager.fetchArray(modelName, queryParams, filterFunction, dependencies, forceReload);
+ }
}
- if (arguments.length === 2) {
- filterFunction = queryParams;
- return this.filteredArraysManager.filter(modelName, null, filterFunction, ['']);
+
+ smartQueryRecord(type, ...params) {
+ return this.queryRecord(type, ...params);
}
- if (!dependencies) {
- return this.filteredArraysManager.filter(modelName, queryParams, filterFunction, ['']);
- } else {
- return this.filteredArraysManager.fetchArray(modelName, queryParams, filterFunction, dependencies, forceReload);
+ push(object) {
+ console.log(`single push ${JSON.stringify(object)}`)
+ const id = object.data.id
+ const type = object.data.type;
+
+ if (this.shouldAdd(object)) {
+ const included = object.included ? JSON.parse(JSON.stringify(object.included)) : null;
+ if (included) {
+ object.included = included.filter(single => {
+ return this.shouldAdd({data: single})
+ });
+ }
+ return super.push(object);
+ } else {
+ return this.peekRecord(type, id);
+ }
}
- },
-
- // Returns a collection with pagination data. If the first page is requested,
- // the collection will be live updated. Otherwise keeping the calculations and
- // figuring out if the record should be put on the page is not easily
- // achieveable (or even impossible in some cases).
- //
- // modelName - a type of the model as a string, for example 'repo'
- // queryParams - params for a store.query call that will be fired to fetch the
- // data from the server
- // options - additional options:
- // filter - a filter function that will be used to test if a
- // record should be added or removed from the array. It
- // will be called with a record under test as an
- // argument. It only matters for live updates
- // sort - either a string or a function to sort the collection
- // with. If it's a string, it should be the name of the
- // property to sort by, with an optional ':asc' or
- // ':desc' suffixes, for example 'id:desc'. If it's a
- // function it will be called with 2 records to compare
- // as an argument
- // dependencies - a set of dependencies that will be watched to
- // re-evaluate if a record should be a part of a
- // collection
- // forceReload - if set to true, store.query will be run on
- // call
- //
- // Examples:
- //
- // store.paginated(
- // 'repo',
- // { active: true, offset: 0, limit: 10 },
- // {
- // filter: (repo) => repo.get('active'),
- // sort: 'id:desc',
- // dependencies: ['active'],
- // forceReload: true
- // }
- //
- paginated(modelName, queryParams, options = {}) {
- let allowLive = !options.hasOwnProperty('live') || options.live;
- if (!parseInt(queryParams.offset) && allowLive) {
- // we're on the first page, live updates can be enabled
- return fetchLivePaginatedCollection(this, ...arguments);
- } else {
- return PaginatedCollectionPromise.create({
- content: this.query(...arguments)
- });
+
+ shouldAdd(object) {
+ const data = object.data;
+ const type = data.type;
+ const newUpdatedAt = data.attributes ? data.attributes.updatedAt : null;
+ const id = data.id
+ if (newUpdatedAt) {
+ const record = this.peekRecord(type, id);
+ if (record) {
+ const existingUpdatedAt = record.get('updatedAt');
+ return !existingUpdatedAt || existingUpdatedAt <= newUpdatedAt;
+ } else {
+ return true
+ }
+ } else {
+ return true
+ }
}
- },
-
- // We shouldn't override private methods, but at the moment I don't see any
- // other way to prevent updating records with outdated data.
- // _pushInternalModel seems to be the entry point for all of the data loading
- // related functions, so it's the best place to override to check the
- // updated_at field
- _pushInternalModel(data) {
- let type = data.type;
- let newUpdatedAt = data.attributes ? data.attributes.updatedAt : null;
-
- if (newUpdatedAt) {
- let internalModel = this._internalModelForId(type, data.id),
- record = internalModel.getRecord(),
- existingUpdatedAt = record.get('updatedAt');
-
- if (!existingUpdatedAt || existingUpdatedAt <= newUpdatedAt) {
- return this._super(...arguments);
- } else {
- // record to push is older than the existing one, we need to skip,
- // but we still need to return the result
- return internalModel;
- }
- } else {
- return this._super(...arguments);
+
+
+ // Returns a collection with pagination data. If the first page is requested,
+ // the collection will be live updated. Otherwise keeping the calculations and
+ // figuring out if the record should be put on the page is not easily
+ // achieveable (or even impossible in some cases).
+ //
+ // modelName - a type of the model as a string, for example 'repo'
+ // queryParams - params for a store.query call that will be fired to fetch the
+ // data from the server
+ // options - additional options:
+ // filter - a filter function that will be used to test if a
+ // record should be added or removed from the array. It
+ // will be called with a record under test as an
+ // argument. It only matters for live updates
+ // sort - either a string or a function to sort the collection
+ // with. If it's a string, it should be the name of the
+ // property to sort by, with an optional ':asc' or
+ // ':desc' suffixes, for example 'id:desc'. If it's a
+ // function it will be called with 2 records to compare
+ // as an argument
+ // dependencies - a set of dependencies that will be watched to
+ // re-evaluate if a record should be a part of a
+ // collection
+ // forceReload - if set to true, store.query will be run on
+ // call
+ //
+ // Examples:
+ //
+ // store.paginated(
+ // 'repo',
+ // { active: true, offset: 0, limit: 10 },
+ // {
+ // filter: (repo) => repo.get('active'),
+ // sort: 'id:desc',
+ // dependencies: ['active'],
+ // forceReload: true
+ // }
+ //
+
+ paginated(modelName, queryParams, options = {}) {
+ let allowLive = !options.hasOwnProperty('live') || options.live;
+ if (!parseInt(queryParams.offset) && allowLive) {
+ // we're on the first page, live updates can be enabled
+ return fetchLivePaginatedCollection(this, ...arguments);
+ } else {
+ return PaginatedCollectionPromise.create({
+ content: this.query(...arguments)
+ });
+ }
}
- },
- destroy() {
- this._super(...arguments);
- this.filteredArraysManager.destroy();
- }
-});
+
+ destroy() {
+ this.filteredArraysManager.destroy();
+ super.destroy();
+ }
+}
diff --git a/app/services/stripe.js b/app/services/stripe.js
index 0e27207836..ef3baf9b02 100644
--- a/app/services/stripe.js
+++ b/app/services/stripe.js
@@ -24,7 +24,7 @@ export default Service.extend({
if (clientSecret) {
yield this.stripev3.handleCardPayment(clientSecret);
}
- yield this.accounts.fetchV2Subscriptions.perform();
+ yield this.accounts.fetchV2Subscriptions.linked().perform();
}).drop(),
handleError(stripeError) {
diff --git a/app/services/tab-states.js b/app/services/tab-states.js
index 294dee0694..27b58de93e 100644
--- a/app/services/tab-states.js
+++ b/app/services/tab-states.js
@@ -18,6 +18,9 @@ export default Service.extend({
switchSidebar(state) {
this.set('sidebarTab', state);
},
+ setMainTab(tabName) {
+ this.set('mainTab', tabName);
+ },
switchSidebarToOwned() {
this.switchSidebar(SIDEBAR_TAB_STATES.OWNED);
},
diff --git a/app/services/tasks.js b/app/services/tasks.js
index 988491ea70..ef8604fa58 100644
--- a/app/services/tasks.js
+++ b/app/services/tasks.js
@@ -1,5 +1,6 @@
import Service, { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
+import { pushPayload } from 'travis/serializers/beta-migration-request'
/**
* Service for shared Ember Concurrency tasks.
@@ -22,10 +23,28 @@ export default Service.extend({
});
if (data) {
- this.store.pushPayload('beta-migration-request', data);
+ const modelClass = this.store.modelFor('beta-migration-request');
+ const serializer = this.store.serializerFor('application')
+ const json = serializer.normalizeArrayResponse(this.store, modelClass, data);
+ this.store.push(json);
}
return this.store.peekAll('beta-migration-request');
- }).drop()
+ }).drop(),
+
+ fetchRepoOwnerAllowance: task(function* (repo) {
+ const allowance = this.store.peekRecord('allowance', repo.id);
+ if (allowance)
+ return allowance;
+ return yield this.store.smartQueryRecord('allowance', { login: repo.owner.login, provider: repo.provider || 'github' });
+ }).drop(),
+ fetchMessages: task(function* (request) {
+ const repoId = request.get('repo.id');
+ const requestId = request.get('build.request.id');
+ if (repoId && requestId) {
+ const response = yield request.api.get(`/repo/${repoId}/request/${requestId}/messages`) || {};
+ return response.messages;
+ }
+ }).drop()
});
diff --git a/app/services/update-times.js b/app/services/update-times.js
index e5afa4ad4c..10c2e48f82 100644
--- a/app/services/update-times.js
+++ b/app/services/update-times.js
@@ -3,31 +3,41 @@ import Service from '@ember/service';
import config from 'travis/config/environment';
import eventually from 'travis/utils/eventually';
import Visibility from 'visibilityjs';
+import { task, timeout } from 'ember-concurrency';
+import { on } from '@ember/object/evented';
export default Service.extend({
allowFinishedBuilds: false,
+ isDestroyedOrDestroying: false,
init() {
- const visibilityId = Visibility.every(config.intervals.updateTimes, bind(this, 'updateTimes'));
- const intervalId = setInterval(this.resetAllowFinishedBuilds.bind(this), 60000);
const records = [];
- this.setProperties({ visibilityId, intervalId, records });
+ this.setProperties({ records });
return this._super(...arguments);
},
+ updateTimesTask: task(function* () {
+ while (true) {
+ yield this.updateTimes();
+ yield timeout(config.intervals.updateTimes);
+ }
+ }).on('init'),
+
+
willDestroy() {
- Visibility.stop(this.visibilityId);
- clearInterval(this.intervalId);
+ this.set('isDestroyedOrDestroying', true);
this._super(...arguments);
},
resetAllowFinishedBuilds() {
+ if (this.isDestroyedOrDestroying) { return; }
this.set('allowFinishedBuilds', true);
},
updateTimes() {
+ if (this.isDestroyedOrDestroying) { return; }
let records = this.records;
records.filter(record => this.allowFinishedBuilds || !record.get('isFinished'))
diff --git a/app/services/utm.js b/app/services/utm.js
index 1b75f8ba7a..ac0cfaba3a 100644
--- a/app/services/utm.js
+++ b/app/services/utm.js
@@ -51,7 +51,7 @@ export default Service.extend({
}),
hasParamsInUrl: computed('searchParams', function () {
- return UTM_FIELD_NAMES.any(field => this.searchParams.has(field));
+ return UTM_FIELD_NAMES.some(field => this.searchParams.has(field));
}),
peek(fields, includeEmpty = true) {
diff --git a/app/styles/app.css b/app/styles/app.css
new file mode 100644
index 0000000000..2763afa4cf
--- /dev/null
+++ b/app/styles/app.css
@@ -0,0 +1 @@
+/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
diff --git a/app/templates/account-error.hbs b/app/templates/account-error.hbs
index d77a1bcac3..b4a4819ad4 100644
--- a/app/templates/account-error.hbs
+++ b/app/templates/account-error.hbs
@@ -16,6 +16,5 @@
If you believe you've received this message in error, please
contact support.
-
-
+
diff --git a/app/templates/account/settings.hbs b/app/templates/account/settings.hbs
index 0f554f4dae..41935681ec 100644
--- a/app/templates/account/settings.hbs
+++ b/app/templates/account/settings.hbs
@@ -7,7 +7,7 @@
Confirm account
-
+
{{#if this.userHasNoEmails}}
We don’t have your email address.
@@ -110,8 +110,7 @@
@query={{hash tab="insights"}}
>
View {{this.account.fullName}}'s Insights
-
-
+
diff --git a/app/templates/branches.hbs b/app/templates/branches.hbs
index ec1cb865d2..1467b5672f 100644
--- a/app/templates/branches.hbs
+++ b/app/templates/branches.hbs
@@ -6,7 +6,7 @@
Default Branch
{{/if}}
@@ -17,7 +17,7 @@
{{#each this.activeBranches as |branch|}}
-
+
{{/each}}
@@ -29,7 +29,7 @@
{{#each this.inactiveBranches as |branch|}}
-
+
{{/each}}
diff --git a/app/templates/caches.hbs b/app/templates/caches.hbs
index 52993fc772..a4d210b69c 100644
--- a/app/templates/caches.hbs
+++ b/app/templates/caches.hbs
@@ -9,36 +9,40 @@
- {{#if this.model.pushes.length}}
+ {{#if this.pushes.length}}
Pushes
- {{#each this.model.pushes as |cache|}}
+ {{#each this.pushes as |cache|}}
{{/each}}
{{/if}}
- {{#if this.model.pullRequests.length}}
+ {{#if this.pullRequests.length}}
{{vcs-vocab this.repo.vcsType 'pullRequest' plural=true}}
- {{#each this.model.pullRequests as |cache|}}
+ {{#each this.pullRequests as |cache|}}
{{/each}}
diff --git a/app/templates/components/.gitkeep b/app/templates/components/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/templates/components/account-token.hbs b/app/templates/components/account-token.hbs
index e8b5201244..21eea25818 100644
--- a/app/templates/components/account-token.hbs
+++ b/app/templates/components/account-token.hbs
@@ -21,14 +21,14 @@
Copy token
-