зеркало из https://github.com/mozilla/treeherder.git
Bug 1336556 - Replace grunt build system with neutrino/webpack
This commit is contained in:
Родитель
11ca87fb34
Коммит
afa7d63d1d
80
.eslintrc
80
.eslintrc
|
@ -1,80 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
// Non-default rules we've chosen to enable.
|
||||
"accessor-pairs": 2,
|
||||
"comma-style": 2,
|
||||
"eol-last": 2,
|
||||
"eqeqeq": 2,
|
||||
"guard-for-in": 2,
|
||||
"indent": [2, 4, {"SwitchCase": 1}],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": 2,
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-bitwise": 2,
|
||||
"no-caller": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-else-return": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-proto": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-useless-call": 2,
|
||||
"no-void": 2,
|
||||
"no-with": 2,
|
||||
"semi": 2,
|
||||
"strict": [2, "global"],
|
||||
"yoda": 2
|
||||
},
|
||||
"globals":{
|
||||
"angular": true,
|
||||
"$": true,
|
||||
"_": true,
|
||||
"treeherder": true,
|
||||
"jsyaml": true,
|
||||
"perf": true,
|
||||
"treeherderApp": true,
|
||||
"failureViewerApp": true,
|
||||
"logViewerApp": true,
|
||||
"userguideApp": true,
|
||||
"admin": true,
|
||||
"Mousetrap": true,
|
||||
"jQuery": true,
|
||||
"React": true,
|
||||
"hawk": true,
|
||||
"jsonSchemaDefaults": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
const path = require('path');
|
||||
const Neutrino = require('neutrino');
|
||||
const api = new Neutrino([path.resolve('./neutrino-custom/development.js')]);
|
||||
|
||||
module.exports = api.custom.eslintrc();
|
|
@ -59,7 +59,6 @@ matrix:
|
|||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- yarn run lint
|
||||
- yarn test
|
||||
- yarn run build
|
||||
|
||||
|
@ -183,14 +182,21 @@ matrix:
|
|||
- mkdir -p $HOME/bin
|
||||
- wget https://github.com/mozilla/geckodriver/releases/download/v0.14.0/geckodriver-v0.14.0-linux64.tar.gz
|
||||
- tar -xzf geckodriver-v0.14.0-linux64.tar.gz -C $HOME/bin
|
||||
- nvm install 7.7.2
|
||||
# Required until Travis makes yarn available in the environment,
|
||||
# since we override the default `install` for clarity.
|
||||
- curl -sSfL https://yarnpkg.com/install.sh | bash
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
install:
|
||||
- pip install --disable-pip-version-check --require-hashes -r requirements/common.txt -r requirements/dev.txt
|
||||
- yarn install --frozen-lockfile --no-bin-links
|
||||
before_script:
|
||||
- while ! curl localhost:9200 &>/dev/null; do sleep 1; done
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 3 # give xvfb some time to start
|
||||
script:
|
||||
- yarn run build
|
||||
- py.test tests/selenium/ --runselenium --driver Firefox
|
||||
|
||||
notifications:
|
||||
|
|
336
Gruntfile.js
336
Gruntfile.js
|
@ -1,336 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
var packageTypes = ['dependencies'];
|
||||
if (!grunt.option('production')) {
|
||||
// Also load tasks from packages listed under `devDependencies`, so
|
||||
// long as a `--production` option hasn't been passed to Grunt.
|
||||
packageTypes.push('devDependencies');
|
||||
}
|
||||
require('load-grunt-tasks')(grunt, {scope: packageTypes});
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
clean: {
|
||||
dist: ['dist/'],
|
||||
tmp: ['.tmp/']
|
||||
},
|
||||
|
||||
htmlangular: {
|
||||
options: {
|
||||
reportpath: null
|
||||
},
|
||||
files: {
|
||||
src: ['ui/*.html'],
|
||||
nonull: true
|
||||
},
|
||||
},
|
||||
|
||||
useminPrepare:{
|
||||
index: {
|
||||
src:'ui/index.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
userguide: {
|
||||
src:'ui/userguide.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
logviewer: {
|
||||
src:'ui/logviewer.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
failureviewer: {
|
||||
src:'ui/failureviewer.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
perf: {
|
||||
src:'ui/perf.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
admin: {
|
||||
src:'ui/admin.html',
|
||||
nonull: true,
|
||||
options:{
|
||||
dest:'dist'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
usemin:{ html:['dist/*.html'] },
|
||||
|
||||
'cacheBust': {
|
||||
options: {
|
||||
assets: ['js/*', 'css/*'],
|
||||
baseDir: 'dist/'
|
||||
},
|
||||
src: ['dist/**/*.html']
|
||||
},
|
||||
|
||||
copy:{
|
||||
|
||||
main: {
|
||||
files: [
|
||||
{ src:'contribute.json', dest:'dist/contribute.json', nonull: true },
|
||||
{ cwd: 'ui/', src: '*', dest:'dist/', expand: true, filter: 'isFile', nonull: true },
|
||||
]
|
||||
},
|
||||
// Copy img dir
|
||||
img:{
|
||||
expand: true,
|
||||
src: 'ui/img/*',
|
||||
dest: 'dist/img/',
|
||||
nonull: true,
|
||||
flatten: true
|
||||
},
|
||||
// Copy html in partials
|
||||
partials:{
|
||||
expand: true,
|
||||
src: 'ui/partials/*',
|
||||
dest: 'dist/partials/',
|
||||
nonull: true,
|
||||
flatten: true
|
||||
},
|
||||
// Copy fonts
|
||||
fonts:{
|
||||
expand: true,
|
||||
src: 'ui/vendor/fonts/*',
|
||||
dest: 'dist/fonts/',
|
||||
nonull: true,
|
||||
flatten: true
|
||||
},
|
||||
// Copy vendor files that don't work with grouped minification
|
||||
vendor:{
|
||||
files: [
|
||||
{ src: 'ui/vendor/defaults.js',
|
||||
dest: 'dist/vendor/defaults.js',
|
||||
nonull: true
|
||||
},
|
||||
{ src: 'ui/vendor/ngReact/ngReact.min.js',
|
||||
dest: 'dist/vendor/ngReact/ngReact.min.js',
|
||||
nonull: true
|
||||
},
|
||||
{ cwd: 'ui/vendor/react',
|
||||
src: '*',
|
||||
dest: 'dist/vendor/react',
|
||||
nonull: true,
|
||||
expand: true
|
||||
},
|
||||
]
|
||||
},
|
||||
// Copy html in plugins, make sure not to flatten
|
||||
// to retain the directory structure for the html
|
||||
// and make paths relative with cwd definition.
|
||||
plugins:{
|
||||
expand: true,
|
||||
cwd: 'ui/plugins/',
|
||||
src: '**/*.html',
|
||||
dest: 'dist/plugins/',
|
||||
nonull: true,
|
||||
flatten: false
|
||||
}
|
||||
},
|
||||
babel: {
|
||||
options: {
|
||||
compact: true,
|
||||
sourceMap: false,
|
||||
presets: ['babel-preset-es2015']
|
||||
},
|
||||
dist: {
|
||||
files: {
|
||||
'.tmp/concat/js/index.min.js': '.tmp/concat/js/index.min.js',
|
||||
'.tmp/concat/js/logviewer.min.js': '.tmp/concat/js/logviewer.min.js',
|
||||
'.tmp/concat/js/perf.min.js': '.tmp/concat/js/perf.min.js',
|
||||
'.tmp/concat/js/admin.min.js': '.tmp/concat/js/admin.min.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify:{
|
||||
options:{
|
||||
report: 'min',
|
||||
// Cannot use mangle, it will break angularjs's dependency
|
||||
// injection
|
||||
mangle: false
|
||||
}
|
||||
},
|
||||
ngtemplates: {
|
||||
treeherder: {
|
||||
cwd: 'ui',
|
||||
src: ['partials/main/*.html', 'plugins/**/*.html'],
|
||||
dest: 'dist/js/index.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/index.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
},
|
||||
logviewer: {
|
||||
cwd: 'ui',
|
||||
src: ['partials/main/thNotificationsBox.html', 'partials/logviewer/*.html'],
|
||||
dest: 'dist/js/logviewer.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/logviewer.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
},
|
||||
failureviewer: {
|
||||
cwd: 'ui',
|
||||
src: ['partials/main/thNotificationsBox.html'],
|
||||
dest: 'dist/js/failureviewer.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/failureviewer.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
},
|
||||
perf: {
|
||||
cwd: 'ui',
|
||||
src: ['partials/main/thLogoutMenu.html',
|
||||
'partials/perf/*.html',
|
||||
'partials/perf/*.tmpl'],
|
||||
dest: 'dist/js/perf.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/perf.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
// intentionally NOT collapsing whitespace here,
|
||||
// so perf regression templates are handled correctly
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
},
|
||||
admin: {
|
||||
cwd: 'ui',
|
||||
src: ['partials/main/thLogoutMenu.html',
|
||||
'partials/main/thHelpMenu.html',
|
||||
'partials/main/thNotificationsBox.html',
|
||||
'partials/main/thMultiSelect.html',
|
||||
'partials/admin/*.html'],
|
||||
dest: 'dist/js/admin.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/admin.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
},
|
||||
userguide: {
|
||||
cwd: 'ui',
|
||||
src: 'partials/main/thShortcutTable.html',
|
||||
dest: 'dist/js/userguide.min.js',
|
||||
options: {
|
||||
usemin: 'dist/js/userguide.min.js',
|
||||
append: true,
|
||||
htmlmin: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
eslint: {
|
||||
options: {
|
||||
config: '.eslintrc'
|
||||
},
|
||||
target: ['ui/']
|
||||
}
|
||||
});
|
||||
|
||||
// Default tasks
|
||||
grunt.registerTask('build', [
|
||||
'clean:dist',
|
||||
'copy:main',
|
||||
'copy:img',
|
||||
'copy:fonts',
|
||||
'copy:vendor',
|
||||
'useminPrepare',
|
||||
'concat',
|
||||
'cssmin',
|
||||
'babel',
|
||||
'uglify',
|
||||
'usemin',
|
||||
'ngtemplates',
|
||||
'cacheBust',
|
||||
'clean:tmp'
|
||||
]);
|
||||
|
||||
grunt.registerTask('checkjs', [
|
||||
'eslint'
|
||||
]);
|
||||
};
|
|
@ -116,15 +116,13 @@ To do this:
|
|||
git checkout (your feature branch)
|
||||
git checkout -b gh-pages
|
||||
cp ui/js/config/sample.local.conf.js ui/js/config/local.conf.js
|
||||
git add -f ui/js/config/local.conf.js
|
||||
git commit -m "Add temp config file to make the UI use prod's API"
|
||||
yarn run build
|
||||
git add -f ui/js/config/local.conf.js dist/
|
||||
git commit -m "Add temp config file and dist directory to make the UI use prod's API"
|
||||
|
||||
* Push the ``gh-pages`` branch to your Treeherder fork.
|
||||
|
||||
* Tell people to visit: ``https://<your-username>.github.io/treeherder/ui/``
|
||||
|
||||
There is no need to perform a ``yarn run build`` prior. After switching away from the local gh-pages branch, you will need to recreate ``ui/js/config/local.conf.js`` if desired, due to the ``git add -f``.
|
||||
|
||||
* Tell people to visit: ``https://<your-username>.github.io/treeherder/dist/``
|
||||
|
||||
Updating package.json
|
||||
---------------------
|
||||
|
|
|
@ -5,12 +5,11 @@ Testing the UI build locally
|
|||
----------------------------
|
||||
|
||||
During local development the UI is served in its original, unprocessed form. In
|
||||
production, a minified/built version of the UI (generated using grunt) is used instead.
|
||||
production, a minified/built version of the UI (generated using webpack) is used instead.
|
||||
|
||||
To build the UI locally:
|
||||
|
||||
* Install local dependencies by running ``yarn install --no-bin-links`` from the project root.
|
||||
* Run ``yarn run build`` to create the ``dist`` directory.
|
||||
|
||||
Then to serve assets from this directory instead of ``ui/``, in the Vagrant environment
|
||||
set ``SERVE_MINIFIED_UI=True`` before starting gunicorn/runserver.
|
||||
Then start gunicorn/runserver as usual.
|
||||
|
|
|
@ -67,6 +67,17 @@ Starting a local Treeherder instance
|
|||
|
||||
this is more convenient because it automatically refreshes every time there's a change in the code.
|
||||
|
||||
* You must also build the UI. Open a new terminal window and ``vagrant ssh`` to
|
||||
the VM again, then run the following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ~/treeherder$ yarn install --no-bin-links
|
||||
vagrant ~/treeherder$ yarn run start:local
|
||||
|
||||
This will build the UI code in the ``dist/`` folder and keep watching for
|
||||
new changes (See the :doc:`UI installation section <ui/installation>` for more ways to work with the UI code).
|
||||
|
||||
* Visit http://localhost:8000 in your browser. Note: There will be no data to display until the ingestion tasks are run.
|
||||
|
||||
Running the ingestion tasks
|
||||
|
|
|
@ -1,48 +1,91 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
You can work on the UI without needing a VM, by using web-server.js.
|
||||
There are a few limitations, such as login not being available, but it works well enough for quick testing. For instructions on how to serve the UI with working URL rewriting, see the Vagrant instructions.
|
||||
It's possible to work on the UI without setting up the Vagrant VM. There are a
|
||||
few limitations, such as login not being available, but it works well enough for
|
||||
quick testing. For instructions on how to serve the UI with working URL rewriting,
|
||||
see the Vagrant instructions.
|
||||
|
||||
Cloning the Repo
|
||||
----------------
|
||||
To get started:
|
||||
|
||||
* Clone the `treeherder repo`_ from Github.
|
||||
|
||||
Running the web-server
|
||||
----------------------
|
||||
|
||||
* Install `Node.js`_ and Yarn_ if not present.
|
||||
* ``yarn install --no-bin-links`` to install all dependencies.
|
||||
* Open a shell, cd into the root of the repository you just cloned and type:
|
||||
* Run ``yarn install --no-bin-links`` to install all dependencies.
|
||||
|
||||
Running the standalone development server
|
||||
-----------------------------------------
|
||||
|
||||
The default development server runs the unminified UI and fetches data from the
|
||||
production site. You do not need to set up the Vagrant VM, but login will be unavailable.
|
||||
|
||||
* Start the development server by running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./web-server.js
|
||||
$ yarn run start
|
||||
|
||||
Viewing the UI
|
||||
--------------
|
||||
* The server will perform an initial build and then watch for new changes. Once the server is running, you can navigate to: `<http://localhost:5000>`_ to see the UI.
|
||||
|
||||
Once the server is running, you can navigate to:
|
||||
`<http://localhost:8000>`_
|
||||
To run the unminified UI with data from the staging site instead of the production site, type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ yarn run start:stage
|
||||
|
||||
If you need to serve data from another domain, type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ SERVICE_DOMAIN=<url> yarn run start
|
||||
|
||||
This will run the unminified UI using ``<url>`` as the service domain.
|
||||
|
||||
Running the unminified UI with Vagrant
|
||||
--------------------------------------
|
||||
You may also run the unminified UI using the full treeherder Vagrant project.
|
||||
|
||||
First, make sure you have set up Vagrant and ingested some data as described in the main
|
||||
installation instructions, then follow these steps:
|
||||
|
||||
* SSH to the Vagrant machine and start the treeherder service, like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ~/treeherder$ ./manage.py runserver
|
||||
|
||||
* Then, open a new terminal window and SSH to the Vagrant machine again. Run the
|
||||
following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ~/treeherder$ yarn run start:local
|
||||
|
||||
This will watch UI files for changes and build an unminified version in the ``dist/`` directory.
|
||||
Note that this process is a little slower than using the regular development server, so you may
|
||||
wish to use it only for development that requires a frontend login.
|
||||
|
||||
Building the minified UI with Vagrant
|
||||
-------------------------------------
|
||||
If you would like to view the minified production version of the UI with Vagrant, follow these steps:
|
||||
|
||||
* SSH to the Vagrant machine and start the treeherder service:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ~/treeherder$ ./manage.py runserver
|
||||
|
||||
* Then run the build task (either outside or inside of the Vagrant machine):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ yarn run build
|
||||
|
||||
Once the build is complete, the minified version of the UI will now be accessible at http://localhost:8000.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
By default, the web server will serve data from treeherder production. If you wish to test the UI against treeherder stage instead, type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./web-server.js --stage
|
||||
|
||||
If you wish to test the UI against a custom service, type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./web-server.js --server <url>
|
||||
|
||||
|
||||
Please note that if ``ui/js/config/local.conf.js`` exists, the above configuration will be overwritten by ``thServiceDomain`` in the config file.
|
||||
Please note that if ``ui/js/config/local.conf.js`` exists, the above configuration will be overwritten by the ``thServiceDomain`` defined in that config file.
|
||||
|
||||
If you wish to run the full treeherder Vagrant project (service + UI), remember to remove local.conf.js or else change ``thServiceDomain`` within it to refer to ``vagrant``, so the UI will use the local Vagrant service API.
|
||||
|
||||
|
@ -51,24 +94,39 @@ Validating JavaScript
|
|||
|
||||
We run our JavaScript code in the frontend through eslint_ to ensure
|
||||
that new code has a consistent style and doesn't suffer from common
|
||||
errors. Before submitting a patch, check that your code passes these tests.
|
||||
|
||||
* If you haven't already done so, install local dependencies by running ``yarn install --no-bin-links`` from the project root.
|
||||
* Run ``yarn run lint``.
|
||||
|
||||
.. _eslint: http://eslint.org/
|
||||
errors. Eslint will run automatically when you build the JavaScript code
|
||||
or run the development server. A production build will fail if your code
|
||||
does not match the style requirements in ``.eslintrc``.
|
||||
|
||||
Running the unit tests
|
||||
======================
|
||||
|
||||
The unit tests for the UI are run with Karma_. To do this:
|
||||
The unit tests for the UI are run with Karma_ and Jasmine_. React components are tested with enzyme_. To run the tests:
|
||||
|
||||
* If you haven't already done so, install local dependencies by running ``yarn install --no-bin-links`` from the project root.
|
||||
* Run ``yarn test``.
|
||||
* Then run the following command to execute the tests:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ yarn run test
|
||||
|
||||
After the tests have finished, you can find a coverage report in the `coverage/` directory.
|
||||
|
||||
Watching the test files
|
||||
-----------------------
|
||||
While working on the frontend, you may wish to watch JavaScript files and re-run tests
|
||||
automatically when files change. To do this, you may run the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ yarn run test:watch
|
||||
|
||||
The tests will perform an initial run and then re-execute each time a project file is changed.
|
||||
|
||||
.. _Karma: http://karma-runner.github.io/0.8/config/configuration-file.html
|
||||
.. _treeherder repo: https://github.com/mozilla/treeherder
|
||||
.. _Node.js: https://nodejs.org/en/download/current/
|
||||
.. _eslint: http://eslint.org
|
||||
.. _Jasmine: https://jasmine.github.io/
|
||||
.. _enzyme: http://airbnb.io/enzyme/
|
||||
.. _Yarn: https://yarnpkg.com/en/docs/install
|
||||
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
'use strict';
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const lintPreset = require('./lint');
|
||||
const reactPreset = require('neutrino-preset-react');
|
||||
const HtmlPlugin = require('html-webpack-plugin');
|
||||
const Md5HashPlugin = require('webpack-md5-hash');
|
||||
|
||||
const CWD = process.cwd();
|
||||
const SRC = path.join(CWD, 'src'); // neutrino's default source directory
|
||||
const UI = path.join(CWD, 'ui');
|
||||
const DIST = path.join(CWD, 'dist');
|
||||
const INDEX_TEMPLATE = path.join(UI, 'index.html');
|
||||
const ADMIN_TEMPLATE = path.join(UI, 'admin.html');
|
||||
const PERF_TEMPLATE = path.join(UI, 'perf.html');
|
||||
const LOGVIEWER_TEMPLATE = path.join(UI, 'logviewer.html');
|
||||
const FAILUREVIEWER_TEMPLATE = path.join(UI, 'failureviewer.html');
|
||||
const USERGUIDE_TEMPLATE = path.join(UI, 'userguide.html');
|
||||
|
||||
const HTML_MINIFY_OPTIONS = {
|
||||
useShortDoctype: true,
|
||||
keepClosingSlash: true,
|
||||
collapseWhitespace: true,
|
||||
preserveLineBreaks: true
|
||||
};
|
||||
|
||||
module.exports = neutrino => {
|
||||
reactPreset(neutrino);
|
||||
lintPreset(neutrino);
|
||||
|
||||
// Change the ouput path from build/ to dist/:
|
||||
neutrino.config.output.path(DIST);
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
// Include files from node_modules in the separate, more-cacheable vendor chunk:
|
||||
const jsDeps = [
|
||||
'angular', 'angular-cookies', 'angular-local-storage', 'angular-resource',
|
||||
'angular-route', 'angular-sanitize', 'angular-toarrayfilter', 'angular-ui-bootstrap',
|
||||
'angular-ui-router', 'bootstrap/dist/js/bootstrap', 'hawk', 'jquery', 'jquery.scrollto',
|
||||
'js-yaml', 'mousetrap', 'react', 'react-dom', 'taskcluster-client'
|
||||
];
|
||||
jsDeps.map(dep =>
|
||||
neutrino.config.entry('vendor').add(dep)
|
||||
);
|
||||
|
||||
// Ensure chunkhashes for unchanged chunks don't change -- this allows the bundled vendor
|
||||
// file to keep its hash and stay cached in the user's browser when it hasn't been updated.
|
||||
neutrino.config.output.chunkFilename('[chunkhash].[id].chunk.js');
|
||||
neutrino.config.plugin('hash').use(Md5HashPlugin);
|
||||
}
|
||||
|
||||
// Neutrino looks for the entry at src/index.js by default; Delete this and add the index in ui/:
|
||||
neutrino.config
|
||||
.entry('index')
|
||||
.delete(path.join(SRC, 'index.js'))
|
||||
.add(path.join(UI, 'entry-index.js'))
|
||||
.end();
|
||||
// Add several other treeherder entry points:
|
||||
neutrino.config
|
||||
.entry('admin')
|
||||
.add(path.join(UI, 'entry-admin.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('perf')
|
||||
.add(path.join(UI, 'entry-perf.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('logviewer')
|
||||
.add(path.join(UI, 'entry-logviewer.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('failureviewer')
|
||||
.add(path.join(UI, 'entry-failureviewer.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('userguide')
|
||||
.add(path.join(UI, 'entry-userguide.js'))
|
||||
.end();
|
||||
|
||||
// Likewise, we must modify the include paths for the compile rule to look in ui/ instead of src/:
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('compile')
|
||||
.include(UI);
|
||||
|
||||
// The page templates should be excluded from the file-loader, so that they don't collide with the html plugins:
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('html')
|
||||
._exclude
|
||||
.add([USERGUIDE_TEMPLATE, PERF_TEMPLATE, LOGVIEWER_TEMPLATE,
|
||||
INDEX_TEMPLATE, ADMIN_TEMPLATE, FAILUREVIEWER_TEMPLATE]);
|
||||
|
||||
// Set up templates for each entry point:
|
||||
neutrino.config.plugins.delete('html');
|
||||
neutrino.config
|
||||
.plugin('html-index')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
template: INDEX_TEMPLATE,
|
||||
chunks: ['index', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-admin')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'admin.html',
|
||||
template: ADMIN_TEMPLATE,
|
||||
chunks: ['admin', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-perf')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'perf.html',
|
||||
template: PERF_TEMPLATE,
|
||||
chunks: ['perf', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-failureviewer')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'failureviewer.html',
|
||||
template: FAILUREVIEWER_TEMPLATE,
|
||||
chunks: ['failureviewer', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-logviewer')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'logviewer.html',
|
||||
template: LOGVIEWER_TEMPLATE,
|
||||
chunks: ['logviewer', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-userguide')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'userguide.html',
|
||||
template: USERGUIDE_TEMPLATE,
|
||||
chunks: ['userguide', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
// Adjust babel env to loosen up browser compatibility requirements
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('compile')
|
||||
.loader('babel', ({ options }) => {
|
||||
options.presets[0][1].targets.browsers = [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Edge versions',
|
||||
'last 1 Safari versions'
|
||||
];
|
||||
return options;
|
||||
});
|
||||
|
||||
// Now provide a few globals (providing angular here does not seem to work, though)
|
||||
neutrino.config
|
||||
.plugin('provide')
|
||||
.use(webpack.ProvidePlugin, {
|
||||
$: require.resolve('jquery'),
|
||||
jQuery: require.resolve('jquery'),
|
||||
'window.$': require.resolve('jquery'),
|
||||
'window.jQuery': require.resolve('jquery'),
|
||||
React: require.resolve('react'),
|
||||
_: require.resolve('lodash'),
|
||||
treeherder: require.resolve(path.join(UI, 'js/treeherder.js')),
|
||||
treeherderApp: require.resolve(path.join(UI, 'js/treeherder_app.js')),
|
||||
perf: require.resolve(path.join(UI, 'js/perf.js')),
|
||||
admin: require.resolve(path.join(UI, 'js/admin.js')),
|
||||
failureViewerApp: require.resolve(path.join(UI, 'js/failureviewer.js')),
|
||||
logViewerApp: require.resolve(path.join(UI, 'js/logviewer.js')),
|
||||
userguideApp: require.resolve(path.join(UI, 'js/userguide.js'))
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.CWD = CWD;
|
||||
module.exports.UI = UI;
|
||||
module.exports.DIST = DIST;
|
|
@ -0,0 +1,34 @@
|
|||
'use strict';
|
||||
const webpack = require('webpack');
|
||||
const basePreset = require('./base');
|
||||
const UI = require('./base').UI;
|
||||
|
||||
// The service domain is used to determine whether login is available in the auth component.
|
||||
let SERVICE_DOMAIN = process.env.SERVICE_DOMAIN;
|
||||
|
||||
// Set the service domain to production if no environment value was provided, since
|
||||
// webpack-dev-server doesn't serve data from the vagrant machine.
|
||||
if (typeof SERVICE_DOMAIN === 'undefined') {
|
||||
SERVICE_DOMAIN = 'https://treeherder.mozilla.org';
|
||||
}
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
|
||||
// Set service domain so that ui/js/config can use it:
|
||||
neutrino.config
|
||||
.plugin('define')
|
||||
.use(webpack.DefinePlugin, {
|
||||
SERVICE_DOMAIN: JSON.stringify(SERVICE_DOMAIN)
|
||||
});
|
||||
|
||||
// Set up the dev server with an api proxy to the service domain:
|
||||
neutrino.config.devServer
|
||||
.contentBase(UI)
|
||||
.set('proxy', {
|
||||
'/api/*': {
|
||||
target: SERVICE_DOMAIN,
|
||||
changeOrigin: true
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
'use strict';
|
||||
|
||||
const merge = require('deepmerge');
|
||||
const lintBase = require('neutrino-lint-base');
|
||||
const path = require('path');
|
||||
|
||||
const CWD = process.cwd();
|
||||
const UI = path.join(CWD, 'ui');
|
||||
|
||||
module.exports = neutrino => {
|
||||
lintBase(neutrino);
|
||||
neutrino.config.module
|
||||
.rule('lint')
|
||||
.include(UI)
|
||||
.test(/\.jsx?$/)
|
||||
.loader('eslint', props => merge(props, {
|
||||
options: {
|
||||
plugins: ['react'],
|
||||
envs: ['browser', 'es6', 'node'],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
ecmaFeatures: {
|
||||
es6: true,
|
||||
jsx: true,
|
||||
impliedStrict: false
|
||||
}
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
rules: {
|
||||
'accessor-pairs': 'error',
|
||||
'comma-style': 'error',
|
||||
'eol-last': 'error',
|
||||
'eqeqeq': 'error',
|
||||
'guard-for-in': 'error',
|
||||
'indent': ['error', 4, {
|
||||
'SwitchCase': 1
|
||||
}],
|
||||
'keyword-spacing': 'error',
|
||||
'linebreak-style': 'error',
|
||||
'new-cap': 'error',
|
||||
'new-parens': 'error',
|
||||
'no-array-constructor': 'error',
|
||||
'no-bitwise': 'error',
|
||||
'no-caller': 'error',
|
||||
'no-div-regex': 'error',
|
||||
'no-else-return': 'error',
|
||||
'no-empty-pattern': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'error',
|
||||
'no-floating-decimal': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-iterator': 'error',
|
||||
'no-label-var': 'error',
|
||||
'no-labels': 'error',
|
||||
'no-lone-blocks': 'error',
|
||||
'no-lonely-if': 'error',
|
||||
'no-multi-spaces': 'error',
|
||||
'no-multi-str': 'error',
|
||||
'no-native-reassign': 'error',
|
||||
'no-new': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-new-object': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-shadow-restricted-names': 'error',
|
||||
'no-spaced-func': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-undef-init': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-void': 'error',
|
||||
'no-with': 'error',
|
||||
'semi': 'error',
|
||||
'strict': ['error', 'global'],
|
||||
'yoda': 'error'
|
||||
},
|
||||
globals: ['angular', '$', '_', 'treeherder', 'jsyaml', 'perf',
|
||||
'treeherderApp', 'failureViewerApp', 'logViewerApp',
|
||||
'userguideApp', 'admin', 'Mousetrap', 'jQuery', 'React',
|
||||
'hawk', 'jsonSchemaDefaults'
|
||||
]
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
'use strict';
|
||||
|
||||
const webpack = require('webpack');
|
||||
const productionPreset = require('./production');
|
||||
|
||||
// Taskcluster login POSTs in treeherder require that the UI is served from the same
|
||||
// origin as the backend. To allow for logins in watched, unminified mode, here we
|
||||
// build a webpack watcher that recompiles sources to dist/ on change, so that the
|
||||
// Vagrant box may serve them. This approach is slower than using the dev server, but
|
||||
// it may become unnecessary if Bug 1317752 (Enable logging in with Taskcluster Auth
|
||||
// cross-domain) is completed.
|
||||
|
||||
module.exports = neutrino => {
|
||||
// This configuration is based on the production config, but is not minified
|
||||
productionPreset(neutrino);
|
||||
|
||||
// Removing the dev server configuration puts neutrino into watch mode
|
||||
neutrino.config.devServer.clear();
|
||||
|
||||
// Don't minify:
|
||||
neutrino.config.plugins.delete('minify');
|
||||
|
||||
// Now we must remove some hot-reload settings triggered by the 'development'
|
||||
// NODE_ENV variable set by `neutrino start`:
|
||||
// Remove hot loader plugin:
|
||||
neutrino.config.plugins.delete('hot');
|
||||
|
||||
// Remove hot loader patch from index:
|
||||
const protocol = process.env.HTTPS ? 'https' : 'http';
|
||||
const host = process.env.HOST || 'localhost';
|
||||
const port = parseInt(process.env.PORT) || 5000;
|
||||
neutrino.config.entry('index')
|
||||
.delete(`webpack-dev-server/client?${protocol}://${host}:${port}/`)
|
||||
.delete('webpack/hot/dev-server')
|
||||
.delete(require.resolve('react-hot-loader/patch'));
|
||||
|
||||
// Remove hot loader react plugin:
|
||||
neutrino.config.module
|
||||
.rule('compile')
|
||||
.loader('babel', ({ options }) => {
|
||||
options.env.development.plugins.shift();
|
||||
return { options };
|
||||
});
|
||||
|
||||
// Finally, the service domain should be set:
|
||||
neutrino.config
|
||||
.plugin('define')
|
||||
.use(webpack.DefinePlugin, {
|
||||
SERVICE_DOMAIN: JSON.stringify(process.env.SERVICE_DOMAIN)
|
||||
});
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
const webpack = require('webpack');
|
||||
const basePreset = require('./base');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
const CWD = require('./base').CWD;
|
||||
const UI = require('./base').UI;
|
||||
const DIST = require('./base').DIST;
|
||||
|
||||
// The service domain is used to determine whether login is available in the auth component.
|
||||
let SERVICE_DOMAIN = process.env.SERVICE_DOMAIN;
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
|
||||
neutrino.config.plugin('minify')
|
||||
.inject(BabiliPlugin => new BabiliPlugin({
|
||||
evaluate: false, // prevents some minification errors
|
||||
}
|
||||
));
|
||||
|
||||
// Define the service domain globally so that window.thServiceDomain can be set:
|
||||
neutrino.config.plugin('define')
|
||||
.use(webpack.DefinePlugin, {
|
||||
SERVICE_DOMAIN: JSON.stringify(SERVICE_DOMAIN)
|
||||
});
|
||||
|
||||
|
||||
// The copy plugin is overwritten and not injected so that when this preset is
|
||||
// imported in ./local-watch.js and run via `neutrino start`, it is still included
|
||||
// in the config (it is only applied in !development by default):
|
||||
neutrino.config.plugin('copy')
|
||||
.use(CopyPlugin, [{
|
||||
context: UI,
|
||||
from: '**'
|
||||
}, {
|
||||
from: './contribute.json',
|
||||
to: 'contribute.json'
|
||||
}], {
|
||||
ignore: ['*.js', '*.jsx', '*.css', '*.eot',
|
||||
'*.otf', '*.svg', '*.ttf', '*.woff', '*.woff2']
|
||||
});
|
||||
|
||||
// Likewise for this clean plugin:
|
||||
neutrino.config.plugin('clean')
|
||||
.use(CleanPlugin, [DIST], { root: CWD } );
|
||||
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
'use strict';
|
||||
const basePreset = require('./base');
|
||||
const karmaPreset = require('neutrino-preset-karma');
|
||||
const UI = require('./base').UI;
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
karmaPreset(neutrino);
|
||||
|
||||
// Add an isntanbul loader to generate coverage for js(x) in ui/
|
||||
neutrino.config.module
|
||||
.rule('coverage')
|
||||
.post()
|
||||
.include(UI)
|
||||
.test(/\.jsx?$/)
|
||||
.loader('istanbul', require.resolve('istanbul-instrumenter-loader'));
|
||||
|
||||
// Normal karma config
|
||||
neutrino.custom.karma = {
|
||||
browsers: ['Firefox'],
|
||||
coverageIstanbulReporter: {
|
||||
reports: ['html'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
plugins: [
|
||||
require.resolve('karma-webpack'),
|
||||
require.resolve('karma-firefox-launcher'),
|
||||
require.resolve('karma-coverage'),
|
||||
require.resolve('karma-jasmine'),
|
||||
require.resolve('karma-coverage-istanbul-reporter')
|
||||
],
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'tests/ui/unit/init.js',
|
||||
{pattern: 'tests/ui/mock/**/*.json', watched: true, served: true, included: false}
|
||||
],
|
||||
preprocessors: {
|
||||
'tests/ui/unit/init.js': ['webpack'],
|
||||
},
|
||||
reporters: ['progress', 'coverage-istanbul'],
|
||||
};
|
||||
};
|
74
package.json
74
package.json
|
@ -10,42 +10,50 @@
|
|||
"node": "7.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-preset-es2015": "6.18.0",
|
||||
"grunt": "1.0.1",
|
||||
"grunt-angular-templates": "1.1.0",
|
||||
"grunt-babel": "6.0.0",
|
||||
"grunt-cache-bust": "1.4.1",
|
||||
"grunt-cli": "1.2.0",
|
||||
"grunt-contrib-clean": "1.0.0",
|
||||
"grunt-contrib-concat": "1.0.1",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-cssmin": "1.0.2",
|
||||
"grunt-contrib-uglify": "2.0.0",
|
||||
"grunt-usemin": "3.1.1",
|
||||
"load-grunt-tasks": "3.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^13.1.1",
|
||||
"eslint": "^3.17.1",
|
||||
"grunt-eslint": "19.0.0",
|
||||
"grunt-html-angular-validate": "0.5.8",
|
||||
"angular": "1.5.8",
|
||||
"angular-cookies": "1.5.8",
|
||||
"angular-local-storage": "0.5.0",
|
||||
"angular-mocks": "1.5.8",
|
||||
"angular-resource": "1.5.8",
|
||||
"angular-route": "1.5.8",
|
||||
"angular-sanitize": "1.5.8",
|
||||
"angular-toarrayfilter": "1.0.1",
|
||||
"angular-ui-bootstrap": "1.3.3",
|
||||
"angular-ui-router": "0.4.2",
|
||||
"bootstrap": "3.3.5",
|
||||
"deepmerge": "1.3.2",
|
||||
"enzyme": "2.7.1",
|
||||
"font-awesome": "4.4.0",
|
||||
"hawk": "2.3.1",
|
||||
"istanbul-instrumenter-loader": "2.0.0",
|
||||
"jasmine-core": "2.5.2",
|
||||
"karma": "1.3.0",
|
||||
"karma-browserify": "^5.1.0",
|
||||
"karma-coverage": "1.1.1",
|
||||
"jquery": "2.1.3",
|
||||
"jquery.scrollto": "2.1.0",
|
||||
"js-yaml": "3.7.0",
|
||||
"json-schema-defaults": "0.2.0",
|
||||
"karma": "1.5.0",
|
||||
"karma-coverage-istanbul-reporter": "0.2.3",
|
||||
"karma-firefox-launcher": "1.0.0",
|
||||
"karma-jasmine": "1.0.2",
|
||||
"karma-junit-reporter": "1.0.0",
|
||||
"karma-ng-scenario": "0.1.0",
|
||||
"minimist": "1.2.0",
|
||||
"ngreact-test-utils": "^1.0.3",
|
||||
"react": "^15.3.1",
|
||||
"react-dom": "^15.4.1",
|
||||
"watchify": "^3.8.0"
|
||||
"karma-jasmine": "1.1.0",
|
||||
"lodash": "4.17.4",
|
||||
"mousetrap": "1.4.6",
|
||||
"neutrino": "4.3.1",
|
||||
"neutrino-lint-base": "4.3.1",
|
||||
"neutrino-preset-karma": "4.2.0",
|
||||
"neutrino-preset-react": "4.2.3",
|
||||
"ngreact": "0.3.0",
|
||||
"react": "15.3.1",
|
||||
"react-addons-test-utils": "15.3.1",
|
||||
"react-dom": "15.3.1",
|
||||
"taskcluster-client": "1.6.3",
|
||||
"webpack-md5-hash": "0.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./node_modules/grunt-cli/bin/grunt build --production",
|
||||
"lint": "./node_modules/grunt-cli/bin/grunt checkjs",
|
||||
"test": "./node_modules/karma/bin/karma start tests/ui/config/karma.conf.js --single-run --browsers Firefox"
|
||||
"build": "./node_modules/neutrino/bin/neutrino build --presets ./neutrino-custom/production.js",
|
||||
"start": "./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/development.js",
|
||||
"start:local": "./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/local-watch.js",
|
||||
"start:stage": "SERVICE_DOMAIN=https://treeherder.allizom.org ./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/development.js",
|
||||
"test": "./node_modules/neutrino/bin/neutrino test --presets ./neutrino-custom/test.js",
|
||||
"test:watch": "./node_modules/neutrino/bin/neutrino test --watch --presets ./neutrino-custom/test.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,11 +36,11 @@ def test_perfherder_main(initial_data, live_server, selenium):
|
|||
This tests that the basic graphs view load and we can click the add tests button
|
||||
'''
|
||||
selenium.get(live_server.url + '/perf.html')
|
||||
add_test_button = WebDriverWait(selenium, 10).until(
|
||||
add_test_button = WebDriverWait(selenium, 20).until(
|
||||
EC.presence_of_element_located((By.ID, 'add-test-data-button'))
|
||||
)
|
||||
add_test_button.click()
|
||||
WebDriverWait(selenium, 10).until(
|
||||
WebDriverWait(selenium, 20).until(
|
||||
EC.presence_of_element_located((By.ID, 'performance-test-chooser'))
|
||||
)
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// NOTE: IF TESTS WON'T RUN
|
||||
// angular-scenario.js in the vendor lib will prevent the
|
||||
// Karma tests from running. Delete it when upgrading AngularJS.
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
frameworks: ['jasmine', 'browserify'],
|
||||
|
||||
basePath: '../../../',
|
||||
|
||||
files: [
|
||||
'ui/vendor/angular/angular.js',
|
||||
'ui/vendor/angular/angular-*.js',
|
||||
'tests/ui/vendor/react-with-addons.min.js',
|
||||
'ui/vendor/react/react-dom.min.js',
|
||||
'ui/vendor/ngReact/ngReact.min.js',
|
||||
'ui/vendor/ui-bootstrap-*.js',
|
||||
'ui/vendor/jquery-*.js',
|
||||
'ui/vendor/jquery.ui.effect.js',
|
||||
'ui/vendor/jquery.ui.effect-highlight.js',
|
||||
'ui/vendor/bootstrap*.js',
|
||||
'ui/js/treeherder.js',
|
||||
'ui/js/filters.js',
|
||||
'ui/js/providers.js',
|
||||
'ui/js/values.js',
|
||||
'ui/js/logviewer.js',
|
||||
'ui/js/failureviewer.js',
|
||||
'ui/js/userguide.js',
|
||||
'ui/js/treeherder_app.js',
|
||||
'ui/js/controllers/*.js',
|
||||
'ui/js/directives/treeherder/**/*.js',
|
||||
'ui/js/models/**/*.js',
|
||||
'ui/js/services/**/*.js',
|
||||
'ui/js/reactrevisions.js',
|
||||
'ui/plugins/**/*.js',
|
||||
'tests/ui/vendor/jasmine-jquery.js',
|
||||
'tests/ui/unit/**/*.js',
|
||||
'ui/vendor/*.js',
|
||||
|
||||
// fixtures
|
||||
{pattern: 'tests/ui/mock/**/*.json', watched: true, served: true, included: false}
|
||||
],
|
||||
|
||||
autoWatch: false,
|
||||
singleRun: true,
|
||||
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
browsers: ['Firefox'],
|
||||
|
||||
junitReporter: {
|
||||
outputFile: 'test_out/unit.xml',
|
||||
suite: 'unit'
|
||||
},
|
||||
|
||||
reporters: ['progress', 'coverage'],
|
||||
preprocessors: {
|
||||
'ui/js/**/*.js': ['coverage'],
|
||||
'tests/ui/unit/react/**/*.js': ['browserify']
|
||||
},
|
||||
|
||||
browserify: {
|
||||
debug: true
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
// Karma/webpack entry for tests
|
||||
|
||||
// Global variables are set here instead of with webpack.ProvidePlugin
|
||||
// because neutrino removes plugin definitions for karma runs
|
||||
window.$ = require('jquery');
|
||||
window.jQuery = require('jquery');
|
||||
window._ = require('lodash');
|
||||
window.angular = require('angular');
|
||||
window.React = require('react');
|
||||
window.SERVICE_DOMAIN = '';
|
||||
window.thServiceDomain = '';
|
||||
require('react-dom');
|
||||
require('../vendor/jasmine-jquery.js');
|
||||
require('angular-mocks');
|
||||
require('angular-resource');
|
||||
require('angular-route');
|
||||
require('angular-sanitize');
|
||||
require('angular-cookies');
|
||||
require('angular-local-storage');
|
||||
require('angular-toarrayfilter');
|
||||
require('mousetrap');
|
||||
require('js-yaml');
|
||||
require('ngreact');
|
||||
require('angular-ui-bootstrap');
|
||||
require('../../../ui/vendor/resizer.js');
|
||||
|
||||
const jsContext = require.context('../../../ui/js', true, /^\.\/.*\.jsx?$/);
|
||||
window.treeherder = jsContext('./treeherder.js');
|
||||
window.treeherderApp = jsContext('./treeherder_app.js');
|
||||
window.admin = jsContext('./admin.js');
|
||||
window.perf = jsContext('./perf.js');
|
||||
window.failureViewerApp = jsContext('./failureviewer.js');
|
||||
window.logViewerApp = jsContext('./logviewer.js');
|
||||
window.userguideApp = jsContext('./userguide.js');
|
||||
jsContext('./values.js');
|
||||
jsContext('./providers.js');
|
||||
jsContext('./filters.js');
|
||||
|
||||
const controllerContext = require.context('../../../ui/js/controllers', true, /^\.\/.*\.jsx?$/);
|
||||
controllerContext.keys().forEach(controllerContext);
|
||||
const directiveContext = require.context('../../../ui/js/directives', true, /^\.\/.*\.jsx?$/);
|
||||
directiveContext.keys().forEach(directiveContext);
|
||||
const modelContext = require.context('../../../ui/js/models', true, /^\.\/.*\.jsx?$/);
|
||||
modelContext.keys().forEach(modelContext);
|
||||
const serviceContext = require.context('../../../ui/js/services', true, /^\.\/.*\.jsx?$/);
|
||||
serviceContext.keys().forEach(serviceContext);
|
||||
const componentContext = require.context('../../../ui/js/components', true, /^\.\/.*\.jsx?$/);
|
||||
componentContext.keys().forEach(componentContext);
|
||||
const pluginContext = require.context('../../../ui/plugins', true, /^\.\/.*\.jsx?$/);
|
||||
pluginContext.keys().forEach(pluginContext);
|
||||
|
||||
const testContext = require.context('./', true, /^\.\/.*\.tests\.jsx?$/);
|
||||
testContext.keys().forEach(testContext);
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
require('../../../../ui/js/models/job.js');
|
||||
describe('ThJobModel', function(){
|
||||
|
||||
var $httpBackend,
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
'use strict';
|
||||
var compile = require('ngreact-test-utils').compile;
|
||||
|
||||
describe('Revision list react component', () => {
|
||||
var $filter;
|
||||
var mockData;
|
||||
beforeEach(angular.mock.module('treeherder'));
|
||||
beforeEach(angular.mock.module('react'));
|
||||
beforeEach(inject((_$filter_) => {
|
||||
$filter = _$filter_;
|
||||
}));
|
||||
beforeEach(() => {
|
||||
var resultset = {
|
||||
"id": 151371,
|
||||
"revision_hash": "0056da58e1efd70711c8f98336eaf866f1aa8936",
|
||||
"revision": "5a110ad242ead60e71d2186bae78b1fb766ad5ff",
|
||||
"revisions_uri": "/api/project/mozilla-inbound/resultset/151371/revisions/",
|
||||
"revision_count": 3,
|
||||
"author": "ryanvm@gmail.com",
|
||||
"push_timestamp": 1481326280,
|
||||
"repository_id": 2,
|
||||
"revisions": [{
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "5a110ad242ead60e71d2186bae78b1fb766ad5ff",
|
||||
"author":"André Bargull <andre.bargull@gmail.com>",
|
||||
"comments": "Bug 1319926 - Part 2: Collect telemetry about deprecated String generics methods. r=jandem"
|
||||
},{
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "07d6bf74b7a2552da91b5e2fce0fa0bc3b457394",
|
||||
"author": "André Bargull <andre.bargull@gmail.com>",
|
||||
"comments":"Bug 1319926 - Part 1: Warn when deprecated String generics methods are used. r=jandem"
|
||||
},{
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "e83eaf2380c65400dc03c6f3615d4b2cef669af3",
|
||||
"author": "Frédéric Wang <fred.wang@free.fr>",
|
||||
"comments": "Bug 1322743 - Add STIX Two Math to the list of math fonts. r=karlt"
|
||||
}]
|
||||
};
|
||||
var repo = {
|
||||
"id": 2,
|
||||
"repository_group": {
|
||||
"name": "development",
|
||||
"description": ""
|
||||
},
|
||||
"name": "mozilla-inbound",
|
||||
"dvcs_type": "hg",
|
||||
"url": "https://hg.mozilla.org/integration/mozilla-inbound",
|
||||
"branch": null,
|
||||
"codebase": "gecko",
|
||||
"description": "",
|
||||
"active_status": "active",
|
||||
"performance_alerts_enabled": true,
|
||||
"pushlogURL": "https://hg.mozilla.org/integration/mozilla-inbound/pushloghtml"
|
||||
};
|
||||
// Mock these simple functions so we don't have to call ThRepositoryModel.load() first to use it
|
||||
repo.getRevisionHref = () => `${repo.url}/rev/${resultset.revision}`;
|
||||
repo.getPushLogHref = (revision) => `${repo.pushlogURL}?changeset=${revision}`;
|
||||
mockData = {
|
||||
resultset,
|
||||
repo
|
||||
};
|
||||
});
|
||||
|
||||
it('renders the correct number of revisions in a list', () => {
|
||||
var component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
var revisionItems = component.el[0].querySelectorAll('li');
|
||||
expect(revisionItems.length).toEqual(mockData['resultset']['revision_count']);
|
||||
});
|
||||
|
||||
it('renders the linked revision hashes', () => {
|
||||
var component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
var links = component.el[0].querySelectorAll('.revision-holder a');
|
||||
expect(links.length).toEqual(mockData['resultset']['revision_count']);
|
||||
Array.prototype.forEach.call(links, (link, i) => {
|
||||
expect(link.href).toEqual(mockData.repo.getRevisionHref());
|
||||
expect(link.title).toEqual(`Open revision ${mockData.resultset.revisions[i].revision} on ${mockData.repo.url}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the contributors\' initials', () => {
|
||||
var component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
var initials = component.el[0].querySelectorAll('.label.label-initials');
|
||||
expect(initials.length).toEqual(mockData.resultset.revision_count);
|
||||
Array.prototype.forEach.call(initials, (initial, i) => {
|
||||
var revisionData = mockData.resultset.revisions[i];
|
||||
var userTokens = revisionData.author.split(/[<>]+/);
|
||||
var name = userTokens[0];
|
||||
var email = null;
|
||||
if (userTokens.length > 1) email = userTokens[1];
|
||||
var nameString = name;
|
||||
if (email !== null) nameString += `: ${email}`;
|
||||
|
||||
expect(initial.outerHTML).toEqual($filter('initials')(name));
|
||||
expect(initial.parentNode.title).toEqual(nameString);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an "...and more" link if the revision count is higher than the max display count of 20', () => {
|
||||
mockData.resultset.revision_count = 21;
|
||||
|
||||
var component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
var revisionItems = component.el[0].querySelectorAll('li');
|
||||
expect(revisionItems.length).toEqual(mockData.resultset.revisions.length + 1);
|
||||
|
||||
var lastItem = revisionItems[revisionItems.length - 1];
|
||||
expect(lastItem.textContent).toEqual('\u2026and more');
|
||||
expect(lastItem.querySelector('a i.fa.fa-external-link-square')).toBeDefined();
|
||||
});
|
||||
|
||||
it('linkifies bugs IDs in the comments', () => {
|
||||
var escapedComment = _.escape(mockData.resultset.revisions[0].comments.split('\n')[0]);
|
||||
var linkifiedCommentText = $filter('linkifyBugs')(escapedComment);
|
||||
|
||||
var component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
var commentEm = component.el[0].querySelector('.revision-comment em');
|
||||
expect(commentEm.innerHTML).toEqual(linkifiedCommentText);
|
||||
|
||||
});
|
||||
|
||||
it('marks the revision as backed out if the words "Back/Backed out" appear in the comments', () => {
|
||||
var component, firstRevision;
|
||||
mockData.resultset.revisions[0].comments = "Backed out changeset a6e2d96c1274 (bug 1322565) for eslint failure";
|
||||
|
||||
component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
firstRevision = component.el[0].querySelector('li .revision');
|
||||
expect(firstRevision.getAttribute('data-tags').indexOf('backout')).not.toEqual(-1);
|
||||
|
||||
mockData.resultset.revisions[0].comments = "Back out changeset a6e2d96c1274 (bug 1322565) for eslint failure";
|
||||
component = compile('<revisions repo="repo" resultset="resultset" />', mockData);
|
||||
firstRevision = component.el[0].querySelector('li .revision');
|
||||
expect(firstRevision.getAttribute('data-tags').indexOf('backout')).not.toEqual(-1);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,178 @@
|
|||
'use strict';
|
||||
const mount = require('enzyme').mount;
|
||||
const revisions = require('../../../../ui/js/reactrevisions.jsx');
|
||||
const RevisionList = revisions.RevisionList;
|
||||
const RevisionItem = revisions.RevisionItem;
|
||||
const MoreRevisionsLink = revisions.MoreRevisionsLink;
|
||||
|
||||
describe('Revision list component', () => {
|
||||
let $injector, mockData;
|
||||
beforeEach(angular.mock.module('treeherder'));
|
||||
beforeEach(inject((_$injector_) => {
|
||||
$injector = _$injector_;
|
||||
|
||||
const repo = {
|
||||
"id": 2,
|
||||
"repository_group": {
|
||||
"name": "development",
|
||||
"description": ""
|
||||
},
|
||||
"name": "mozilla-inbound",
|
||||
"dvcs_type": "hg",
|
||||
"url": "https://hg.mozilla.org/integration/mozilla-inbound",
|
||||
"branch": null,
|
||||
"codebase": "gecko",
|
||||
"description": "",
|
||||
"active_status": "active",
|
||||
"performance_alerts_enabled": true,
|
||||
"pushlogURL": "https://hg.mozilla.org/integration/mozilla-inbound/pushloghtml"
|
||||
};
|
||||
// Mock these simple functions so we don't have to call ThRepositoryModel.load() first to use them
|
||||
repo.getRevisionHref = () => `${repo.url}/rev/${resultset.revision}`;
|
||||
repo.getPushLogHref = (revision) => `${repo.pushlogURL}?changeset=${revision}`;
|
||||
|
||||
const resultset = {
|
||||
"id": 151371,
|
||||
"revision_hash": "0056da58e1efd70711c8f98336eaf866f1aa8936",
|
||||
"revision": "5a110ad242ead60e71d2186bae78b1fb766ad5ff",
|
||||
"revisions_uri": "/api/project/mozilla-inbound/resultset/151371/revisions/",
|
||||
"revision_count": 3,
|
||||
"author": "ryanvm@gmail.com",
|
||||
"push_timestamp": 1481326280,
|
||||
"repository_id": 2,
|
||||
"revisions": [{
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "5a110ad242ead60e71d2186bae78b1fb766ad5ff",
|
||||
"author": "André Bargull <andre.bargull@gmail.com>",
|
||||
"comments": "Bug 1319926 - Part 2: Collect telemetry about deprecated String generics methods. r=jandem"
|
||||
}, {
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "07d6bf74b7a2552da91b5e2fce0fa0bc3b457394",
|
||||
"author": "André Bargull <andre.bargull@gmail.com>",
|
||||
"comments": "Bug 1319926 - Part 1: Warn when deprecated String generics methods are used. r=jandem"
|
||||
}, {
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "e83eaf2380c65400dc03c6f3615d4b2cef669af3",
|
||||
"author": "Frédéric Wang <fred.wang@free.fr>",
|
||||
"comments": "Bug 1322743 - Add STIX Two Math to the list of math fonts. r=karlt"
|
||||
}]
|
||||
};
|
||||
mockData = {
|
||||
resultset,
|
||||
repo
|
||||
};
|
||||
}));
|
||||
|
||||
it('renders the correct number of revisions in a list', () => {
|
||||
const wrapper = mount(<RevisionList repo={mockData.repo} resultset={mockData.resultset}
|
||||
$injector={$injector}/>);
|
||||
expect(wrapper.find(RevisionItem).length).toEqual(mockData['resultset']['revision_count']);
|
||||
});
|
||||
|
||||
it('renders an "...and more" link if the revision count is higher than the max display count of 20', () => {
|
||||
mockData.resultset.revision_count = 21;
|
||||
|
||||
const wrapper = mount(<RevisionList repo={mockData.repo} resultset={mockData.resultset}
|
||||
$injector={$injector}/>);
|
||||
expect(wrapper.find(MoreRevisionsLink).length).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Revision item component', () => {
|
||||
let $injector, linkifyBugsFilter, initialsFilter, mockData;
|
||||
beforeEach(angular.mock.module('treeherder'));
|
||||
beforeEach(inject((_$injector_, $filter) => {
|
||||
$injector = _$injector_;
|
||||
initialsFilter = $filter('initials');
|
||||
linkifyBugsFilter = $filter('linkifyBugs');
|
||||
|
||||
const repo = {
|
||||
"id": 2,
|
||||
"repository_group": {
|
||||
"name": "development",
|
||||
"description": ""
|
||||
},
|
||||
"name": "mozilla-inbound",
|
||||
"dvcs_type": "hg",
|
||||
"url": "https://hg.mozilla.org/integration/mozilla-inbound",
|
||||
"branch": null,
|
||||
"codebase": "gecko",
|
||||
"description": "",
|
||||
"active_status": "active",
|
||||
"performance_alerts_enabled": true,
|
||||
"pushlogURL": "https://hg.mozilla.org/integration/mozilla-inbound/pushloghtml"
|
||||
};
|
||||
const revision = {
|
||||
"result_set_id": 151371,
|
||||
"repository_id": 2,
|
||||
"revision": "5a110ad242ead60e71d2186bae78b1fb766ad5ff",
|
||||
"author": "André Bargull <andre.bargull@gmail.com>",
|
||||
"comments": "Bug 1319926 - Part 2: Collect telemetry about deprecated String generics methods. r=jandem"
|
||||
};
|
||||
// Mock these simple functions so we don't have to call ThRepositoryModel.load() first to use them
|
||||
repo.getRevisionHref = () => `${repo.url}/rev/${revision.revision}`;
|
||||
repo.getPushLogHref = (revision) => `${repo.pushlogURL}?changeset=${revision}`;
|
||||
|
||||
mockData = {
|
||||
revision,
|
||||
repo
|
||||
};
|
||||
}));
|
||||
|
||||
it('renders a linked revision hash', () => {
|
||||
const wrapper = mount(<RevisionItem repo={mockData.repo} revision={mockData.revision}
|
||||
initialsFilter={initialsFilter} linkifyBugsFilter={linkifyBugsFilter}/>);
|
||||
const link = wrapper.find('a');
|
||||
expect(link.length).toEqual(1);
|
||||
expect(link.node.href).toEqual(mockData.repo.getRevisionHref());
|
||||
expect(link.node.title).toEqual(`Open revision ${mockData.revision.revision} on ${mockData.repo.url}`);
|
||||
});
|
||||
|
||||
it(`renders the contributors' initials`, () => {
|
||||
const wrapper = mount(<RevisionItem repo={mockData.repo} revision={mockData.revision}
|
||||
initialsFilter={initialsFilter} linkifyBugsFilter={linkifyBugsFilter}/>);
|
||||
const initials = wrapper.find('span[title="André Bargull: andre.bargull@gmail.com"]');
|
||||
expect(initials.length).toEqual(1);
|
||||
expect(initials.text()).toEqual('AB');
|
||||
});
|
||||
|
||||
it('linkifies bug IDs in the comments', () => {
|
||||
const wrapper = mount(<RevisionItem repo={mockData.repo} revision={mockData.revision}
|
||||
initialsFilter={initialsFilter} linkifyBugsFilter={linkifyBugsFilter}/>);
|
||||
const escapedComment = _.escape(mockData.revision.comments.split('\n')[0]);
|
||||
const linkifiedCommentText = linkifyBugsFilter(escapedComment);
|
||||
|
||||
const comment = wrapper.find('.revision-comment em');
|
||||
expect(comment.node.innerHTML).toEqual(linkifiedCommentText);
|
||||
});
|
||||
|
||||
it('marks the revision as backed out if the words "Back/Backed out" appear in the comments', () => {
|
||||
mockData.revision.comments = "Backed out changeset a6e2d96c1274 (bug 1322565) for eslint failure";
|
||||
let wrapper = mount(<RevisionItem repo={mockData.repo} revision={mockData.revision}
|
||||
initialsFilter={initialsFilter} linkifyBugsFilter={linkifyBugsFilter}/>);
|
||||
expect(wrapper.find({'data-tags': 'backout'}).length).toEqual(1);
|
||||
|
||||
mockData.revision.comments = "Back out changeset a6e2d96c1274 (bug 1322565) for eslint failure";
|
||||
wrapper = mount(<RevisionItem repo={mockData.repo} revision={mockData.revision}
|
||||
initialsFilter={initialsFilter} linkifyBugsFilter={linkifyBugsFilter}/>);
|
||||
expect(wrapper.find({'data-tags': 'backout'}).length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('More revisions link component', () => {
|
||||
it('renders an "...and more" link', () => {
|
||||
const wrapper = mount(<MoreRevisionsLink href='http://more.link/'/>);
|
||||
const link = wrapper.find('a');
|
||||
expect(link.node.href).toEqual('http://more.link/');
|
||||
expect(link.text()).toEqual('\u2026and more');
|
||||
});
|
||||
|
||||
it('has an external link icon', () => {
|
||||
const wrapper = mount(<MoreRevisionsLink href='http://more.link'/>);
|
||||
expect(wrapper.find('i.fa.fa-external-link-square').length).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -44,9 +44,8 @@ TIME_ZONE = "UTC"
|
|||
USE_I18N = False
|
||||
USE_L10N = True
|
||||
|
||||
SERVE_MINIFIED_UI = env.bool("SERVE_MINIFIED_UI", default=False)
|
||||
# Files in this directory will be served by WhiteNoise at the site root.
|
||||
WHITENOISE_ROOT = path("..", "dist" if SERVE_MINIFIED_UI else "ui")
|
||||
WHITENOISE_ROOT = path("..", "dist")
|
||||
|
||||
STATIC_ROOT = path("static")
|
||||
STATIC_URL = "/static/"
|
||||
|
|
|
@ -8,12 +8,12 @@ class CustomWhiteNoise(WhiteNoiseMiddleware):
|
|||
Adds two additional features to WhiteNoise:
|
||||
|
||||
1) Serving index pages for directory paths (such as the site root).
|
||||
2) Setting long max-age headers for files created by grunt-cache-bust.
|
||||
2) Setting long max-age headers for bundled js files
|
||||
"""
|
||||
|
||||
# Matches grunt-cache-bust's style of hash filenames. eg:
|
||||
# index.min-e10ba468ffc8816a.js
|
||||
IMMUTABLE_FILE_RE = re.compile(r'\.min\.[a-f0-9]{16,}\.(js|css)$')
|
||||
# Matches webpack's style of chunk filenames. eg:
|
||||
# index.f03882a6258f16fceb70.bundle.js
|
||||
IMMUTABLE_FILE_RE = re.compile(r'\.[a-f0-9]{16,}\.bundle\.(js|css)$')
|
||||
INDEX_NAME = 'index.html'
|
||||
|
||||
def update_files_dictionary(self, *args):
|
||||
|
@ -41,7 +41,7 @@ class CustomWhiteNoise(WhiteNoiseMiddleware):
|
|||
return super(CustomWhiteNoise, self).find_file(url)
|
||||
|
||||
def is_immutable_file(self, path, url):
|
||||
"""Support grunt-cache-busting style filenames when setting long max-age headers."""
|
||||
"""Support webpack bundle filenames when setting long max-age headers."""
|
||||
if self.IMMUTABLE_FILE_RE.search(url):
|
||||
return True
|
||||
# Otherwise fall back to the default method, so we catch filenames in the
|
||||
|
|
|
@ -3,13 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Treeherder Admin</title>
|
||||
<!-- build:css css/treeherder-admin.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-admin.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-navbar.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-notifications.css" rel="stylesheet" type="text/css">
|
||||
<!-- endbuild -->
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/favicon-wrench.ico">
|
||||
<style>
|
||||
</style>
|
||||
|
@ -55,60 +48,6 @@
|
|||
<section ui-view></section>
|
||||
|
||||
<th-notification-box></th-notification-box>
|
||||
<script src="vendor/react/react.min.js"></script>
|
||||
<script src="vendor/react/react-dom.min.js"></script>
|
||||
|
||||
<!-- build:dontbuild -->
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:js js/admin.min.js -->
|
||||
<script src="vendor/jquery-2.1.3.js"></script>
|
||||
<script src="vendor/lodash.js"></script>
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="vendor/angular-clipboard.js"></script>
|
||||
<script src="vendor/angular/angular-resource.js"></script>
|
||||
<script src="vendor/angular/angular-cookies.js"></script>
|
||||
<script src="vendor/angular/angular-ui-router.js"></script>
|
||||
<script src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/ui-bootstrap-tpls-1.3.3.js"></script>
|
||||
<script src="vendor/bootstrap.js"></script>
|
||||
<script src="vendor/mousetrap.min.js"></script>
|
||||
<script src="vendor/hawk/browser-6.6.0.js"></script>
|
||||
|
||||
<script src="js/treeherder.js"></script>
|
||||
<script src="js/admin.js"></script>
|
||||
<script src="js/services/treestatus.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/values.js"></script>
|
||||
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
<script src="js/services/jsonpushes.js"></script>
|
||||
|
||||
<script src="js/models/repository.js"></script>
|
||||
<script src="js/models/build_platform.js"></script>
|
||||
<script src="js/models/job_type.js"></script>
|
||||
<script src="js/models/option_collection.js"></script>
|
||||
<script src="js/models/exclusion_profile.js"></script>
|
||||
<script src="js/models/job_exclusion.js"></script>
|
||||
<script src="js/models/user.js"></script>
|
||||
<script src="js/models/error.js"></script>
|
||||
|
||||
<script src="js/components/auth.js"></script>
|
||||
<script src="js/directives/treeherder/main.js"></script>
|
||||
<script src="js/reactselect.js"></script>
|
||||
|
||||
<script src="js/controllers/admin/admin.js"></script>
|
||||
<script src="js/controllers/admin/exclusions_list.js"></script>
|
||||
<script src="js/controllers/admin/exclusions_detail.js"></script>
|
||||
<script src="js/controllers/admin/profiles_list.js"></script>
|
||||
<script src="js/controllers/admin/profiles_detail.js"></script>
|
||||
<!--endbuild-->
|
||||
|
||||
<script src="vendor/ngReact/ngReact.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
// Webpack entry point for admin.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
|
||||
require('./js/config');
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('./css/treeherder-admin.css');
|
||||
require('./css/treeherder-navbar.css');
|
||||
require('./css/treeherder-notifications.css');
|
||||
|
||||
// Vendor JS
|
||||
require('angular');
|
||||
require('angular-resource');
|
||||
require('angular-cookies');
|
||||
require('angular-ui-router');
|
||||
require('angular-sanitize');
|
||||
require('angular-local-storage');
|
||||
require('bootstrap/dist/js/bootstrap');
|
||||
require('angular-ui-bootstrap');
|
||||
require('mousetrap');
|
||||
require('react-dom');
|
||||
require('ngreact');
|
||||
require('./vendor/angular-clipboard.js');
|
||||
|
||||
// Admin JS
|
||||
require('./js/services/treestatus.js');
|
||||
require('./js/providers.js');
|
||||
require('./js/values.js');
|
||||
require('./js/services/main.js');
|
||||
require('./js/services/log.js');
|
||||
require('./js/services/jsonpushes.js');
|
||||
require('./js/models/repository.js');
|
||||
require('./js/models/build_platform.js');
|
||||
require('./js/models/job_type.js');
|
||||
require('./js/models/option_collection.js');
|
||||
require('./js/models/exclusion_profile.js');
|
||||
require('./js/models/job_exclusion.js');
|
||||
require('./js/models/user.js');
|
||||
require('./js/models/error.js');
|
||||
require('./js/components/auth.js');
|
||||
require('./js/directives/treeherder/main.js');
|
||||
require('./js/reactselect.js');
|
||||
require('./js/controllers/admin/admin.js');
|
||||
require('./js/controllers/admin/exclusions_list.js');
|
||||
require('./js/controllers/admin/exclusions_detail.js');
|
||||
require('./js/controllers/admin/profiles_list.js');
|
||||
require('./js/controllers/admin/profiles_detail.js');
|
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
// Webpack entry point for failurereviewer.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
|
||||
require("./js/config");
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('./css/treeherder-global.css');
|
||||
require('./css/treeherder-notifications.css');
|
||||
require('./css/failureviewer.css');
|
||||
|
||||
// Vendor JS
|
||||
require('angular');
|
||||
require('angular-route');
|
||||
require('angular-resource');
|
||||
require('angular-cookies');
|
||||
require('angular-sanitize');
|
||||
require('angular-local-storage');
|
||||
require('bootstrap/dist/js/bootstrap');
|
||||
require('angular-ui-bootstrap');
|
||||
require('./vendor/resizer.js');
|
||||
|
||||
// Failureviewer JS
|
||||
require('./js/providers.js');
|
||||
require('./js/directives/treeherder/main.js');
|
||||
require('./js/services/main.js');
|
||||
require('./js/services/log.js');
|
||||
require('./js/models/classified_failure.js');
|
||||
require('./js/models/failure_lines.js');
|
||||
require('./js/filters.js');
|
||||
require('./js/controllers/failureviewer.js');
|
|
@ -0,0 +1,98 @@
|
|||
'use strict';
|
||||
// Webpack entry point for index.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
require('./js/config');
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('./vendor/css/bootstrap-non-responsive.css');
|
||||
require('./css/treeherder-global.css');
|
||||
require('./css/treeherder-navbar.css');
|
||||
require('./css/treeherder-navbar-panels.css');
|
||||
require('./css/treeherder-notifications.css');
|
||||
require('./css/treeherder-info-panel.css');
|
||||
require('./css/treeherder-job-buttons.css');
|
||||
require('./css/treeherder-resultsets.css');
|
||||
require('./css/treeherder-pinboard.css');
|
||||
require('./css/treeherder-bugfiler.css');
|
||||
require('./css/treeherder-loading-overlay.css');
|
||||
|
||||
// Vendor JS
|
||||
require('angular');
|
||||
require('angular-route');
|
||||
require('angular-resource');
|
||||
require('angular-cookies');
|
||||
require('angular-sanitize');
|
||||
require('angular-toarrayfilter');
|
||||
require('angular-local-storage');
|
||||
require('bootstrap/dist/js/bootstrap');
|
||||
require('angular-ui-bootstrap');
|
||||
require('mousetrap');
|
||||
require('js-yaml');
|
||||
require('react-dom');
|
||||
require('ngreact');
|
||||
require('jquery.scrollto');
|
||||
require('json-schema-defaults');
|
||||
require('./vendor/resizer.js');
|
||||
|
||||
// Treeherder JS
|
||||
require('./js/services/log.js');
|
||||
require('./js/providers.js');
|
||||
require('./js/values.js');
|
||||
require('./js/components/auth.js');
|
||||
require('./js/directives/treeherder/main.js');
|
||||
require('./js/directives/treeherder/clonejobs.js');
|
||||
require('./js/directives/treeherder/resultsets.js');
|
||||
require('./js/directives/treeherder/top_nav_bar.js');
|
||||
require('./js/directives/treeherder/bottom_nav_panel.js');
|
||||
require('./js/reactrevisions.jsx');
|
||||
require('./js/services/main.js');
|
||||
require('./js/services/buildapi.js');
|
||||
require('./js/services/taskcluster.js');
|
||||
require('./js/services/classifications.js');
|
||||
require('./js/services/jobfilters.js');
|
||||
require('./js/services/pinboard.js');
|
||||
require('./js/services/treestatus.js');
|
||||
require('./js/services/tcactions.js');
|
||||
require('./js/models/resultset.js');
|
||||
require('./js/models/resultsets_store.js');
|
||||
require('./js/models/job_detail.js');
|
||||
require('./js/models/repository.js');
|
||||
require('./js/models/bug_job_map.js');
|
||||
require('./js/models/classification.js');
|
||||
require('./js/models/job.js');
|
||||
require('./js/models/runnable_job.js');
|
||||
require('./js/models/job_exclusion.js');
|
||||
require('./js/models/exclusion_profile.js');
|
||||
require('./js/models/build_platform.js');
|
||||
require('./js/models/job_type.js');
|
||||
require('./js/models/job_group.js');
|
||||
require('./js/models/job_log_url.js');
|
||||
require('./js/models/option_collection.js');
|
||||
require('./js/models/user.js');
|
||||
require('./js/models/error.js');
|
||||
require('./js/models/matcher.js');
|
||||
require('./js/models/failure_lines.js');
|
||||
require('./js/models/text_log_errors.js');
|
||||
require('./js/models/classified_failure.js');
|
||||
require('./js/models/bug_suggestions.js');
|
||||
require('./js/models/text_log_step.js');
|
||||
require('./js/models/text_log_summary.js');
|
||||
require('./js/models/text_log_summary_line.js');
|
||||
require('./js/models/perf/series.js');
|
||||
require('./js/controllers/main.js');
|
||||
require('./js/controllers/settings.js');
|
||||
require('./js/controllers/repository.js');
|
||||
require('./js/controllers/filters.js');
|
||||
require('./js/controllers/jobs.js');
|
||||
require('./js/controllers/bugfiler.js');
|
||||
require('./js/controllers/tcjobactions.js');
|
||||
require('./plugins/tabs.js');
|
||||
require('./plugins/controller.js');
|
||||
require('./plugins/pinboard.js');
|
||||
require('./plugins/annotations/controller.js');
|
||||
require('./plugins/failure_summary/controller.js');
|
||||
require('./plugins/similar_jobs/controller.js');
|
||||
require('./plugins/auto_classification/controller.js');
|
||||
require('./js/filters.js');
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
// Webpack entry point for logviewer.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
require('./js/config');
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('./css/treeherder-global.css');
|
||||
require('./css/logviewer.css');
|
||||
|
||||
// Vendor JS
|
||||
require('angular');
|
||||
require('angular-route');
|
||||
require('angular-resource');
|
||||
require('angular-cookies');
|
||||
require('angular-sanitize');
|
||||
require('angular-local-storage');
|
||||
require('bootstrap/dist/js/bootstrap');
|
||||
require('./vendor/resizer.js');
|
||||
|
||||
// Logviewer JS
|
||||
require('./js/providers.js');
|
||||
require('./js/values.js');
|
||||
require('./js/directives/treeherder/log_viewer_steps.js');
|
||||
require('./js/directives/treeherder/main.js');
|
||||
require('./js/components/logviewer/logviewer.js');
|
||||
require('./js/services/main.js');
|
||||
require('./js/services/log.js');
|
||||
require('./js/services/taskcluster.js');
|
||||
require('./js/models/job_detail.js');
|
||||
require('./js/models/job.js');
|
||||
require('./js/models/runnable_job.js');
|
||||
require('./js/models/resultset.js');
|
||||
require('./js/models/text_log_step.js');
|
||||
require('./js/filters.js');
|
||||
require('./js/controllers/logviewer.js');
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
// Webpack entry point for perf.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
|
||||
require('./js/config');
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('./css/treeherder-navbar.css');
|
||||
require('./css/perf.css');
|
||||
require('./css/treeherder-loading-overlay.css');
|
||||
|
||||
// Vendor JS
|
||||
require('angular');
|
||||
require('angular-resource');
|
||||
require('angular-cookies');
|
||||
require('angular-ui-router');
|
||||
require('angular-sanitize');
|
||||
require('angular-local-storage');
|
||||
require('mousetrap');
|
||||
require('bootstrap/dist/js/bootstrap');
|
||||
require('angular-ui-bootstrap');
|
||||
require('./vendor/angular-clipboard.js');
|
||||
// The jquery flot package does not seem to be updated on npm, so we use a local version:
|
||||
require('./vendor/jquery.flot.js');
|
||||
require('./vendor/jquery.flot.time.js');
|
||||
require('./vendor/jquery.flot.selection.js');
|
||||
|
||||
// Perf JS
|
||||
require('./js/services/treestatus.js');
|
||||
require('./js/providers.js');
|
||||
require('./js/values.js');
|
||||
require('./js/filters.js');
|
||||
require('./js/models/option_collection.js');
|
||||
require('./js/services/main.js');
|
||||
require('./js/services/log.js');
|
||||
require('./js/services/taskcluster.js');
|
||||
require('./js/services/jsonpushes.js');
|
||||
require('./js/models/repository.js');
|
||||
require('./js/models/job.js');
|
||||
require('./js/models/runnable_job.js');
|
||||
require('./js/models/resultset.js');
|
||||
require('./js/models/user.js');
|
||||
require('./js/models/error.js');
|
||||
require('./js/perf.js');
|
||||
require('./js/models/perf/series.js');
|
||||
require('./js/models/perf/performance_framework.js');
|
||||
require('./js/models/perf/alerts.js');
|
||||
require('./js/services/perf/math.js');
|
||||
require('./js/services/perf/compare.js');
|
||||
require('./js/controllers/perf/compare.js');
|
||||
require('./js/controllers/perf/graphs.js');
|
||||
require('./js/controllers/perf/alerts.js');
|
||||
require('./js/controllers/perf/dashboard.js');
|
||||
require('./js/controllers/perf/e10s-trend.js');
|
||||
require('./js/components/perf/compare.js');
|
||||
require('./js/components/auth.js');
|
||||
require('./js/components/loading.js');
|
||||
require('./js/perfapp.js');
|
||||
require('./js/filters.js');
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
// Webpack entry point for userguide.html
|
||||
// Scripts and styles included here are automatically included on the page at build time
|
||||
|
||||
// Styles
|
||||
require('bootstrap/dist/css/bootstrap.css');
|
||||
require('./css/treeherder-global.css');
|
||||
require('./css/treeherder-userguide.css');
|
||||
require('./css/treeherder-job-buttons.css');
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
|
||||
// Userguide JS
|
||||
require('./js/userguide.js');
|
||||
require('./js/controllers/userguide.js');
|
|
@ -3,13 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Failure Viewer</title>
|
||||
<!-- build:css css/failureviewer.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-global.css" rel="stylesheet" type="text/css">
|
||||
<link href="css/treeherder-notifications.css" rel="stylesheet" type="text/css">
|
||||
<link href="css/failureviewer.css" rel="stylesheet" type="text/css">
|
||||
<!-- endbuild -->
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/tree_open.png">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -76,43 +69,5 @@
|
|||
|
||||
<th-notification-box></th-notification-box>
|
||||
|
||||
<!-- build:js js/failureviewer.min.js -->
|
||||
<script src="vendor/jquery-2.1.3.js"></script>
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="vendor/angular/angular-route.js"></script>
|
||||
<script src="vendor/angular/angular-resource.js"></script>
|
||||
<script src="vendor/angular/angular-cookies.js"></script>
|
||||
<script src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/ui-bootstrap-tpls-0.13.0.js"></script>
|
||||
<script src="vendor/bootstrap.js"></script>
|
||||
<script src="vendor/lodash.js"></script>
|
||||
<script src="vendor/resizer.js"></script>
|
||||
|
||||
<script src="js/treeherder.js"></script>
|
||||
<script src="js/failureviewer.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
|
||||
<!-- Directives -->
|
||||
<script src="js/directives/treeherder/main.js"></script>
|
||||
|
||||
<!-- Main services -->
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
|
||||
<!-- Model services -->
|
||||
<script src="js/models/classified_failure.js"></script>
|
||||
<script src="js/models/failure_lines.js"></script>
|
||||
|
||||
<!-- Filter -->
|
||||
<script src="js/filters.js"></script>
|
||||
|
||||
<!-- Controllers -->
|
||||
<script src="js/controllers/failureviewer.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:dontbuild -->
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
115
ui/index.html
115
ui/index.html
|
@ -6,23 +6,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/tree_open.png">
|
||||
<th-favicon-link></th-favicon-link>
|
||||
|
||||
<!-- build:css css/index.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/bootstrap-non-responsive.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-global.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-navbar.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-navbar-panels.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-notifications.css" rel="stylesheet" type="text/css">
|
||||
<link href="css/treeherder-info-panel.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-job-buttons.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-resultsets.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-pinboard.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-bugfiler.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-loading-overlay.css" rel="stylesheet" media="screen">
|
||||
<!-- endbuild -->
|
||||
|
||||
</head>
|
||||
<body ng-controller="MainCtrl">
|
||||
<div id="global-container">
|
||||
|
@ -56,104 +39,6 @@
|
|||
</div>
|
||||
|
||||
<th-notification-box></th-notification-box>
|
||||
<script src="vendor/react/react.min.js"></script>
|
||||
<script src="vendor/react/react-dom.min.js"></script>
|
||||
|
||||
<!-- build:js js/index.min.js -->
|
||||
<script src="vendor/jquery-2.1.3.js"></script>
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="vendor/angular/angular-route.js"></script>
|
||||
<script src="vendor/angular/angular-resource.js"></script>
|
||||
<script src="vendor/angular/angular-cookies.js"></script>
|
||||
<script src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script src="vendor/toArrayFilter.js"></script>
|
||||
<script src="vendor/ui-bootstrap-tpls-1.3.3.js"></script>
|
||||
<script src="vendor/bootstrap.js"></script>
|
||||
<script src="vendor/taskcluster-1.6.0.js"></script>
|
||||
<script src="vendor/js-yaml.min.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/mousetrap.min.js"></script>
|
||||
<script src="vendor/jquery.scrollTo.js"></script>
|
||||
<script src="vendor/lodash.js"></script>
|
||||
<script src="vendor/resizer.js"></script>
|
||||
<script src="vendor/hawk/browser-6.6.0.js"></script>
|
||||
|
||||
<script src="js/treeherder.js"></script>
|
||||
<script src="js/treeherder_app.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/values.js"></script>
|
||||
<!-- Components-->
|
||||
<script src="js/components/auth.js"></script>
|
||||
<!-- Directives -->
|
||||
<script src="js/directives/treeherder/main.js"></script>
|
||||
<script src="js/directives/treeherder/clonejobs.js"></script>
|
||||
<script src="js/directives/treeherder/resultsets.js"></script>
|
||||
<script src="js/directives/treeherder/top_nav_bar.js"></script>
|
||||
<script src="js/directives/treeherder/bottom_nav_panel.js"></script>
|
||||
<script src="js/reactrevisions.js"></script>
|
||||
<!-- Main services -->
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/buildapi.js"></script>
|
||||
<script src="js/services/taskcluster.js"></script>
|
||||
<script src="js/services/classifications.js"></script>
|
||||
<script src="js/services/jobfilters.js"></script>
|
||||
<script src="js/services/pinboard.js"></script>
|
||||
<script src="js/services/treestatus.js"></script>
|
||||
<script src="js/services/tcactions.js"></script>
|
||||
<!-- Model services -->
|
||||
<script src="js/models/resultset.js"></script>
|
||||
<script src="js/models/resultsets_store.js"></script>
|
||||
<script src="js/models/job_detail.js"></script>
|
||||
<script src="js/models/repository.js"></script>
|
||||
<script src="js/models/bug_job_map.js"></script>
|
||||
<script src="js/models/classification.js"></script>
|
||||
<script src="js/models/job.js"></script>
|
||||
<script src="js/models/runnable_job.js"></script>
|
||||
<script src="js/models/job_exclusion.js"></script>
|
||||
<script src="js/models/exclusion_profile.js"></script>
|
||||
<script src="js/models/build_platform.js"></script>
|
||||
<script src="js/models/job_type.js"></script>
|
||||
<script src="js/models/job_group.js"></script>
|
||||
<script src="js/models/job_log_url.js"></script>
|
||||
<script src="js/models/option_collection.js"></script>
|
||||
<script src="js/models/user.js"></script>
|
||||
<script src="js/models/error.js"></script>
|
||||
<script src="js/models/matcher.js"></script>
|
||||
<script src="js/models/failure_lines.js"></script>
|
||||
<script src="js/models/text_log_errors.js"></script>
|
||||
<script src="js/models/classified_failure.js"></script>
|
||||
<script src="js/models/bug_suggestions.js"></script>
|
||||
<script src="js/models/text_log_step.js"></script>
|
||||
<script src="js/models/text_log_summary.js"></script>
|
||||
<script src="js/models/text_log_summary_line.js"></script>
|
||||
<script src="js/models/perf/series.js"></script>
|
||||
<!-- Controllers -->
|
||||
<script src="js/controllers/main.js"></script>
|
||||
<script src="js/controllers/settings.js"></script>
|
||||
<script src="js/controllers/repository.js"></script>
|
||||
<script src="js/controllers/filters.js"></script>
|
||||
<script src="js/controllers/jobs.js"></script>
|
||||
<script src="js/controllers/bugfiler.js"></script>
|
||||
<script src="js/controllers/tcjobactions.js"></script>
|
||||
<!-- Plugins -->
|
||||
<script src="plugins/tabs.js"></script>
|
||||
<script src="plugins/controller.js"></script>
|
||||
<script src="plugins/pinboard.js"></script>
|
||||
<script src="plugins/annotations/controller.js"></script>
|
||||
<script src="plugins/failure_summary/controller.js"></script>
|
||||
<script src="plugins/similar_jobs/controller.js"></script>
|
||||
<script src="plugins/auto_classification/controller.js"></script>
|
||||
|
||||
<script src="js/filters.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<script src="vendor/defaults.js"></script>
|
||||
<script src="vendor/ngReact/ngReact.min.js"></script>
|
||||
|
||||
<!-- build:dontbuild -->
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- Clone targets -->
|
||||
|
||||
|
|
|
@ -38,3 +38,5 @@ admin.config(['$compileProvider', '$httpProvider', '$stateProvider', '$urlRouter
|
|||
|
||||
$urlRouterProvider.otherwise('/profiles');
|
||||
}]);
|
||||
|
||||
module.exports = admin;
|
||||
|
|
|
@ -147,6 +147,7 @@ treeherder.component("loginCallback", {
|
|||
`,
|
||||
controller: ['localStorageService', '$location', '$window', '$http', '$scope',
|
||||
function(localStorageService, $location, $window, $http, $scope) {
|
||||
const hawk = require('hawk');
|
||||
const host = $location.host();
|
||||
const port = $location.port();
|
||||
const loginUrl = `${$location.protocol()}://${host}:${port}/api/auth/login/`;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/* global SERVICE_DOMAIN */
|
||||
'use strict';
|
||||
|
||||
// The treeherder API endpoint is set via window.thServiceDomain.
|
||||
// The SERVICE_DOMAIN global is set by webpack's DefinePlugin based on the SERVICE_DOMAIN environment variable.
|
||||
// Use it to set window.thServiceDomain if possible:
|
||||
if (!_.isEmpty(SERVICE_DOMAIN)) {
|
||||
window.thServiceDomain = SERVICE_DOMAIN;
|
||||
}
|
||||
|
||||
// Check for a config file and use it if available
|
||||
try {
|
||||
// (Requiring via context allows us to avoid ugly warnings when no file is present)
|
||||
const req = require.context('./', false, /^.*\.js/);
|
||||
if (req.keys().indexOf('./local.conf.js') > -1) {
|
||||
req('./local.conf.js');
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
|
@ -6,3 +6,5 @@ failureViewerApp.config(['$compileProvider', function($compileProvider) {
|
|||
// Disable debug data, as recommended by https://docs.angularjs.org/guide/production
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}]);
|
||||
|
||||
module.exports = failureViewerApp;
|
||||
|
|
|
@ -13,3 +13,5 @@ logViewerApp.config(['$compileProvider', '$resourceProvider',
|
|||
// All queries should be cancellable by default (why is this configurable??)
|
||||
$resourceProvider.defaults.cancellable = true;
|
||||
}]);
|
||||
|
||||
module.exports = logViewerApp;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
/*exported perf*/
|
||||
var perf = angular.module("perf", ['ui.router', 'ui.bootstrap', 'treeherder', 'angular-clipboard']);
|
||||
module.exports = angular.module("perf", ['ui.router', 'ui.bootstrap', 'treeherder', 'angular-clipboard']);
|
||||
|
|
|
@ -68,3 +68,5 @@ perf.config(['$compileProvider', '$httpProvider', '$stateProvider', '$urlRouterP
|
|||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
module.exports = perf;
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var moreRevisionsLinkComponent = React.createClass({
|
||||
displayName: 'pushlogRevisionComponent',
|
||||
propTypes: {
|
||||
href: React.PropTypes.string.isRequired
|
||||
},
|
||||
render() {
|
||||
return React.DOM.li(
|
||||
null,
|
||||
React.DOM.a({
|
||||
href: this.props.href,
|
||||
'data-ignore-job-clear-on-click': true,
|
||||
target: '_blank'
|
||||
},
|
||||
'\u2026and more',
|
||||
React.DOM.i({ className: 'fa fa-external-link-square' })
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
var moreRevisionsLinkComponentFactory = React.createFactory(moreRevisionsLinkComponent);
|
||||
|
||||
var revisionItemComponent = React.createClass({
|
||||
displayName: 'revisionItemComponent',
|
||||
propTypes: {
|
||||
revision: React.PropTypes.object.isRequired,
|
||||
repo: React.PropTypes.object.isRequired,
|
||||
linkifyBugsFilter: React.PropTypes.func.isRequired,
|
||||
initialsFilter: React.PropTypes.func.isRequired
|
||||
},
|
||||
render() {
|
||||
var email, name, userTokens, escapedComment, escapedCommentHTML, initialsHTML, tags;
|
||||
|
||||
userTokens = this.props.revision.author.split(/[<>]+/);
|
||||
name = userTokens[0];
|
||||
if (userTokens.length > 1) email = userTokens[1];
|
||||
initialsHTML = { __html: this.props.initialsFilter(name) };
|
||||
|
||||
escapedComment = _.escape(this.props.revision.comments.split('\n')[0]);
|
||||
escapedCommentHTML = { __html: this.props.linkifyBugsFilter(escapedComment) };
|
||||
|
||||
tags = "";
|
||||
if (escapedComment.search("Backed out") >= 0 ||
|
||||
escapedComment.search("Back out") >= 0) {
|
||||
tags += "backout ";
|
||||
}
|
||||
tags = tags.trim();
|
||||
|
||||
return React.DOM.li(
|
||||
{ className: 'clearfix' },
|
||||
React.DOM.span({
|
||||
className: 'revision',
|
||||
'data-tags': tags
|
||||
},
|
||||
React.DOM.span(
|
||||
{ className: 'revision-holder' },
|
||||
React.DOM.a({
|
||||
title: `Open revision ${this.props.revision.revision} on ${this.props.repo.url}`,
|
||||
href: this.props.repo.getRevisionHref(this.props.revision.revision),
|
||||
'data-ignore-job-clear-on-click': true
|
||||
},
|
||||
this.props.revision.revision.substring(0, 12)
|
||||
)),
|
||||
React.DOM.span({
|
||||
title: `${name}: ${email}`,
|
||||
dangerouslySetInnerHTML: initialsHTML
|
||||
}),
|
||||
React.DOM.span(
|
||||
{ title: _.unescape(escapedComment) },
|
||||
React.DOM.span(
|
||||
{ className: 'revision-comment' },
|
||||
React.DOM.em({ dangerouslySetInnerHTML: escapedCommentHTML })
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
var revisionItemComponentFactory = React.createFactory(revisionItemComponent);
|
||||
|
||||
var revisionListComponent = React.createClass({
|
||||
displayName: 'revisionListComponent',
|
||||
propTypes: {
|
||||
resultset: React.PropTypes.object.isRequired,
|
||||
repo: React.PropTypes.object.isRequired,
|
||||
$injector: React.PropTypes.object.isRequired
|
||||
},
|
||||
render() {
|
||||
var repo = this.props.repo;
|
||||
// Possible "...and more" link
|
||||
var moreLink = null;
|
||||
|
||||
if (this.props.resultset.revision_count > this.props.resultset.revisions.length) {
|
||||
moreLink = moreRevisionsLinkComponentFactory({
|
||||
href: this.props.repo.getPushLogHref(this.props.resultset.revision)
|
||||
});
|
||||
}
|
||||
var $injector = this.props.$injector;
|
||||
|
||||
return React.DOM.span(
|
||||
{ className: 'revision-list col-xs-5' },
|
||||
React.DOM.ul(
|
||||
{ className: 'list-unstyled' },
|
||||
this.props.resultset.revisions.map(function(item, i) {
|
||||
return revisionItemComponentFactory({
|
||||
initialsFilter: $injector.get('$filter')('initials'),
|
||||
linkifyBugsFilter: $injector.get('$filter')('linkifyBugs'),
|
||||
revision: item,
|
||||
repo: repo,
|
||||
key: i
|
||||
});
|
||||
}),
|
||||
moreLink
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive('revisions', ['reactDirective', '$injector', function (reactDirective, $injector) {
|
||||
return reactDirective(revisionListComponent, undefined, {}, {$injector});
|
||||
}]);
|
|
@ -0,0 +1,97 @@
|
|||
'use strict';
|
||||
|
||||
const MoreRevisionsLink = (props) => (
|
||||
<li>
|
||||
<a href={props.href}
|
||||
data-ignore-job-clear-on-click={true}
|
||||
target='_blank'>
|
||||
{`\u2026and more`}
|
||||
<i className='fa fa-external-link-square' />
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
MoreRevisionsLink.propTypes = {
|
||||
href: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const RevisionItem = (props) => {
|
||||
let email, name, userTokens, escapedComment, escapedCommentHTML, initialsHTML, tags;
|
||||
|
||||
userTokens = props.revision.author.split(/[<>]+/);
|
||||
name = userTokens[0].trim();
|
||||
if (userTokens.length > 1) email = userTokens[1];
|
||||
initialsHTML = { __html: props.initialsFilter(name) };
|
||||
|
||||
escapedComment = _.escape(props.revision.comments.split('\n')[0]);
|
||||
escapedCommentHTML = { __html: props.linkifyBugsFilter(escapedComment) };
|
||||
|
||||
tags = '';
|
||||
if (escapedComment.search("Backed out") >= 0 ||
|
||||
escapedComment.search("Back out") >= 0) {
|
||||
tags += "backout";
|
||||
}
|
||||
|
||||
return <li className="clearfix">
|
||||
<span className="revision" data-tags={tags}>
|
||||
<span className="revision-holder">
|
||||
<a title={`Open revision ${props.revision.revision} on ${props.repo.url}`}
|
||||
href={props.repo.getRevisionHref(props.revision.revision)}
|
||||
data-ignore-job-clear-on-click>
|
||||
{props.revision.revision.substring(0, 12)}
|
||||
</a>
|
||||
</span>
|
||||
<span title={`${name}: ${email}`}
|
||||
dangerouslySetInnerHTML={initialsHTML} />
|
||||
<span title={escapedComment}>
|
||||
<span className='revision-comment'>
|
||||
<em dangerouslySetInnerHTML={escapedCommentHTML} />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</li>;
|
||||
};
|
||||
RevisionItem.propTypes = {
|
||||
revision: React.PropTypes.object.isRequired,
|
||||
repo: React.PropTypes.object.isRequired,
|
||||
linkifyBugsFilter: React.PropTypes.func.isRequired,
|
||||
initialsFilter: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const RevisionList = (props) => {
|
||||
const initialsFilter = props.$injector.get('$filter')('initials');
|
||||
const linkifyBugsFilter = props.$injector.get('$filter')('linkifyBugs');
|
||||
const hasMore = props.resultset.revision_count > props.resultset.revisions.length;
|
||||
return (
|
||||
<span className='revision-list col-xs-5'>
|
||||
<ul className='list-unstyled'>
|
||||
{props.resultset.revisions.map((revision, i) =>
|
||||
<RevisionItem
|
||||
initialsFilter={initialsFilter}
|
||||
linkifyBugsFilter={linkifyBugsFilter}
|
||||
revision={revision}
|
||||
repo={props.repo}
|
||||
key={i} />
|
||||
)}
|
||||
{hasMore &&
|
||||
<MoreRevisionsLink
|
||||
key='more'
|
||||
href={props.repo.getPushLogHref(props.resultset.revision)} />
|
||||
}
|
||||
</ul>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
RevisionList.propTypes = {
|
||||
resultset: React.PropTypes.object.isRequired,
|
||||
repo: React.PropTypes.object.isRequired,
|
||||
$injector: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
RevisionList,
|
||||
RevisionItem,
|
||||
MoreRevisionsLink
|
||||
};
|
||||
|
||||
treeherder.directive('revisions', ['reactDirective', '$injector', (reactDirective, $injector) =>
|
||||
reactDirective(RevisionList, undefined, {}, {$injector})]);
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('thTaskcluster', ['$window', '$rootScope', 'localStorageService',
|
||||
function($window, $rootScope, localStorageService) {
|
||||
let client = $window.taskcluster;
|
||||
treeherder.factory('thTaskcluster', ['$rootScope', 'localStorageService',
|
||||
function($rootScope, localStorageService) {
|
||||
let client = require('taskcluster-client');
|
||||
$rootScope.$on("LocalStorageModule.notification.setitem", function() {
|
||||
client.config({
|
||||
credentials: localStorageService.get('taskcluster.credentials') || {},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
/*exported treeherder*/
|
||||
var treeherder = angular.module('treeherder',
|
||||
module.exports = angular.module('treeherder',
|
||||
['ngResource', 'ngSanitize', 'ngCookies', 'LocalStorageModule']);
|
||||
|
|
|
@ -45,3 +45,5 @@ treeherderApp.config(['$compileProvider', '$routeProvider', '$httpProvider',
|
|||
}).
|
||||
otherwise({redirectTo: '/jobs'});
|
||||
}]);
|
||||
|
||||
module.exports = treeherderApp;
|
||||
|
|
|
@ -6,3 +6,5 @@ userguideApp.config(['$compileProvider', function ($compileProvider) {
|
|||
// Disable debug data, as recommended by https://docs.angularjs.org/guide/production
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}]);
|
||||
|
||||
module.exports = userguideApp;
|
||||
|
|
|
@ -3,12 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title ng-bind="::logViewerTitle">Log viewer</title>
|
||||
<!-- build:css css/logviewer.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-global.css" rel="stylesheet" type="text/css">
|
||||
<link href="css/logviewer.css" rel="stylesheet" type="text/css">
|
||||
<!-- endbuild -->
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/logviewerIcon.png">
|
||||
</head>
|
||||
<body class="body-logviewer">
|
||||
|
@ -119,52 +113,5 @@
|
|||
|
||||
<th-notification-box></th-notification-box>
|
||||
|
||||
<!-- build:js js/logviewer.min.js -->
|
||||
<script src="vendor/jquery-2.1.3.js"></script>
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="vendor/angular/angular-route.js"></script>
|
||||
<script src="vendor/angular/angular-resource.js"></script>
|
||||
<script src="vendor/angular/angular-cookies.js"></script>
|
||||
<script src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/bootstrap.js"></script>
|
||||
<script src="vendor/lodash.js"></script>
|
||||
<script src="vendor/resizer.js"></script>
|
||||
<script src="vendor/taskcluster-1.6.0.js"></script>
|
||||
|
||||
<script src="js/treeherder.js"></script>
|
||||
<script src="js/logviewer.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/values.js"></script>
|
||||
|
||||
<!-- Directives -->
|
||||
<script src="js/directives/treeherder/log_viewer_steps.js"></script>
|
||||
<script src="js/directives/treeherder/main.js"></script>
|
||||
|
||||
<!-- Components -->
|
||||
<script src="js/components/logviewer/logviewer.js"></script>
|
||||
|
||||
<!-- Main services -->
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
<script src="js/services/taskcluster.js"></script>
|
||||
|
||||
<!-- Model services -->
|
||||
<script src="js/models/job_detail.js"></script>
|
||||
<script src="js/models/job.js"></script>
|
||||
<script src="js/models/runnable_job.js"></script>
|
||||
<script src="js/models/resultset.js"></script>
|
||||
<script src="js/models/text_log_step.js"></script>
|
||||
|
||||
<!-- Filter -->
|
||||
<script src="js/filters.js"></script>
|
||||
|
||||
<!-- Controllers -->
|
||||
<script src="js/controllers/logviewer.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:dontbuild -->
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
65
ui/perf.html
65
ui/perf.html
|
@ -3,13 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Perfherder</title>
|
||||
<!-- build:css css/perf.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-navbar.css" rel="stylesheet" media="screen">
|
||||
<link href="css/perf.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-loading-overlay.css" rel="stylesheet" media="screen">
|
||||
<!-- endbuild -->
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/line_chart.png">
|
||||
<style>
|
||||
</style>
|
||||
|
@ -65,64 +58,8 @@
|
|||
</div>
|
||||
</nav>
|
||||
<div style="padding-top: 20px;"></div>
|
||||
<section ui-view>
|
||||
</section>
|
||||
|
||||
<!-- build:dontbuild -->
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:js js/perf.min.js -->
|
||||
<script src="vendor/jquery-2.1.3.js"></script>
|
||||
<script src="vendor/lodash.js"></script>
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="vendor/angular-clipboard.js"></script>
|
||||
<script src="vendor/angular/angular-resource.js"></script>
|
||||
<script src="vendor/angular/angular-cookies.js"></script>
|
||||
<script src="vendor/angular/angular-ui-router.js"></script>
|
||||
<script src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/ui-bootstrap-tpls-1.3.3.js"></script>
|
||||
<script src="vendor/bootstrap.js"></script>
|
||||
<script src="vendor/jquery.flot.js"></script>
|
||||
<script src="vendor/jquery.flot.time.js"></script>
|
||||
<script src="vendor/jquery.flot.selection.js"></script>
|
||||
<script src="vendor/mousetrap.min.js"></script>
|
||||
<script src="vendor/hawk/browser-6.6.0.js"></script>
|
||||
<script src="vendor/taskcluster-1.6.0.js"></script>
|
||||
<script src="js/treeherder.js"></script>
|
||||
<script src="js/services/taskcluster.js"></script>
|
||||
<script src="js/services/treestatus.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/values.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
<script src="js/models/option_collection.js"></script>
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
<script src="js/services/jsonpushes.js"></script>
|
||||
<script src="js/models/repository.js"></script>
|
||||
<script src="js/models/job.js"></script>
|
||||
<script src="js/models/runnable_job.js"></script>
|
||||
<script src="js/models/resultset.js"></script>
|
||||
<script src="js/models/user.js"></script>
|
||||
<script src="js/models/error.js"></script>
|
||||
<script src="js/perf.js"></script>
|
||||
<script src="js/models/perf/series.js"></script>
|
||||
<script src="js/models/perf/performance_framework.js"></script>
|
||||
<script src="js/models/perf/alerts.js"></script>
|
||||
<script src="js/services/perf/math.js"></script>
|
||||
<script src="js/services/perf/compare.js"></script>
|
||||
<script src="js/controllers/perf/compare.js"></script>
|
||||
<script src="js/controllers/perf/graphs.js"></script>
|
||||
<script src="js/controllers/perf/alerts.js"></script>
|
||||
<script src="js/controllers/perf/dashboard.js"></script>
|
||||
<script src="js/controllers/perf/e10s-trend.js"></script>
|
||||
<script src="js/components/perf/compare.js"></script>
|
||||
<script src="js/components/auth.js"></script>
|
||||
<script src="js/components/loading.js"></script>
|
||||
<script src="js/perfapp.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
<!-- endbuild -->
|
||||
<section ui-view></section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -3,13 +3,6 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Treeherder User Guide</title>
|
||||
<!-- build:css css/userguide.min.css -->
|
||||
<link href="vendor/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-global.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-userguide.css" rel="stylesheet" media="screen">
|
||||
<link href="css/treeherder-job-buttons.css" rel="stylesheet" media="screen">
|
||||
<link href="vendor/css/font-awesome.css" rel="stylesheet" media="screen">
|
||||
<!-- endbuild -->
|
||||
<link id="favicon" type="image/png" rel="shortcut icon" href="img/tree_open.png">
|
||||
</head>
|
||||
|
||||
|
@ -610,10 +603,5 @@
|
|||
<!-- End of content panel -->
|
||||
</div>
|
||||
|
||||
<!-- build:js js/userguide.min.js -->
|
||||
<script src="vendor/angular/angular.js"></script>
|
||||
<script src="js/userguide.js"></script>
|
||||
<script src="js/controllers/userguide.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
291
web-server.js
291
web-server.js
|
@ -1,291 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var util = require('util'),
|
||||
http = require('http'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
url = require('url'),
|
||||
events = require('events'),
|
||||
minimist = require('minimist');
|
||||
|
||||
var DEFAULT_PORT = 8000;
|
||||
var APP_ROOT = path.join(__dirname, "ui");
|
||||
var INDEX_FILE = 'index.html';
|
||||
var DEFAULT_SERVICE_DOMAIN;
|
||||
|
||||
function main(argv) {
|
||||
setServiceDomain(argv)
|
||||
new HttpServer({
|
||||
'GET': createServlet(StaticServlet),
|
||||
'HEAD': createServlet(StaticServlet)
|
||||
}).start(Number(argv[2]) || DEFAULT_PORT);
|
||||
}
|
||||
|
||||
function setServiceDomain(argv) {
|
||||
var options = minimist(argv.slice(2));
|
||||
if ("stage" in options)
|
||||
DEFAULT_SERVICE_DOMAIN = 'https://treeherder.allizom.org';
|
||||
else if ("server" in options)
|
||||
DEFAULT_SERVICE_DOMAIN = options['server'];
|
||||
else
|
||||
DEFAULT_SERVICE_DOMAIN = 'https://treeherder.mozilla.org';
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return value.toString().
|
||||
replace('<', '<').
|
||||
replace('>', '>').
|
||||
replace('"', '"');
|
||||
}
|
||||
|
||||
function createServlet(Class) {
|
||||
var servlet = new Class();
|
||||
return servlet.handleRequest.bind(servlet);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Http server implementation that uses a map of methods to decide
|
||||
* action routing.
|
||||
*
|
||||
* @param {Object} Map of method => Handler function
|
||||
*/
|
||||
function HttpServer(handlers) {
|
||||
this.handlers = handlers;
|
||||
this.server = http.createServer(this.handleRequest_.bind(this));
|
||||
}
|
||||
|
||||
HttpServer.prototype.start = function(port) {
|
||||
this.port = port;
|
||||
console.log("Starting web server at http://localhost:" + port + "/");
|
||||
this.server.listen(port).on('error', function(err) {
|
||||
if (err.code === "EADDRINUSE") {
|
||||
console.error("\033[31mPort %d is already in use, can't start web server.\033[0m", port);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
HttpServer.prototype.parseUrl_ = function(urlString) {
|
||||
var parsed = url.parse(urlString);
|
||||
parsed.pathname = url.resolve('/', parsed.pathname);
|
||||
return url.parse(url.format(parsed), true);
|
||||
};
|
||||
|
||||
HttpServer.prototype.handleRequest_ = function(req, res) {
|
||||
var logEntry = req.method + ' ' + req.url;
|
||||
if (req.headers['user-agent']) {
|
||||
logEntry += ' ' + req.headers['user-agent'];
|
||||
}
|
||||
util.puts(logEntry);
|
||||
req.url = this.parseUrl_(req.url);
|
||||
var handler = this.handlers[req.method];
|
||||
if (!handler) {
|
||||
res.writeHead(501);
|
||||
res.end();
|
||||
} else {
|
||||
handler.call(this, req, res);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles static content.
|
||||
*/
|
||||
function StaticServlet() {}
|
||||
|
||||
StaticServlet.MimeMap = {
|
||||
'txt': 'text/plain',
|
||||
'html': 'text/html',
|
||||
'css': 'text/css',
|
||||
'xml': 'application/xml',
|
||||
'json': 'application/json',
|
||||
'js': 'application/javascript',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'gif': 'image/gif',
|
||||
'png': 'image/png',
|
||||
'svg': 'image/svg+xml'
|
||||
};
|
||||
|
||||
StaticServlet.prototype.handleRequest = function(req, res) {
|
||||
var self = this;
|
||||
var path = (APP_ROOT + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
|
||||
return String.fromCharCode(parseInt(hex, 16));
|
||||
});
|
||||
var parts = path.split('/');
|
||||
if (parts[parts.length-1].charAt(0) === '.')
|
||||
return self.sendForbidden_(req, res, path);
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (req.url.pathname == '/js/config/local.conf.js' && err) {
|
||||
res.write("window.thServiceDomain = '" + DEFAULT_SERVICE_DOMAIN + "';");
|
||||
res.end();
|
||||
return
|
||||
}
|
||||
if (err)
|
||||
return self.sendMissing_(req, res, path);
|
||||
if (stat.isDirectory())
|
||||
return self.handleDirectoryRequest_(req, res, path);
|
||||
return self.sendFile_(req, res, path);
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.handleDirectoryRequest_ = function(req, res, path) {
|
||||
var self = this;
|
||||
if (path.match(/[^\/]$/)) {
|
||||
req.url.pathname += '/';
|
||||
var redirectUrl = url.format(url.parse(url.format(req.url)));
|
||||
return self.sendRedirect_(req, res, redirectUrl);
|
||||
}
|
||||
// Check if there is an index file (eg index.html) we can serve.
|
||||
var indexpath = path + INDEX_FILE;
|
||||
fs.stat(indexpath, function(err, stat) {
|
||||
if (stat && stat.isFile())
|
||||
return self.sendFile_(req, res, indexpath);
|
||||
// Otherwise just display the directory listing.
|
||||
return self.sendDirectory_(req, res, path);
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendError_ = function(req, res, error) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>Internal Server Error</title>\n');
|
||||
res.write('<h1>Internal Server Error</h1>');
|
||||
res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
|
||||
util.puts('500 Internal Server Error');
|
||||
util.puts(util.inspect(error));
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>404 Not Found</title>\n');
|
||||
res.write('<h1>Not Found</h1>');
|
||||
res.write(
|
||||
'<p>The requested URL ' +
|
||||
escapeHtml(path) +
|
||||
' was not found on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('404 Not Found: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(403, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>403 Forbidden</title>\n');
|
||||
res.write('<h1>Forbidden</h1>');
|
||||
res.write(
|
||||
'<p>You do not have permission to access ' +
|
||||
escapeHtml(path) + ' on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('403 Forbidden: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
|
||||
res.writeHead(301, {
|
||||
'Content-Type': 'text/html',
|
||||
'Location': redirectUrl
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>301 Moved Permanently</title>\n');
|
||||
res.write('<h1>Moved Permanently</h1>');
|
||||
res.write(
|
||||
'<p>The document has moved <a href="' +
|
||||
redirectUrl +
|
||||
'">here</a>.</p>'
|
||||
);
|
||||
res.end();
|
||||
util.puts('301 Moved Permanently: ' + redirectUrl);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendFile_ = function(req, res, path) {
|
||||
var self = this;
|
||||
var file = fs.createReadStream(path);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': StaticServlet.
|
||||
MimeMap[path.split('.').pop()] || 'text/plain'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
} else {
|
||||
file.on('data', res.write.bind(res));
|
||||
file.on('close', function() {
|
||||
res.end();
|
||||
});
|
||||
file.on('error', function(error) {
|
||||
self.sendError_(req, res, error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
|
||||
var self = this;
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, error);
|
||||
|
||||
if (!files.length)
|
||||
return self.writeDirectoryIndex_(req, res, path, []);
|
||||
|
||||
var remaining = files.length;
|
||||
files.forEach(function(fileName, index) {
|
||||
fs.stat(path + '/' + fileName, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, err);
|
||||
if (stat.isDirectory()) {
|
||||
files[index] = fileName + '/';
|
||||
}
|
||||
if (!(--remaining))
|
||||
return self.writeDirectoryIndex_(req, res, path, files);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>' + escapeHtml(path) + '</title>\n');
|
||||
res.write('<style>\n');
|
||||
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
|
||||
res.write('</style>\n');
|
||||
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
|
||||
res.write('<ol>');
|
||||
files.forEach(function(fileName) {
|
||||
if (fileName.charAt(0) !== '.') {
|
||||
res.write('<li><a href="' +
|
||||
escapeHtml(fileName) + '">' +
|
||||
escapeHtml(fileName) + '</a></li>');
|
||||
}
|
||||
});
|
||||
res.write('</ol>');
|
||||
res.end();
|
||||
};
|
||||
|
||||
function isFile(filename) {
|
||||
try {
|
||||
if (!fs.statSync(filename).isFile())
|
||||
throw("not a file");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Must be last,
|
||||
main(process.argv);
|
5027
yarn.lock
5027
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче