diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23bd6c92..2106853b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,9 @@
- New styling that follows the source{d} branding ([#139](https://github.com/src-d/sourced-ui/issues/139), [#142](https://github.com/src-d/sourced-ui/issues/142), [#204](https://github.com/src-d/sourced-ui/pull/204).)
- Improved method to export and import dashboards as JSON ([#165](https://github.com/src-d/sourced-ui/issues/165)).
+- Added a development mode to run source{d} in a hot reloading way, so every change in `srcd` or `superset`
+will trigger a refresh in the rowser [[see docs](./CONTRIBUTING.md#run-sourced-ce-for-development-with-hot-reloading)].
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..dd91f3ee
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,107 @@
+# Contribution Guidelines
+
+As all source{d} projects, this project follows the
+[source{d} Contributing Guidelines](https://github.com/src-d/guide/blob/master/engineering/documents/CONTRIBUTING.md).
+
+
+# Additional Contribution Guidelines
+
+In addition to the [source{d} Contributing Guidelines](https://github.com/src-d/guide/blob/master/engineering/documents/CONTRIBUTING.md),
+this project follows the following guidelines.
+
+**Content:**
+
+- [Changelog](#changelog)
+- [Run source{d} CE For Development With Hot Reloading](#run-source-d-ce-for-development-with-hot-reloading)
+
+
+## Changelog
+
+This project lists the important changes between releases in the
+[`CHANGELOG.md`](CHANGELOG.md) file.
+
+If you open a PR, you should also add a brief summary in the `CHANGELOG.md`
+mentioning the new feature, change or bugfix that you proposed.
+
+
+## Run source{d} CE For Development With Hot Reloading
+
+Running **source{d} CE** in development mode will enable hot reloading at
+http://127.0.0.1:8088, so every change you perform in `srcd` or `superset`
+directories will trigger a refresh in your browser with the new code.
+
+1. Install the latest version of [**source{d} CE**](https://github.com/src-d/sourced-ce/releases)
+(see [installation guide](https://docs.sourced.tech/community-edition/quickstart/2-install-sourced)
+if needed)
+
+1. Run the watcher.
+
+
+ IMPORTANT note: Requirements for the watcher
+
+ The watcher requires either [`inotify-tools`](https://github.com/rvoicilas/inotify-tools/wiki)
+ (for Linux), or [`fswatch`](https://github.com/emcrisostomo/fswatch)
+ (for Linux and MacOS)
+
+ - To install `inotify-tools`
+ - in Ubuntu you can run:
+ ```shell
+ $ sudo apt-get install inotify-tools
+ ```
+
+ - To install `fswatch`:
+ - in MacOS, you can [use brew to install `fswatch`](https://brewinstall.org/install-fswatch-on-mac-with-brew):
+ ```shell
+ $ brew install fswatch
+ ```
+ - in Ubuntu you can build and install [from `fswatch` sources](https://github.com/emcrisostomo/fswatch):
+ ```shell
+ $ ./autogen.sh
+ $ ./configure
+ $ make
+ $ sudo make install
+ $ sudo ldconfig
+ ```
+
+
+
+ ALTERNATIVE: If you can not use any watcher
+
+ - If you cannot use any of these watchers, you can just run this:
+
+ ```shell
+ $ make set-override # to prepare the environment
+ $ make patch # needed EVERYTIME you change something in `srcd`
+ $ make clean # once you finish, to clean up everything.
+ ```
+
+
+ To watch for changes in `srcd` directory, run the local watcher in one tab,
+ from your local `sourced-ui` root directory:
+
+ ```shell
+ $ make dev-prepare
+ ```
+
+ You can stop it pressing `Ctrl+C`. Once you do it, the patch will be
+ automatically cleaned.
+
+1. Run `sourced` as usually (see [execution guide](https://docs.sourced.tech/community-edition/quickstart/3-init-sourced)
+ if needed):
+
+ ```shell
+ # for repositories from GitHub organizations
+ $ sourced init orgs --token=
+
+ # or, for repositories stored locally
+ $ sourced init local
+ ```
+
+ The first time you launch it, it will take some time to build all the UI assets,
+ you can see the progress of the build from `sourced-ui` logs (see next step).
+
+1. To see `sourced-ui` logs (with `npm` errors and such), run:
+
+ ```shell
+ $ sourced logs -f sourced-ui
+ ```
diff --git a/Makefile b/Makefile
index fbf78d9e..43f7d0af 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,13 @@ SUPERSET_DIR = superset
# Directory with custom code to copy into SUPERSET_DIR
PATCH_SOURCE_DIR = srcd
+# Directory with template for docker-compose.override.yml
+OVERRIDE_TEMPLATE_PATH := $(PATCH_SOURCE_DIR)/contrib/docker/docker-compose.override.yml
+# Directory for the sourced global docker-compose.override.yml
+OVERRIDE_OUTPUT_PATH := $(HOME)/.sourced/compose-files/__active__/docker-compose.override.yml
+# Directory for the docker-compose.override.yml backup
+OVERRIDE_BACKUP_PATH := $(OVERRIDE_OUTPUT_PATH).bak
+
# Name of the docker image to build
DOCKER_IMAGE_NAME ?= srcd/sourced-ui
# Docker registry where the docker image should be pushed to.
@@ -21,6 +28,29 @@ DOCKER_PASSWORD ?=
# will cause make docker-push to also push the latest tag.
DOCKER_PUSH_LATEST ?=
+# Tools
+STAT := stat -c
+
+
+# Set the user as root inside of the container to have permissions to change
+# internal `superset` UID and GUID, and make it match the host user.
+# It will grant write access to the data from the volumes,
+# inside of the container and on host file system (see #221)
+LOGIN_USER := 0
+ifeq ($(shell uname),Darwin)
+ STAT := stat -f
+ # In OSX there's no problem if the host user and the internal user are
+ # different when reading and accessing data trough docker volumes.
+ LOGIN_USER := superset
+endif
+
+SOURCED_UI_ABS_PATH := $(shell pwd)
+LOCAL_USER := $(shell $(STAT) "%u" superset/superset)
+export SOURCED_UI_ABS_PATH
+export LOCAL_USER
+export LOGIN_USER
+
+
# Build information
VERSION ?= latest
# Travis CI
@@ -33,9 +63,13 @@ IS_RELEASE := $(shell echo $(VERSION) | grep -q -E '^v[[:digit:]]+\.[[:digit:]]+
all: build
-# Copy src-d files in the superset repository
+# Clean, and copy src-d files in the superset repository
.PHONY: patch
-patch: clean
+patch: clean apply-patch
+
+# Copy src-d files in the superset repository
+.PHONY: apply-patch
+apply-patch:
cp -r $(PATCH_SOURCE_DIR)/* $(SUPERSET_DIR)/
# Copy src-d files in the superset repository using symlinks. it's useful for development.
@@ -49,6 +83,30 @@ patch-dev: clean
done; \
ln -s "$(PWD)/$(PATCH_SOURCE_DIR)/superset/superset_config_dev.py" "$(SUPERSET_DIR)/superset_config.py"; \
+# Start a watcher that will run 'make apply-patch' automatically when 'srcd' changes
+# It will require either inotify or fswatch. More info in CONTRIBUTING.md
+.PHONY: watch
+watch:
+ @DIRECTORY_TO_OBSERVE=$(PATCH_SOURCE_DIR) bash watcher
+
+# Writes the proper `docker-compose.override.yml` as the sourced global override file
+.PHONY: set-override
+set-override: $(OVERRIDE_BACKUP_PATH)
+ @awk '{ system("echo \""$$0"\"") }' $(OVERRIDE_TEMPLATE_PATH) > $(OVERRIDE_OUTPUT_PATH)
+
+# Creates a backup of the sourced global override file if it exists
+$(OVERRIDE_BACKUP_PATH):
+ @mkdir -p ~/.sourced/compose-files/__active__
+ @if [ -f "$(OVERRIDE_OUTPUT_PATH)" ]; then \
+ cp $(OVERRIDE_OUTPUT_PATH) $(OVERRIDE_BACKUP_PATH); \
+ else \
+ echo "\033[33mno docker-compose.override.yml to backup\033[0m"; \
+ fi;
+
+# Prepares the development enviroment with hot reloading
+.PHONY: dev-prepare
+dev-prepare: set-override watch
+
# Create docker image
.PHONY: patch
build: patch
@@ -92,6 +150,12 @@ docker-push-latest-release:
clean:
rm -f "$(SUPERSET_DIR)/superset_config.py"
git clean -fd $(SUPERSET_DIR)
+ rm -f $(OVERRIDE_OUTPUT_PATH)
+ @if [ -f "$(OVERRIDE_BACKUP_PATH)" ]; then \
+ mv $(OVERRIDE_BACKUP_PATH) $(OVERRIDE_OUTPUT_PATH); \
+ else \
+ echo "\033[33mno docker-compose.override.yml.bak to restore\033[0m"; \
+ fi;
# Add superset upstream remote if doesn't exists
.PHONY: remote-add
diff --git a/srcd/contrib/docker/docker-compose.override.yml b/srcd/contrib/docker/docker-compose.override.yml
new file mode 100644
index 00000000..15a2855c
--- /dev/null
+++ b/srcd/contrib/docker/docker-compose.override.yml
@@ -0,0 +1,13 @@
+version: '3.2'
+services:
+ sourced-ui:
+ user: ${LOGIN_USER:-superset}:${LOGIN_USER:-superset}
+ volumes:
+ - ${SOURCED_UI_ABS_PATH}/superset/superset:/home/superset/superset
+ - ${SOURCED_UI_ABS_PATH}/superset/dashboards:/home/superset/dashboards
+ - ${SOURCED_UI_ABS_PATH}/superset/contrib/docker/superset_config.py:/home/superset/superset/superset_config.py
+ - ${SOURCED_UI_ABS_PATH}/superset/contrib/docker/bootstrap.py:/home/superset/bootstrap.py
+ - ${SOURCED_UI_ABS_PATH}/superset/contrib/docker/docker-entrypoint.sh:/entrypoint.sh
+ environment:
+ SUPERSET_ENV: development
+ LOCAL_USER: ${LOCAL_USER:-}
diff --git a/superset/contrib/docker/docker-entrypoint.sh b/superset/contrib/docker/docker-entrypoint.sh
index 6e708cef..17ad1155 100755
--- a/superset/contrib/docker/docker-entrypoint.sh
+++ b/superset/contrib/docker/docker-entrypoint.sh
@@ -17,6 +17,19 @@
#
set -ex
+if [ "`whoami`" = "root" ] && [ -n "$LOCAL_USER" ]; then
+
+ # Change the UID and GUID of 'superset' user (matching host user).
+ # It will grant write access to the data from the volumes,
+ # inside of the container and on host file system (see #221)
+ find . -group superset -exec chgrp $LOCAL_USER '{}' \;
+ groupmod -g $LOCAL_USER superset
+ usermod -u $LOCAL_USER superset
+
+ su -c "/entrypoint.sh" superset
+ exit $?
+fi
+
if [ "$SUPERSET_NO_DB_INIT" != "true" ]; then
python bootstrap.py
fi
@@ -25,10 +38,13 @@ if [ "$#" -ne 0 ]; then
exec "$@"
elif [ "$SUPERSET_ENV" = "development" ]; then
celery worker --app=superset.sql_lab:celery_app --pool=gevent -Ofair &
- # needed by superset runserver
+ # In development mode, the UI will be served by `webpack-dev-server` instead of by `Flask`
+ # `webpack-dev-server` will serve the UI from the port 8088, and it will proxy
+ # non-asset requests to `Flask`, wich is listening at the port 8081
+ # Doing so, updates to asset sources will be reflected in-browser without a refresh.
(cd superset/assets/ && npm ci)
- (cd superset/assets/ && npm run dev) &
- FLASK_ENV=development FLASK_APP=superset:app flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0
+ (cd superset/assets/ && npm run dev-server -- --host=0.0.0.0 --port=8088 --supersetPort=8081) &
+ FLASK_ENV=development FLASK_APP=superset:app flask run -p 8081 --with-threads --reload --debugger --host=0.0.0.0
elif [ "$SUPERSET_ENV" = "production" ]; then
celery worker --app=superset.sql_lab:celery_app --pool=gevent -Ofair &
gunicorn --bind 0.0.0.0:8088 \
diff --git a/watcher b/watcher
new file mode 100755
index 00000000..6d6bd5b6
--- /dev/null
+++ b/watcher
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+trap ctrl_c SIGINT
+
+function ctrl_c {
+ make --no-print-directory clean && \
+ echo -e "\033[1;92mPatch cleaned\033[0m" || \
+ echo -e "\033[1;33mCould not clean patch\033[0m"
+ exit $?
+}
+
+function watch_inotify {
+ inotifywait --recursive \
+ --event modify,move,create,delete \
+ $DIRECTORY_TO_OBSERVE
+}
+
+function watch_fswatch {
+ fswatch --recursive --one-event \
+ --event Created --event Updated --event Removed \
+ ${DIRECTORY_TO_OBSERVE}
+}
+
+
+if command -v inotifywait > /dev/null 2>&1; then
+ whichWatcher=inotify
+ function watcher { watch_inotify; }
+elif command -v fswatch > /dev/null 2>&1; then
+ whichWatcher=fswatch
+ function watcher { watch_fswatch; }
+else
+ echo -e "\033[1;31m[ERROR] No watcher available\033[0m"
+ echo "Neither 'inotify' nor 'fswatch' are available."
+ echo "You can follow 'CONTRIBUTING.md' guidelines to install one of them,"
+ echo ""
+ echo -e "\033[1;33m[alternative]\033[0m follow these steps:"
+ echo "1. run 'make set-override' first,"
+ echo "2. then 'make patch' every time you change a file in 'srcd' dir"
+ echo "3. and 'make clean' once you finish developing"
+ echo ""
+ exit 1
+fi
+
+make --no-print-directory apply-patch
+echo -e "\033[1;92mWatching for changes in 'srcd'; using '${whichWatcher}' ...\033[0m"
+while watcher; do
+ make --no-print-directory apply-patch
+done