Skip to content

Commit

Permalink
Closes #2565: Add frontend extension capability. (#2799)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marina Samuel authored and arikfr committed Oct 14, 2018
1 parent 99c73ae commit 02e919c
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
steps:
- checkout
- run: npm install
- run: npm run bundle
- run: npm run build
- run: .circleci/update_version
- run: .circleci/pack
Expand Down Expand Up @@ -101,6 +102,7 @@ jobs:
docker-compose run -d -p 5000:5000 --user root server
docker-compose start postgres
docker-compose run --rm --user root server npm install
docker-compose run --rm --user root server npm run bundle
docker-compose run --rm --user root server npm run build
- run:
name: Run tests
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RUN pip install -r requirements.txt -r requirements_dev.txt
RUN if [ "x$skip_ds_deps" = "x" ] ; then pip install -r requirements_all_ds.txt ; else echo "Skipping pip install -r requirements_all_ds.txt" ; fi

COPY . ./
RUN npm install && npm run build && rm -rf node_modules
RUN npm install && npm run bundle && npm run build && rm -rf node_modules
RUN chown -R redash /app
USER redash

Expand Down
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.PHONY: build bundle compose_build create_database tests test_db clean

compose_build:
docker-compose build

test_db:
docker-compose run --rm postgres psql -h postgres -U postgres -c "create database tests"

create_database:
docker-compose run server create_db

clean:
docker ps -a -q | xargs docker kill;docker ps -a -q | xargs docker rm

bundle:
docker-compose run server bin/bundle-extensions

tests:
docker-compose run server tests

build: bundle
npm run build

watch: bundle
npm run watch

start: bundle
npm run start
39 changes: 39 additions & 0 deletions bin/bundle-extensions
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python

import os
from subprocess import call
from distutils.dir_util import copy_tree

from pkg_resources import iter_entry_points, resource_filename, resource_isdir



# Make a directory for extensions and set it as an environment variable
# to be picked up by webpack.
EXTENSIONS_RELATIVE_PATH = os.path.join('client', 'app', 'extensions')
EXTENSIONS_DIRECTORY = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
EXTENSIONS_RELATIVE_PATH)

if not os.path.exists(EXTENSIONS_DIRECTORY):
os.makedirs(EXTENSIONS_DIRECTORY)
os.environ["EXTENSIONS_DIRECTORY"] = EXTENSIONS_RELATIVE_PATH

for entry_point in iter_entry_points('redash.extensions'):
# This is where the frontend code for an extension lives
# inside of its package.
content_folder_relative = os.path.join(
entry_point.name, 'bundle')
(root_module, _) = os.path.splitext(entry_point.module_name)

if not resource_isdir(root_module, content_folder_relative):
continue

content_folder = resource_filename(root_module, content_folder_relative)

# This is where we place our extensions folder.
destination = os.path.join(
EXTENSIONS_DIRECTORY,
entry_point.name)

copy_tree(content_folder, destination)
6 changes: 6 additions & 0 deletions client/app/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ function registerComponents() {
registerAll(context);
}

function registerExtensions() {
const context = require.context('extensions', true, /^((?![\\/]test[\\/]).)*\.js$/);
registerAll(context);
}

function registerServices() {
const context = require.context('@/services', true, /^((?![\\/]test[\\/]).)*\.js$/);
registerAll(context);
Expand Down Expand Up @@ -142,6 +147,7 @@ markdownFilter(ngModule);
dateTimeFilter(ngModule);
registerComponents();
registerPages();
registerExtensions();
registerVisualizations(ngModule);

export default ngModule;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"start": "webpack-dev-server",
"dev": "REDASH_BACKEND=https://dev.redashapp.com npm start",
"bundle": "bin/bundle-extensions",
"build": "rm -rf ./client/dist/ && NODE_ENV=production webpack",
"watch": "webpack --watch --progress --colors -d",
"analyze": "rm -rf ./client/dist/ && BUNDLE_ANALYZER=on webpack",
Expand Down
22 changes: 19 additions & 3 deletions redash/extensions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pkg_resources import iter_entry_points
import os
from pkg_resources import iter_entry_points, resource_isdir, resource_listdir


def init_extensions(app):
Expand All @@ -10,5 +11,20 @@ def init_extensions(app):

for entry_point in iter_entry_points('redash.extensions'):
app.logger.info('Loading Redash extension %s.', entry_point.name)
extension = entry_point.load()
app.redash_extensions[entry_point.name] = extension(app)
try:
extension = entry_point.load()
app.redash_extensions[entry_point.name] = {
"entry_function": extension(app),
"resources_list": []
}
except ImportError:
app.logger.info('%s does not have a callable and will not be loaded.', entry_point.name)
(root_module, _) = os.path.splitext(entry_point.module_name)
content_folder_relative = os.path.join(entry_point.name, 'bundle')

# If it's a frontend extension only, store a list of files in the bundle directory.
if resource_isdir(root_module, content_folder_relative):
app.redash_extensions[entry_point.name] = {
"entry_function": None,
"resources_list": resource_listdir(root_module, content_folder_relative)
}
7 changes: 6 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const redashBackend = process.env.REDASH_BACKEND || "http://localhost:5000";
const basePath = fs.realpathSync(path.join(__dirname, "client"));
const appPath = fs.realpathSync(path.join(__dirname, "client", "app"));

const extensionsRelativePath = process.env.EXTENSIONS_DIRECTORY ||
path.join("client", "app", "extensions");
const extensionPath = fs.realpathSync(path.join(__dirname, extensionsRelativePath));

const config = {
entry: {
app: ["./client/app/index.js", "./client/app/assets/less/main.less"],
Expand All @@ -30,7 +34,8 @@ const config = {
resolve: {
extensions: ['.js', '.jsx'],
alias: {
"@": appPath
"@": appPath,
"extensions": extensionPath
}
},
plugins: [
Expand Down

0 comments on commit 02e919c

Please sign in to comment.