Commit b31d0abc authored by Eric Eastwood's avatar Eric Eastwood

Merge branch 'release/19.37.0'

parents ef64d6c8 b131e934
......@@ -101,3 +101,4 @@ unread-notification.out.html
node_modules_2
config/overrides
.env
webpack-report/
......@@ -20,7 +20,6 @@ stages:
- /npm_cache/
- npm_cache/
validate:
<<: *node_job
stage: build_unit_test
......@@ -32,7 +31,6 @@ validate:
# to re-use the scripts that GitLab has, https://gitlab.com/gitlab-org/gitlab-ce/issues/57010
- npm run prettier -- --check "**/*.js"
test:
<<: *node_job
variables:
......@@ -67,7 +65,7 @@ mobile-asset-build:
<<: *node_job
stage: build_unit_test
only:
- master
- master
script:
- npm run build-android-assets
- npm run build-ios-assets
......@@ -76,8 +74,6 @@ mobile-asset-build:
- output/android/www
- output/ios/www
.distribute_job: &distribute_job
image: registry.gitlab.com/gitlab-org/gitter/webapp/deploy-build-image:latest
stage: pre_deploy
......@@ -89,14 +85,12 @@ mobile-asset-build:
distribute_beta:
<<: *distribute_job
only:
- develop
- develop
variables:
DIST_S3_URL: s3://gitter-deployments/gitter-webapp/beta
distribute_beta_staging:
<<: *distribute_job
only:
- /^feature\/.*$/
variables:
DIST_S3_URL: s3://gitter-deployments/gitter-webapp/beta-staging
......@@ -168,13 +162,12 @@ deploy-build-image:
tags:
- internal # This has to be within the Gitter network
deploy_beta: &deploy_beta
<<: *deploy_job
dependencies:
- distribute_beta
only:
- develop
- develop
script:
- cd $ANSIBLE_DIR && ansible-playbook -i beta --vault-password-file "/root/.vault_pass" playbooks/gitter/webapp-deploy.yml
environment:
......@@ -200,6 +193,8 @@ deploy_beta_staging: &deploy_beta_staging
deploy_beta_staging_manual:
<<: *deploy_beta_staging
when: manual
only:
- /.*/
deploy_staging: &deploy_staging
<<: *deploy_job
......@@ -231,7 +226,6 @@ deploy_prod_manual: &deploy_prod
name: prod
url: https://gitter.im
docker-base:
image: docker:latest
stage: docker_images
......
# 19.37.0 - 2019-2-19
- Fix inline code blocks showing vertical scrollbar in the dark theme
- Thanks to [@tameo](https://gitlab.com/tameo) for the contribution, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1335
Developer facing:
- Upgrade from webpack v1 to latest webpack v4, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1322
- JavaScript chunks/bundles are now dynamically loaded based on webpack build manifest/artifact
- Try larger timeout for flakey GitHub integration tests
- https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1334
- https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1337
- Remove extraneous `lodash` from frontend webpack bundles (use `underscore`), https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1336
# 19.36.0 - 2019-2-15
- Fix GitLab issue decorations opening in GitHub (404) on mobile, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1321
......
......@@ -28,6 +28,9 @@ upload-to-s3:
ci-test:
mkdir -p output/
# Create the `output/assets/js/webpack-manifest.json` so
# we know which chunks to serve in `boot-script-utils.js`
gulp clientapp:compile:webpack
gulp test --test-coverage --test-suite docker --test-xunit-reports --test-bail
test: clean
......
......@@ -31,26 +31,7 @@ gulp.task('clientapp:compile:copy-files', function() {
gulp.task('clientapp:compile:webpack', ['clientapp:compile:copy-files'], function() {
return gulp
.src('./public/js/webpack.config')
.pipe(
webpack(require('../public/js/webpack.config'), null, function(err, stats) {
if (!stats) return;
/*
Removed as webpack-bundle-size-analyzer is broken
var webpackBundleSizeAnalyzer = require('webpack-bundle-size-analyzer');
var bundleStats = stats.toJson("normal");
var depTrees = webpackBundleSizeAnalyzer.dependencySizeTree(bundleStats);
gutil.log('-----------------------------------------------');
gutil.log('Webpack Bundle Size Report');
gutil.log('-----------------------------------------------');
depTrees.forEach(function (tree) {
return webpackBundleSizeAnalyzer.printDependencySizeTree(tree);
});
gutil.log('-----------------------------------------------');
*/
})
)
.pipe(webpack(require('../public/js/webpack.config')))
.pipe(gulp.dest('output/assets/js'));
});
......
......@@ -10,10 +10,13 @@ const mqpacker = require('css-mqpacker');
const csswring = require('csswring');
const styleBuilder = require('./style-builder');
const getSourceMapOptions = require('./get-sourcemap-options');
const webpack = require('webpack-stream');
const uglify = require('gulp-uglify');
const childProcessPromise = require('./child-process-promise');
const extractUrls = require('./extract-urls');
const bootScriptUtils = require('gitter-web-templates/lib/boot-script-utils');
// We need access to the `clientapp:compile:webpack` task
require('./gulpfile-clientapp');
var opts = require('yargs')
.option('android', {
......@@ -42,14 +45,14 @@ if (opts.android) {
* Hook into the compile stage
*/
gulp.task('embedded:compile', [
'clientapp:compile:copy-files',
'embedded:compile:copy-files',
'embedded:compile:markup',
'embedded:compile:css',
'embedded:compile:webpack'
'embedded:compile:copy-webpack-builds'
]);
// We also copy files after the CSS is compiled in `embedded:post-compile:copy-linked-assets`
gulp.task('clientapp:compile:copy-files', function() {
gulp.task('embedded:compile:copy-files', function() {
return gulp
.src(
[
......@@ -117,10 +120,14 @@ gulp.task('embedded:compile:css', function() {
});
/* Generate embedded native */
gulp.task('embedded:compile:webpack', ['clientapp:compile:copy-files'], function() {
gulp.task('embedded:compile:copy-webpack-builds', ['clientapp:compile:webpack'], function() {
const assets = bootScriptUtils.generateAssetsForChunk('mobile-native-embedded-chat');
return gulp
.src('./public/js/webpack-mobile-native.config')
.pipe(webpack(require('../public/js/webpack-mobile-native.config')))
.src(assets.map(asset => path.join('output/assets/js/', asset)), {
base: './output/assets/js/',
stat: true
})
.pipe(gulp.dest(path.join(buildPath, 'js')));
});
......
......@@ -44,6 +44,7 @@ gulp.task('process:assemble:copy-app:files', function() {
'npm-shrinkwrap.json',
'preinstall.sh',
'config/**',
'output/assets/js/webpack-manifest.json',
'public/templates/**',
'public/layouts/**',
'public/js/**',
......
......@@ -58,3 +58,20 @@ which will just 404 on a separate device when you try to sign in.
Recreate your secrets using your local network IP, see https://gitlab.com/gitlab-org/gitter/webapp#configure-service-secrets
Restart the server. You should now be able to access Gitter over your local IP from other devices
## View `webpack` bundle visualization (webpack report)
Run the webapp with the `WEBPACK_REPORT` environment variable set to generate the HTML report
macOS/Linux:
```
WEBPACK_REPORT=1 npm start
```
Windows:
```
set WEBPACK_REPORT=1&&npm start
```
Open `webpack-report/index.html` in your browser
......@@ -4,7 +4,7 @@ var isGitHubUser = require('../shared/is-github-user');
var avatarCdnResolver = require('../shared/avatar-cdn-resolver');
var extractTwitterAvatarInfo = require('../shared/extract-twitter-avatar-info');
var DEFAULT = require('url?limit=1024!../../../public/images/default-avatar.png'); // eslint-disable-line
var DEFAULT = require('url-loader?limit=1024!../../../public/images/default-avatar.png'); // eslint-disable-line
function getForGitHubUsername(githubUsername) {
return avatarCdnResolver('/gh/u/' + githubUsername);
......
......@@ -14,6 +14,9 @@ function assertNoDuplicates(collaborators) {
}
describe('collaborators-service #slow #github', function() {
// These tests timeout at 10000 sometimes otherwise
this.timeout(30000);
fixtureLoader.ensureIntegrationEnvironment('#integrationUser1');
var fixture = fixtureLoader.setup({
......
......@@ -6,6 +6,9 @@ var assert = require('assert');
var GithubContibutorService = require('..').GitHubContributorService;
describe('github-contributor-service #slow #github', function() {
// These tests timeout at 10000 sometimes otherwise
this.timeout(30000);
it('members should detailed emailed', function(done) {
var gh = new GithubContibutorService(null);
......
......@@ -7,6 +7,9 @@ var GitHubRepoService = require('..').GitHubRepoService;
var fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
describe('github-issue-service #slow #github', function() {
// These tests timeout at 10000 sometimes otherwise
this.timeout(30000);
fixtureLoader.ensureIntegrationEnvironment(
'GITTER_INTEGRATION_USERNAME',
'GITTER_INTEGRATION_REPO_SCOPE_TOKEN',
......
......@@ -7,6 +7,9 @@ const proxyquireNoCallThru = require('proxyquire').noCallThru();
const fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
describe('gitlab-issue-service #slow #gitlab', function() {
// These tests timeout at 10000 sometimes otherwise
this.timeout(30000);
fixtureLoader.ensureIntegrationEnvironment(
'GITLAB_USER_USERNAME',
'GITLAB_USER_TOKEN',
......
......@@ -5,9 +5,8 @@ function create() {
var webpackMiddleware = require('webpack-dev-middleware');
return webpackMiddleware(webpack(require('../webpack.config')), {
noInfo: false,
quiet: true,
lazy: true,
logLevel: 'warn',
lazy: false,
watchOptions: {
aggregateTimeout: 400
},
......
......@@ -2,7 +2,10 @@
var path = require('path');
const IS_PRODUCTION = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod';
var config = {
mode: IS_PRODUCTION ? 'production' : 'development',
entry: {
sw: require.resolve('./service-worker/sw')
},
......
'use strict';
const _ = require('lodash');
const cdn = require('gitter-web-cdn');
function cdnUrlGenerator(url, options = {}) {
if (options.root) {
return options.root + url;
}
return cdn(url, {});
}
let webpackBuildManifest;
function generateAssetsForChunk(chunkName) {
// We have this loaded just in time so we can wait for the initial Gitter boot-up that creates the manifest
try {
webpackBuildManifest =
// eslint-disable-next-line node/no-unpublished-require, node/no-missing-require
webpackBuildManifest || require('../../../output/assets/js/webpack-manifest.json');
} catch (err) {
throw new Error(
`You probably need to wait for the Gitter webpack build to finish. Error occured while requiring \`output/assets/js/webpack-manifest.json\`: ${err}\n${
err.stack
}`
);
}
const defaultAssets = webpackBuildManifest.entrypoints.default.assets || [];
const entryAssets = webpackBuildManifest.entrypoints[chunkName].assets;
const assets = Object.keys(
defaultAssets
.concat(entryAssets)
.filter(asset => !/.*\.map$/.test(asset))
.reduce((assetMap, asset) => {
assetMap[asset] = true;
return assetMap;
}, {})
);
return assets;
}
const bootScriptHelper = _.memoize(function(chunkName, parameters) {
const options = parameters.hash;
const jsRoot = (options && options.jsRoot) || 'js';
const assets = generateAssetsForChunk(chunkName);
const baseUrl = cdnUrlGenerator(jsRoot + '/', options);
const chunkScriptList = assets.map(asset => {
const cdnUrl = cdnUrlGenerator(`${jsRoot}/${asset}`, options);
return `<script type="text/javascript" src="${cdnUrl}"></script>`;
});
return `
<script type="text/javascript">window.webpackPublicPath = '${baseUrl}';</script>
${chunkScriptList.join('\n')}
`;
});
module.exports = {
generateAssetsForChunk,
bootScriptHelper
};
......@@ -7,37 +7,12 @@ var clientEnv = require('gitter-client-env');
var cdn = require('gitter-web-cdn');
var pluralize = require('../shared/helpers/pluralize');
var when = require('../shared/helpers/when');
const bootScriptUtils = require('./boot-script-utils');
exports.cdn = function(url, parameters) {
function cdnHelper(url, parameters) {
return cdn(url, parameters ? parameters.hash : null);
};
function cdnUrlGenerator(url, options) {
if (options.root) {
return options.root + url;
}
return cdn(url, {});
}
exports.bootScript = function(url, parameters) {
var options = parameters.hash;
var jsRoot = (options && options.jsRoot) || 'js';
var baseUrl = cdnUrlGenerator(jsRoot + '/', options);
var vendorScriptUrl = cdnUrlGenerator(jsRoot + '/vendor.js', options);
var bootScriptUrl = cdnUrlGenerator(jsRoot + '/' + url + '.js', options);
return util.format(
"<script type='text/javascript'>window.webpackPublicPath = '%s';</script>" +
"<script type='text/javascript' src='%s'></script>" +
"<script type='text/javascript' src='%s'></script>",
baseUrl,
vendorScriptUrl,
bootScriptUrl
);
};
function createEnv(context, options) {
if (options) {
return _.extend(
......@@ -50,7 +25,7 @@ function createEnv(context, options) {
}
return clientEnv;
}
exports.generateEnv = function(parameters) {
function generateEnv(parameters) {
var options = parameters.hash;
var env = createEnv(this, options);
......@@ -61,9 +36,9 @@ exports.generateEnv = function(parameters) {
';' +
'</script>'
);
};
}
exports.generateTroupeContext = function(troupeContext, parameters) {
function generateTroupeContext(troupeContext, parameters) {
var options = parameters.hash;
var env = createEnv(this, options);
......@@ -78,16 +53,13 @@ exports.generateTroupeContext = function(troupeContext, parameters) {
';' +
'</script>'
);
};
exports.pluralize = pluralize;
exports.when = when;
}
exports.toLowerCase = function(str) {
function toLowerCase(str) {
return str.toLowerCase();
};
}
exports.pad = function(options) {
function pad(options) {
var content = '' + options.fn(this);
var width = options.hash.width || 40;
var directionRight = options.hash.direction ? options.hash.direction === 'right' : true;
......@@ -100,10 +72,10 @@ exports.pad = function(options) {
}
}
return content;
};
}
// FIXME REMOVE THIS ONCE THE NEW ERRORS PAGES ARE DONE
exports.typewriter = function(el, str) {
function typewriter(el, str) {
return util.format(
'<script type="text/javascript">\n' +
'var text = "%s";' +
......@@ -122,25 +94,40 @@ exports.typewriter = function(el, str) {
str,
el
);
};
}
exports.formatNumber = function(n) {
function formatNumber(n) {
if (n < 1000) return n;
if (n < 1000000) return (n / 1000).toFixed(1) + 'k';
return (n / 100000).toFixed(1) + 'm';
};
}
/** FIXME we do not yet cover the ONE-TO-ONE case, also need to do better default values
* githubTypeToClass() takes a GitHub type and provdides a css class
*
*/
exports.githubTypeToClass = function(type) {
function githubTypeToClass(type) {
if (/_CHANNEL/.test(type)) return 'icon-hash';
else if (/REPO/.test(type)) return 'octicon-repo';
else if (/ORG/.test(type)) return 'octicon-organization';
else return 'default';
};
}
exports.getRoomName = function(name) {
function getRoomName(name) {
return name.split('/')[1] || 'general';
}
module.exports = {
cdn: cdnHelper,
bootScript: bootScriptUtils.bootScriptHelper,
generateEnv,
generateTroupeContext,
pluralize,
when,
toLowerCase,
pad,
typewriter,
formatNumber,
githubTypeToClass,
getRoomName
};
This diff is collapsed.
......@@ -167,14 +167,14 @@
"yargs": "^4.2.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@gitterhq/backbone-proxy-collection": "^2.3.0",
"@gitterhq/cal-heatmap": "^3.6.1",
"@gitterhq/handlebars-loader": "1.2.0-a",
"@gitterhq/styleguide": "^2.0.0",
"@gitterhq/handlebars-loader": "^1.2.0-a",
"@gitterhq/styleguide": "^2.2.0",
"autoprefixer-core": "^6.0.1",
"babel-core": "^6.16.0",
"babel-loader": "^6.4.1",
"babel-preset-es2015": "^6.24.1",
"babel-loader": "^8.0.5",
"backbone-url-resolver": "^0.1.1",
"backbone.cocktail": "^0.5.13",
"backbone.marionette": "^2.4.4",
......@@ -183,7 +183,7 @@
"chokidar": "^2.0.1",
"concurrently": "^2.0.0",
"cross-spawn": "^4.0.2",
"css-loader": "^0.28.11",
"css-loader": "^2.1.0",
"css-mqpacker": "^6.0.1",
"csswring": "^6.0.0",
"cumberbatch-name": "^1.0.9",
......@@ -223,7 +223,6 @@
"gulp-using": "^0.1.1",
"gulp-util": "^3.0.7",
"hammerjs": "^2.0.4",
"istanbul-instrumenter-loader": "^0.1.3",
"jquery": "2.1.0",
"jsmockito": "^1.0.5",
"keymaster": "^1.6.2",
......@@ -241,9 +240,8 @@
"parse-diff": "^0.4.0",
"path-parse": "^1.0.5",
"postcss": "^4.0.0",
"postcss-loader": "^1.3.3",
"postcss-loader": "^3.0.0",
"prettier": "^1.16.1",
"progress-bar-webpack-plugin": "^1.2.0",
"proxyquire": "0.4.0",
"pump": "^1.0.1",
"random-seed": "^0.2.0",
......@@ -254,17 +252,18 @@
"simple-git": "^1.73.0",
"sinon": "1.5.2",
"speedy": "^0.1.1",
"style-loader": "^0.13.1",
"style-loader": "^0.23.1",
"supertest": "^1.2.0",
"supertest-as-promised": "^3.1.0",
"tiny-cookie": "^0.5.5",
"url-loader": "^0.5.7",
"url-loader": "^1.1.2",
"vinyl-file": "^3.0.0",
"webpack": "^1.15.0",
"webpack-dev-middleware": "^1.12.2",
"webpack-stats-plugin": "^0.1.3",
"webpack-stream": "^3.2.0",
"webpack-visualizer-plugin": "^0.1.5"
"webpack": "^4.29.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-dev-middleware": "^3.5.1",
"webpack-stats-plugin": "^0.2.1",
"webpack-stream": "^5.2.1",
"webpack-visualizer-plugin": "^0.1.11"
},
"engines": {
"node": ">=6.0.0",
......
'use strict';
require('./utils/webpack');
require('./utils/context');
require('./utils/log');
require('./components/api-client');
// Polyfills
require('core-js/fn/object/assign');
'use strict';
var _ = require('lodash');
var _ = require('underscore');
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var $ = require('jquery');
......
'use strict';
var path = require('path');
var _ = require('lodash');
var mainWebpackConfig = require('./webpack.config');
var DedupePlugin = require('webpack/lib/optimize/DedupePlugin');
var mobileConfig = _.extend({}, mainWebpackConfig);
mobileConfig.entry = {
'mobile-native-embedded-chat': path.resolve(
path.join(__dirname, './mobile-native-embedded-chat')
),
vendor: mainWebpackConfig.entry.vendor
};
mobileConfig.plugins = mobileConfig.plugins.slice();
mobileConfig.plugins.push(new DedupePlugin());
module.exports = mobileConfig;
'use strict';
var path = require('path');
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
var ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');
const path = require('path');
const webpack = require('webpack');
const ProvidePlugin = require('webpack/lib/ProvidePlugin');
const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var getPostcssStack = require('@gitterhq/styleguide/postcss-stack');
const getPostcssStack = require('@gitterhq/styleguide/postcss-stack');
var devMode = process.env.WEBPACK_DEV_MODE === '1';
// Default to production unless we know for sure we are in dev
const IS_PRODUCTION = process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'dev';
const WEBPACK_REPORT = process.env.WEBPACK_REPORT;
const ROOT_PATH = path.resolve(__dirname, '../../');
const webpackConfig = {
mode: IS_PRODUCTION ? 'production' : 'development',
target: 'web',
var webpackConfig = {
entry: {
default: [path.resolve(path.join(__dirname, './default'))],
'router-nli-app': path.resolve(path.join(__dirname, './router-nli-app.js')),
'router-nli-chat': path.resolve(path.join(__dirname, './router-nli-chat.js')),
'router-app': path.resolve(path.join(__dirname, './router-app.js')),
......@@ -35,49 +46,28 @@ var webpackConfig = {
'chat-message-reports': path.resolve(path.join(__dirname, './chat-message-reports.js')),
'mobile-native-userhome': path.resolve(path.join(__dirname, './mobile-native-userhome')),
'router-home-learn': path.resolve(path.join(__dirname, './router-home-learn')),
vendor: [
require.resolve('./utils/webpack'),
require.resolve('./utils/context'),
'underscore',
'jquery',
'backbone',
'backbone.marionette',
'loglevel',
require.resolve('./utils/log'),
require.resolve('./components/api-client'),
'handlebars/runtime',
'gitter-realtime-client',
'raven-js',
'keymaster',
'moment',
'bluebird',
'fuzzysearch',
'url-join',
path.resolve(path.join(__dirname, './commons.js'))
]
'mobile-native-embedded-chat': path.resolve(
path.join(__dirname, './mobile-native-embedded-chat')
),
'router-home-learn': path.resolve(path.join(__dirname, './router-home-learn'))
},
output: {
path: path.resolve(__dirname, '../../output/assets/js/'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
publicPath: '/_s/l/js/',
devtoolModuleFilenameTemplate: '[resource-path]',
devtoolFallbackModuleFilenameTemplate: '[resource-path]?[hash]'
},
module: {
//JP 12/1/16 If you add a loader remember to add it to /test/in-browser/webpack.config.js
loaders: [
strictExportPresence: true,
rules: [
{
test: /\.js?$/,
loader: 'babel',
test: /\.js$/,