mirror of https://github.com/apache/superset.git
chore: allow webpack-dev-server proxy to any destination (#9296)
One of the pain points in developing Superset frontend code is the lack of testing data. Local installation often do not have enough examples setup to test all edge cases. This change allows `webpack-dev-server` to proxy to any remote Superset service, but the same time replaces frontend asset references in HTML with links to local development version. This allows developers to test with production data locally, tackling edge cases all while maintaining the productivity of editing the code locally.
This commit is contained in:
parent
8f7ce168a0
commit
c36a7e3ada
|
@ -496,6 +496,10 @@ npm run dev-server -- --devserverPort=9001
|
||||||
|
|
||||||
# Run the dev server proxying to a Flask server on a non-default port
|
# Run the dev server proxying to a Flask server on a non-default port
|
||||||
npm run dev-server -- --supersetPort=8081
|
npm run dev-server -- --supersetPort=8081
|
||||||
|
|
||||||
|
# Or proxy it to a remote backend so you can test frontend changes without
|
||||||
|
# starting the backend locally
|
||||||
|
npm run dev-server -- --superset=https://superset-dev.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively you can use one of the following commands.
|
Alternatively you can use one of the following commands.
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = {
|
||||||
'prettier',
|
'prettier',
|
||||||
'prettier/@typescript-eslint',
|
'prettier/@typescript-eslint',
|
||||||
],
|
],
|
||||||
plugins: ['@typescript-eslint', 'prettier', 'react'],
|
plugins: ['@typescript-eslint/eslint-plugin', 'prettier', 'react'],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/ban-ts-ignore': 0,
|
'@typescript-eslint/ban-ts-ignore': 0,
|
||||||
'@typescript-eslint/camelcase': [
|
'@typescript-eslint/camelcase': [
|
||||||
|
|
|
@ -177,6 +177,7 @@
|
||||||
"@types/react-redux": "^7.1.7",
|
"@types/react-redux": "^7.1.7",
|
||||||
"@types/react-select": "^3.0.10",
|
"@types/react-select": "^3.0.10",
|
||||||
"@types/react-table": "^7.0.2",
|
"@types/react-table": "^7.0.2",
|
||||||
|
"@types/yargs": "12 - 15",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.20.0",
|
"@typescript-eslint/eslint-plugin": "^2.20.0",
|
||||||
"@typescript-eslint/parser": "^2.20.0",
|
"@typescript-eslint/parser": "^2.20.0",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
|
@ -216,7 +217,6 @@
|
||||||
"less": "^3.9.0",
|
"less": "^3.9.0",
|
||||||
"less-loader": "^5.0.0",
|
"less-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "^0.4.0",
|
"mini-css-extract-plugin": "^0.4.0",
|
||||||
"minimist": "^1.2.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
"po2json": "^0.4.5",
|
"po2json": "^0.4.5",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
|
@ -238,7 +238,8 @@
|
||||||
"webpack-bundle-analyzer": "^3.4.1",
|
"webpack-bundle-analyzer": "^3.4.1",
|
||||||
"webpack-cli": "^3.1.1",
|
"webpack-cli": "^3.1.1",
|
||||||
"webpack-dev-server": "^3.1.14",
|
"webpack-dev-server": "^3.1.14",
|
||||||
"webpack-sources": "^1.1.0"
|
"webpack-sources": "^1.1.0",
|
||||||
|
"yargs": "12 - 15"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "^2.0.7"
|
"fsevents": "^2.0.7"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
@ -29,9 +31,7 @@ const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const WebpackAssetsManifest = require('webpack-assets-manifest');
|
const WebpackAssetsManifest = require('webpack-assets-manifest');
|
||||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
|
const parsedArgs = require('yargs').argv;
|
||||||
// Parse command-line arguments
|
|
||||||
const parsedArgs = require('minimist')(process.argv.slice(2));
|
|
||||||
|
|
||||||
// input dir
|
// input dir
|
||||||
const APP_DIR = path.resolve(__dirname, './');
|
const APP_DIR = path.resolve(__dirname, './');
|
||||||
|
@ -41,13 +41,23 @@ const BUILD_DIR = path.resolve(__dirname, '../superset/static/assets');
|
||||||
const {
|
const {
|
||||||
mode = 'development',
|
mode = 'development',
|
||||||
devserverPort = 9000,
|
devserverPort = 9000,
|
||||||
supersetPort = 8088,
|
|
||||||
measure = false,
|
measure = false,
|
||||||
analyzeBundle = false,
|
analyzeBundle = false,
|
||||||
} = parsedArgs;
|
} = parsedArgs;
|
||||||
|
|
||||||
const isDevMode = mode !== 'production';
|
const isDevMode = mode !== 'production';
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
path: BUILD_DIR,
|
||||||
|
publicPath: '/static/assets/', // necessary for lazy-loaded chunks
|
||||||
|
};
|
||||||
|
if (isDevMode) {
|
||||||
|
output.filename = '[name].[hash:8].entry.js';
|
||||||
|
output.chunkFilename = '[name].[hash:8].chunk.js';
|
||||||
|
} else {
|
||||||
|
output.filename = '[name].[chunkhash].entry.js';
|
||||||
|
output.chunkFilename = '[name].[chunkhash].chunk.js';
|
||||||
|
}
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
// creates a manifest.json mapping of name to hashed output used in template files
|
// creates a manifest.json mapping of name to hashed output used in template files
|
||||||
new WebpackAssetsManifest({
|
new WebpackAssetsManifest({
|
||||||
|
@ -86,7 +96,6 @@ const plugins = [
|
||||||
{ copyUnmodified: true },
|
{ copyUnmodified: true },
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
// Enable hot module replacement
|
// Enable hot module replacement
|
||||||
plugins.push(new webpack.HotModuleReplacementPlugin());
|
plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||||
|
@ -101,19 +110,6 @@ if (isDevMode) {
|
||||||
plugins.push(new OptimizeCSSAssetsPlugin());
|
plugins.push(new OptimizeCSSAssetsPlugin());
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = {
|
|
||||||
path: BUILD_DIR,
|
|
||||||
publicPath: '/static/assets/', // necessary for lazy-loaded chunks
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDevMode) {
|
|
||||||
output.filename = '[name].[hash:8].entry.js';
|
|
||||||
output.chunkFilename = '[name].[hash:8].chunk.js';
|
|
||||||
} else {
|
|
||||||
output.filename = '[name].[chunkhash].entry.js';
|
|
||||||
output.chunkFilename = '[name].[chunkhash].chunk.js';
|
|
||||||
}
|
|
||||||
|
|
||||||
const PREAMBLE = ['babel-polyfill', path.join(APP_DIR, '/src/preamble.js')];
|
const PREAMBLE = ['babel-polyfill', path.join(APP_DIR, '/src/preamble.js')];
|
||||||
|
|
||||||
function addPreamble(entry) {
|
function addPreamble(entry) {
|
||||||
|
@ -292,27 +288,50 @@ const config = {
|
||||||
'react/lib/ReactContext': true,
|
'react/lib/ReactContext': true,
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
devtool: isDevMode ? 'cheap-module-eval-source-map' : false,
|
devtool: false,
|
||||||
devServer: {
|
};
|
||||||
|
|
||||||
|
let proxyConfig = {};
|
||||||
|
const requireModule = module.require;
|
||||||
|
|
||||||
|
function loadProxyConfig() {
|
||||||
|
try {
|
||||||
|
delete require.cache[require.resolve('./webpack.proxy-config')];
|
||||||
|
proxyConfig = requireModule('./webpack.proxy-config');
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'ENOENT') {
|
||||||
|
console.error('\n>> Error loading proxy config:');
|
||||||
|
console.trace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDevMode) {
|
||||||
|
config.devtool = 'cheap-module-eval-source-map';
|
||||||
|
|
||||||
|
config.devServer = {
|
||||||
|
before() {
|
||||||
|
loadProxyConfig();
|
||||||
|
// hot reloading proxy config
|
||||||
|
fs.watch('./webpack.proxy-config.js', loadProxyConfig);
|
||||||
|
},
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
hot: true,
|
hot: true,
|
||||||
index: '', // This line is needed to enable root proxying
|
|
||||||
inline: true,
|
inline: true,
|
||||||
stats: 'minimal',
|
stats: 'minimal',
|
||||||
overlay: true,
|
overlay: true,
|
||||||
port: devserverPort,
|
port: devserverPort,
|
||||||
// Only serves bundled files from webpack-dev-server
|
// Only serves bundled files from webpack-dev-server
|
||||||
// and proxy everything else to Superset backend
|
// and proxy everything else to Superset backend
|
||||||
proxy: {
|
proxy: [
|
||||||
context: () => true,
|
// functions are called for every request
|
||||||
'/': `http://localhost:${supersetPort}`,
|
() => {
|
||||||
target: `http://localhost:${supersetPort}`,
|
return proxyConfig;
|
||||||
},
|
},
|
||||||
|
],
|
||||||
contentBase: path.join(process.cwd(), '../static/assets'),
|
contentBase: path.join(process.cwd(), '../static/assets'),
|
||||||
},
|
};
|
||||||
};
|
} else {
|
||||||
|
|
||||||
if (!isDevMode) {
|
|
||||||
config.optimization.minimizer = [
|
config.optimization.minimizer = [
|
||||||
new TerserPlugin({
|
new TerserPlugin({
|
||||||
cache: '.terser-plugin-cache/',
|
cache: '.terser-plugin-cache/',
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const zlib = require('zlib');
|
||||||
|
const path = require('path');
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
const parsedArgs = require('yargs').argv;
|
||||||
|
|
||||||
|
const { supersetPort = 8088, superset: supersetUrl = null } = parsedArgs;
|
||||||
|
const backend = (supersetUrl || `http://localhost:${supersetPort}`).replace(
|
||||||
|
'//+$/',
|
||||||
|
'',
|
||||||
|
); // strip ending backslash
|
||||||
|
const MANIFEST_FILE = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../superset/static/assets/manifest.json',
|
||||||
|
);
|
||||||
|
|
||||||
|
let manifestContent;
|
||||||
|
let manifest;
|
||||||
|
function loadManifest() {
|
||||||
|
try {
|
||||||
|
const newContent = fs.readFileSync(MANIFEST_FILE, { encoding: 'utf-8' });
|
||||||
|
if (!newContent || newContent === manifestContent) return;
|
||||||
|
manifestContent = newContent;
|
||||||
|
manifest = JSON.parse(manifestContent);
|
||||||
|
console.log(`${MANIFEST_FILE} loaded.`);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'ENOENT') {
|
||||||
|
console.error('\n>> Error loading manifest file:');
|
||||||
|
console.trace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHTML(res) {
|
||||||
|
const CONTENT_TYPE_HEADER = 'content-type';
|
||||||
|
const contentType = res.getHeader
|
||||||
|
? res.getHeader(CONTENT_TYPE_HEADER)
|
||||||
|
: res.headers[CONTENT_TYPE_HEADER];
|
||||||
|
return contentType.includes('text/html');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDevHTML(originalHtml) {
|
||||||
|
let html = originalHtml.replace(
|
||||||
|
/(<head>\s*<title>)([\s\S]*)(<\/title>)/i,
|
||||||
|
'$1[DEV] $2 $3',
|
||||||
|
);
|
||||||
|
// load manifest file only when needed
|
||||||
|
if (!manifest) {
|
||||||
|
loadManifest();
|
||||||
|
}
|
||||||
|
if (manifest) {
|
||||||
|
// replace bundled asset files, HTML comment tags generated by Jinja macros
|
||||||
|
// in superset/templates/superset/partials/asset_bundle.html
|
||||||
|
html = html.replace(
|
||||||
|
/<!-- Bundle (css|js) (.*?) START -->[\s\S]*?<!-- Bundle \1 \2 END -->/gi,
|
||||||
|
(match, assetType, bundleName) => {
|
||||||
|
if (bundleName in manifest.entrypoints) {
|
||||||
|
return `<!-- DEV bundle: ${bundleName} ${assetType} START -->\n ${(
|
||||||
|
manifest.entrypoints[bundleName][assetType] || []
|
||||||
|
)
|
||||||
|
.map(chunkFilePath =>
|
||||||
|
assetType === 'css'
|
||||||
|
? `<link rel="stylesheet" type="text/css" href="${chunkFilePath}" />`
|
||||||
|
: `<script src="${chunkFilePath}"></script>`,
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
'\n ',
|
||||||
|
)}\n <!-- DEV bundle: ${bundleName} ${assetType} END -->`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyHeaders(originalResponse, response) {
|
||||||
|
response.statusCode = originalResponse.statusCode;
|
||||||
|
response.statusMessage = originalResponse.statusMessage;
|
||||||
|
if (response.setHeader) {
|
||||||
|
let keys = Object.keys(originalResponse.headers);
|
||||||
|
if (isHTML(originalResponse)) {
|
||||||
|
keys = keys.filter(
|
||||||
|
key => key !== 'content-encoding' && key !== 'content-length',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
keys.forEach(key => {
|
||||||
|
let value = originalResponse.headers[key];
|
||||||
|
if (key === 'set-cookie') {
|
||||||
|
// remove cookie domain
|
||||||
|
value = Array.isArray(value) ? value : [value];
|
||||||
|
value = value.map(x => x.replace(/Domain=[^;]+?/i, ''));
|
||||||
|
} else if (key === 'location') {
|
||||||
|
// set redirects to use local URL
|
||||||
|
value = (value || '').replace(backend, '');
|
||||||
|
}
|
||||||
|
response.setHeader(key, value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response.headers = originalResponse.headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manipulate HTML server response to replace asset files with
|
||||||
|
* local webpack-dev-server build.
|
||||||
|
*/
|
||||||
|
function processHTML(proxyResponse, response) {
|
||||||
|
let body = Buffer.from([]);
|
||||||
|
let originalResponse = proxyResponse;
|
||||||
|
|
||||||
|
// decode GZIP response
|
||||||
|
if (originalResponse.headers['content-encoding'] === 'gzip') {
|
||||||
|
const gunzip = zlib.createGunzip();
|
||||||
|
originalResponse.pipe(gunzip);
|
||||||
|
originalResponse = gunzip;
|
||||||
|
}
|
||||||
|
|
||||||
|
originalResponse
|
||||||
|
.on('data', data => {
|
||||||
|
body = Buffer.concat([body, data]);
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
response.end(toDevHTML(body.toString()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the manifest file exists
|
||||||
|
fs.mkdirSync(path.dirname(MANIFEST_FILE), { recursive: true });
|
||||||
|
fs.closeSync(fs.openSync(MANIFEST_FILE, 'as+'));
|
||||||
|
// watch it as webpack-dev-server updates it
|
||||||
|
fs.watch(MANIFEST_FILE, loadManifest);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
context: '/',
|
||||||
|
target: backend,
|
||||||
|
hostRewrite: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
cookieDomainRewrite: '', // remove cookie domain
|
||||||
|
selfHandleResponse: true, // so that the onProxyRes takes care of sending the response
|
||||||
|
onProxyRes(proxyResponse, request, response) {
|
||||||
|
try {
|
||||||
|
copyHeaders(proxyResponse, response);
|
||||||
|
if (isHTML(response)) {
|
||||||
|
processHTML(proxyResponse, response);
|
||||||
|
} else {
|
||||||
|
proxyResponse.pipe(response);
|
||||||
|
}
|
||||||
|
response.flushHeaders();
|
||||||
|
} catch (e) {
|
||||||
|
response.setHeader('content-type', 'text/plain');
|
||||||
|
response.write(`Error requesting ${request.path} from proxy:\n\n`);
|
||||||
|
response.end(e.stack);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -43,7 +43,7 @@ app: Flask = current_app
|
||||||
cache = LocalProxy(lambda: cache_manager.cache)
|
cache = LocalProxy(lambda: cache_manager.cache)
|
||||||
conf = LocalProxy(lambda: current_app.config)
|
conf = LocalProxy(lambda: current_app.config)
|
||||||
get_feature_flags = feature_flag_manager.get_feature_flags
|
get_feature_flags = feature_flag_manager.get_feature_flags
|
||||||
get_css_manifest_files = manifest_processor.get_css_manifest_files
|
get_manifest_files = manifest_processor.get_manifest_files
|
||||||
is_feature_enabled = feature_flag_manager.is_feature_enabled
|
is_feature_enabled = feature_flag_manager.is_feature_enabled
|
||||||
jinja_base_context = jinja_context_manager.base_context
|
jinja_base_context = jinja_context_manager.base_context
|
||||||
results_backend = LocalProxy(lambda: results_backend_manager.results_backend)
|
results_backend = LocalProxy(lambda: results_backend_manager.results_backend)
|
||||||
|
|
|
@ -82,11 +82,18 @@ class UIManifestProcessor:
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def get_manifest(): # pylint: disable=unused-variable
|
def get_manifest(): # pylint: disable=unused-variable
|
||||||
|
loaded_chunks = set()
|
||||||
|
|
||||||
|
def get_files(bundle, asset_type="js"):
|
||||||
|
files = self.get_manifest_files(bundle, asset_type)
|
||||||
|
filtered_files = [f for f in files if f not in loaded_chunks]
|
||||||
|
for f in filtered_files:
|
||||||
|
loaded_chunks.add(f)
|
||||||
|
return filtered_files
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
loaded_chunks=set(),
|
js_manifest=lambda bundle: get_files(bundle, "js"),
|
||||||
get_unloaded_chunks=self.get_unloaded_chunks,
|
css_manifest=lambda bundle: get_files(bundle, "css"),
|
||||||
js_manifest=self.get_js_manifest_files,
|
|
||||||
css_manifest=self.get_css_manifest_files,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_manifest_json(self):
|
def parse_manifest_json(self):
|
||||||
|
@ -99,28 +106,13 @@ class UIManifestProcessor:
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_js_manifest_files(self, filename):
|
def get_manifest_files(self, bundle, asset_type):
|
||||||
if self.app.debug:
|
if self.app.debug:
|
||||||
self.parse_manifest_json()
|
self.parse_manifest_json()
|
||||||
entry_files = self.manifest.get(filename, {})
|
return self.manifest.get(bundle, {}).get(asset_type, [])
|
||||||
return entry_files.get("js", [])
|
|
||||||
|
|
||||||
def get_css_manifest_files(self, filename):
|
|
||||||
if self.app.debug:
|
|
||||||
self.parse_manifest_json()
|
|
||||||
entry_files = self.manifest.get(filename, {})
|
|
||||||
return entry_files.get("css", [])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_unloaded_chunks(files, loaded_chunks):
|
|
||||||
filtered_files = [f for f in files if f not in loaded_chunks]
|
|
||||||
for f in filtered_files:
|
|
||||||
loaded_chunks.add(f)
|
|
||||||
return filtered_files
|
|
||||||
|
|
||||||
|
|
||||||
APP_DIR = os.path.dirname(__file__)
|
APP_DIR = os.path.dirname(__file__)
|
||||||
|
|
||||||
appbuilder = AppBuilder(update_perms=False)
|
appbuilder = AppBuilder(update_perms=False)
|
||||||
cache_manager = CacheManager()
|
cache_manager = CacheManager()
|
||||||
celery_app = celery.Celery()
|
celery_app = celery.Celery()
|
||||||
|
|
|
@ -31,7 +31,5 @@
|
||||||
|
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% with filename="addSlice" %}
|
{{ js_bundle("addSlice") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -17,25 +17,20 @@
|
||||||
under the License.
|
under the License.
|
||||||
#}
|
#}
|
||||||
{% extends "appbuilder/baselayout.html" %}
|
{% extends "appbuilder/baselayout.html" %}
|
||||||
|
{% from 'superset/partials/asset_bundle.html' import css_bundle, js_bundle with context %}
|
||||||
|
|
||||||
{% block head_css %}
|
{% block head_css %}
|
||||||
{{super()}}
|
{{ super() }}
|
||||||
<link rel="icon" type="image/png" href="/static/assets/images/favicon.png">
|
<link rel="icon" type="image/png" href="/static/assets/images/favicon.png">
|
||||||
{% for entry in get_unloaded_chunks(css_manifest('theme'), loaded_chunks) %}
|
{{ css_bundle("theme") }}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ entry }}" />
|
{% endblock %}
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block head_js %}
|
{% block head_js %}
|
||||||
{{super()}}
|
{{ super() }}
|
||||||
{% with filename="theme" %}
|
{{ js_bundle("theme") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
{% endblock %}
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{{super()}}
|
{{ super() }}
|
||||||
{% with filename="preamble" %}
|
{{ js_bundle("preamble") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
{% endblock %}
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% import 'appbuilder/general/lib.html' as lib %}
|
{% import 'appbuilder/general/lib.html' as lib %}
|
||||||
|
{% from 'superset/partials/asset_bundle.html' import css_bundle, js_bundle with context %}
|
||||||
|
|
||||||
{% set favicons = appbuilder.app.config['FAVICONS'] %}
|
{% set favicons = appbuilder.app.config['FAVICONS'] %}
|
||||||
|
|
||||||
|
@ -45,22 +46,15 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/appbuilder/css/flags/flags16.css" />
|
<link rel="stylesheet" type="text/css" href="/static/appbuilder/css/flags/flags16.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/appbuilder/css/font-awesome.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/appbuilder/css/font-awesome.min.css">
|
||||||
|
|
||||||
{% for entry in get_unloaded_chunks(css_manifest('theme'), loaded_chunks) %}
|
{{ css_bundle("theme") }}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ entry }}" />
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if entry %}
|
{% if entry %}
|
||||||
{% set entry_files = css_manifest(entry) %}
|
{{ css_bundle(entry) }}
|
||||||
{% for entry in get_unloaded_chunks(entry_files, loaded_chunks) %}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{ entry }}" />
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% with filename="theme" %}
|
{{ js_bundle("theme") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@ -105,9 +99,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{% if entry %}
|
{% if entry %}
|
||||||
{% with filename=entry %}
|
{{ js_bundle(entry) }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -29,7 +29,5 @@
|
||||||
|
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% with filename="showSavedQuery" %}
|
{{ js_bundle("showSavedQuery") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -16,8 +16,20 @@
|
||||||
specific language governing permissions and limitations
|
specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
#}
|
#}
|
||||||
{% block partial_js %}
|
{% macro js_bundle(filename) %}
|
||||||
{% for entry in get_unloaded_chunks(js_manifest(filename), loaded_chunks) %}
|
{# HTML comment is needed for webpack-dev-server to replace assets
|
||||||
|
with development version #}
|
||||||
|
<!-- Bundle js {{ filename }} START -->
|
||||||
|
{% for entry in js_manifest(filename) %}
|
||||||
<script src="{{ entry }}"></script>
|
<script src="{{ entry }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
<!-- Bundle js {{ filename }} END -->
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro css_bundle(filename) %}
|
||||||
|
<!-- Bundle css {{ filename }} START -->
|
||||||
|
{% for entry in css_manifest(filename) %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ entry }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<!-- Bundle css {{ filename }} END -->
|
||||||
|
{% endmacro %}
|
|
@ -22,7 +22,5 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{% with filename="welcome" %}
|
{{ js_bundle("welcome") }}
|
||||||
{% include "superset/partials/_script_tag.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -45,7 +45,7 @@ from geopy.point import Point
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from pandas.tseries.frequencies import to_offset
|
from pandas.tseries.frequencies import to_offset
|
||||||
|
|
||||||
from superset import app, cache, get_css_manifest_files, security_manager
|
from superset import app, cache, get_manifest_files, security_manager
|
||||||
from superset.constants import NULL_STRING
|
from superset.constants import NULL_STRING
|
||||||
from superset.exceptions import NullValueException, SpatialException
|
from superset.exceptions import NullValueException, SpatialException
|
||||||
from superset.models.helpers import QueryResult
|
from superset.models.helpers import QueryResult
|
||||||
|
@ -786,7 +786,7 @@ class MarkupViz(BaseViz):
|
||||||
code = self.form_data.get("code", "")
|
code = self.form_data.get("code", "")
|
||||||
if markup_type == "markdown":
|
if markup_type == "markdown":
|
||||||
code = markdown(code)
|
code = markdown(code)
|
||||||
return dict(html=code, theme_css=get_css_manifest_files("theme"))
|
return dict(html=code, theme_css=get_manifest_files("theme", "css"))
|
||||||
|
|
||||||
|
|
||||||
class SeparatorViz(MarkupViz):
|
class SeparatorViz(MarkupViz):
|
||||||
|
|
Loading…
Reference in New Issue