Merge pull request #118 from uxbox/devenv-improvements

Docker/Devenv Improvements
This commit is contained in:
Andrey Antukh 2019-09-21 15:11:13 +02:00 committed by GitHub
commit c9d7de4022
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 228 additions and 471 deletions

View file

@ -30,25 +30,27 @@ editable in many other vector tools and easy to use on the web.
### Introduction ### ### Introduction ###
The development environment consists in a docker container that mounts The main development environment consists in a docker compose
your local copy of the uxbox souce code directory tree and executes a configuration that starts the external services and the development
tmux inside the container in order to facilitate execute multiple container (called **devenv**).
processes inside.
We use tmux script in order to multiplex the signle terminal and run
both the backend and frontend in the same container.
### System requirements ### ### System requirements ###
You should have `docker` installed in your system in order to set up You should have `docker` and `docker-compose` installed in your system
properly the uxbox development enviroment. in order to set up properly the uxbox development enviroment.
In debian like linux distributions you can install it executing: In debian like linux distributions you can install it executing:
```bash ```bash
sudo apt-get install docker sudo apt-get install docker docker-compose
``` ```
### Start the docker container ### ### Start the devenv ###
**Requires a minimum knowledge of tmux usage in order to use that **Requires a minimum knowledge of tmux usage in order to use that
development environment.** development environment.**
@ -61,10 +63,9 @@ For start it, staying in this repository, execute:
This will do the following: This will do the following:
- Build the image if it is not done before. - Build the images if it is not done before.
- Download all repositories if them are not downloaded previously. - Starts all the containers in the background.
- Start a container with predefined tmux layout. - Attaches to the **devenv** container and executes the tmux session.
- Start all needed processes such as gulp and figwheel.
### First steps with tmux ### ### First steps with tmux ###
@ -82,36 +83,37 @@ current window.
#### UI #### #### UI ####
The UI related tasks starts automatically so you do not need do anything. The The UI related tasks starts automatically so you do not need do
**window 0** and **window 1** are used for the UI related environment. anything. The **window 0** and **window 1** are used for the UI
related environment.
#### Backend #### #### Backend ####
The backend related environment is located in the **window 2**, and you can go The backend related environment is located in the **window 2**, and
directly to it using `ctrl+b 2` shortcut. you can go directly to it using `ctrl+b 2` shortcut.
By default this tasks are performed: By default the clojure repl will be executed, waiting you to run
commands or start the http server.
- Start postgresql.
- Load initial fixtures into the database.
The repl should be started automatically, if not, you can execute:
```bash
clojure -Adev:repl
```
Then use `(start)` to start all the environment, `(stop)` for stoping Then use `(start)` to start all the environment, `(stop)` for stoping
it and `(reset)` for restart with code reloading. If some exception is it and `(reset)` for restart with code reloading. If some exception is
raised when code is reloaded, just use `(refresh)` in order to finish raised when code is reloaded, just use `(repl/refresh)` in order to finish
correctly the code swaping and later use `(reset)` again. correctly the code swaping and later use `(reset)` again.
If this is your first run, you maybe want to load fixtures first. Then
you can done this in two ways:
- In the same repl, require the `uxbox.fixtures` namespace and execute
`(uxbox.fixtures/-main [])`.
- Stop the repl with `Ctrl+c` and then execute `clojure -Adev -m
uxbox.fixtures`; then start the repl again with `clojure -Adev:repl`.
## Production (Docker) ## Production (Docker)
Docker is also used to build release images for backend and Docker is also used to build release images for backend and
frontend. Use the helper script `manage.sh` to build the images. You frontend. Use the helper script `manage.sh` to build the images. You
can run locally UXBOX through a docker-compose or by manually running can run locally UXBOX through a docker-compose or by manually running
the containers. the containers.
@ -129,35 +131,16 @@ uploads, etc). The docker daemon will store that data within the
docker directory `/var/lib/docker/volumes/...`. That means your data docker directory `/var/lib/docker/volumes/...`. That means your data
is saved even if the container crashes, is stopped or deleted. is saved even if the container crashes, is stopped or deleted.
To make your data persistent to upgrading and get access for backups The default production docker-compose already handles it for you,
is using named docker volume or mount a host folder. To achieve this but if you. So check the `docker/docker-compose.yml` file.
you need one volume for your database container.
Database:
- `/var/lib/postgresql/data` PostgreSQL Data
```console
$ docker run -d \
-v db:/var/lib/postgresql/data \
postgresql
```
You also need to persist the UXBOX backend public resources (media and
assets) to not lose images uploaded and allow the frontend to expose
assets.
- `/srv/uxbox/resources/public` UXBOX backend public resources
```console
$ docker run -d \
-v db:/srv/uxbox/resources/public \
monogramm/docker-uxbox-backend
```
### Auto configuration via environment variables ### Auto configuration via environment variables
The following environment variables are also honored for configuring The following environment variables are also honored for configuring
your UXBOX instance: your UXBOX instance:
#### Frontend #### Frontend
**Only available at build time!** **Only available at build time!**
@ -205,6 +188,7 @@ Available at runtime:
variables or the backend might try to interpret the values as symbols variables or the backend might try to interpret the values as symbols
and have weird issues. and have weird issues.
## Collections import ## Collections import
You can easily import icons and images as global stores with the You can easily import icons and images as global stores with the
@ -243,6 +227,7 @@ clojure -Adev -m uxbox.cli.collimp ../media/config.edn
Take a look at the `sample_media` directory for a sample configuration. Take a look at the `sample_media` directory for a sample configuration.
## Contributing ## ## Contributing ##
**Open to you!** **Open to you!**
@ -253,6 +238,7 @@ and improve UXBOX. All your awesome ideas and code are welcome!
Please refer to the [Contributing Guide](./CONTRIBUTING.md) Please refer to the [Contributing Guide](./CONTRIBUTING.md)
## License ## ## License ##
``` ```

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -x set -xe
sudo pg_ctlcluster 11 main start;
clj -Adev -m uxbox.tests.main clojure -Adev -m uxbox.tests.main;

View file

@ -126,7 +126,7 @@
(s/keys :req-un [::id])) (s/keys :req-un [::id]))
(s/def ::retrieve-page-history|query (s/def ::retrieve-page-history|query
(s/keys :req-un [::max (s/keys :opt-un [::max
::since ::since
::pinned])) ::pinned]))

View file

@ -20,7 +20,7 @@
(s/def ::id ::us/uuid) (s/def ::id ::us/uuid)
(s/def ::name string?) (s/def ::name string?)
(s/def ::version (s/and int? pos?)) (s/def ::version int?)
;; --- List Projects ;; --- List Projects

View file

@ -7,6 +7,7 @@
(ns uxbox.http.middleware (ns uxbox.http.middleware
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[clojure.java.io :as io]
[cuerdas.core :as str] [cuerdas.core :as str]
[promesa.core :as p] [promesa.core :as p]
[reitit.ring :as rr] [reitit.ring :as rr]
@ -208,7 +209,7 @@
(def format-response-middleware (def format-response-middleware
(letfn [(process-response [{:keys [body] :as rsp}] (letfn [(process-response [{:keys [body] :as rsp}]
(if body (if (coll? body)
(let [body (t/encode body {:type :json-verbose})] (let [body (t/encode body {:type :json-verbose})]
(-> rsp (-> rsp
(assoc :body body) (assoc :body body)
@ -226,11 +227,24 @@
(letfn [(get-content-type [request] (letfn [(get-content-type [request]
(or (:content-type request) (or (:content-type request)
(get (:headers request) "content-type"))) (get (:headers request) "content-type")))
(slurp-bytes [body]
(with-open [input (io/input-stream body)
output (java.io.ByteArrayOutputStream. (.available input))]
(io/copy input output)
(.toByteArray output)))
(parse-body [body]
(let [^bytes body (slurp-bytes body)]
(when (pos? (alength body))
(t/decode body))))
(process-request [request] (process-request [request]
(let [ctype (get-content-type request)] (let [ctype (get-content-type request)]
(if (= "application/transit+json" ctype) (if (= "application/transit+json" ctype)
(try (try
(assoc request :body-params (t/decode (:body request))) (let [body (parse-body (:body request))]
(assoc request :body-params body))
(catch Exception e (catch Exception e
(ex/raise :type :parse (ex/raise :type :parse
:message "Unable to parse transit from request body." :message "Unable to parse transit from request body."
@ -243,11 +257,11 @@
([request] ([request]
(handler (process-request request))) (handler (process-request request)))
([request respond raise] ([request respond raise]
(try (let [^HttpInput body (:body request)]
(let [request (process-request request)] (try
(handler request respond raise)) (handler (process-request request) respond raise)
(catch Exception e (catch Exception e
(raise e))))))})) (raise e)))))))}))
(def middleware (def middleware
[cors-middleware [cors-middleware

View file

@ -49,17 +49,28 @@
;; --- High-Level Api ;; --- High-Level Api
;; TODO: check performance of different options
(defn decode (defn decode
([data] ([data]
(decode data nil)) (decode data nil))
([data opts] ([data opts]
(with-open [input (io/input-stream data)] (cond
(read! (reader input opts))))) (string? data)
(decode (.getBytes data "UTF-8") opts)
(bytes? data)
(with-open [input (ByteArrayInputStream. data)]
(read! (reader input opts)))
:else
(with-open [input (io/input-stream data)]
(read! (reader input opts))))))
(defn encode (defn encode
([data] (^bytes [data]
(encode data nil)) (encode data nil))
([data opts] (^bytes [data opts]
(with-open [out (ByteArrayOutputStream.)] (with-open [out (ByteArrayOutputStream.)]
(let [w (writer out opts)] (let [w (writer out opts)]
(write! w data) (write! w data)

View file

@ -43,7 +43,7 @@
(let [uri (str th/+base-url+ "/api/projects/" (:id proj)) (let [uri (str th/+base-url+ "/api/projects/" (:id proj))
params {:body (assoc proj :name "proj2")} params {:body (assoc proj :name "proj2")}
[status data] (th/http-put user uri params)] [status data] (th/http-put user uri params)]
(prn "RESPONSE:" status data) ;; (prn "RESPONSE:" status data)
(t/is (= 200 status)) (t/is (= 200 status))
(t/is (= (:user data) (:id user))) (t/is (= (:user data) (:id user)))
(t/is (= (:name data) "proj2"))))))) (t/is (= (:name data) "proj2")))))))
@ -55,6 +55,7 @@
(th/with-server {:handler @http/app} (th/with-server {:handler @http/app}
(let [uri (str th/+base-url+ "/api/projects/" (:id proj)) (let [uri (str th/+base-url+ "/api/projects/" (:id proj))
[status data] (th/http-delete user uri)] [status data] (th/http-delete user uri)]
;; (prn "RESPONSE:" status data)
(t/is (= 204 status)) (t/is (= 204 status))
(let [sqlv ["SELECT * FROM projects WHERE \"user\"=? AND deleted_at is null" (let [sqlv ["SELECT * FROM projects WHERE \"user\"=? AND deleted_at is null"
(:id user)] (:id user)]

View file

@ -2,6 +2,7 @@ FROM ubuntu:bionic
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>" LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ARG EXTERNAL_UID=1000 ARG EXTERNAL_UID=1000
ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=v10.16.3 \ ENV NODE_VERSION=v10.16.3 \
CLOJURE_VERSION=1.10.1.469 \ CLOJURE_VERSION=1.10.1.469 \
@ -37,7 +38,6 @@ RUN set -ex; \
echo "deb http://repos.azulsystems.com/ubuntu stable main" >> /etc/apt/sources.list.d/zulu.list; \ echo "deb http://repos.azulsystems.com/ubuntu stable main" >> /etc/apt/sources.list.d/zulu.list; \
echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/postgresql.list;
ARG DEBIAN_FRONTEND=noninteractive
RUN set -ex; \ RUN set -ex; \
apt-get -qq update; \ apt-get -qq update; \
@ -86,9 +86,10 @@ RUN set -ex; \
COPY files/bashrc /home/uxbox/.bashrc COPY files/bashrc /home/uxbox/.bashrc
COPY files/zshrc /home/uxbox/.zshrc COPY files/zshrc /home/uxbox/.zshrc
COPY files/vimrc /home/uxbox/.vimrc COPY files/vimrc /home/uxbox/.vimrc
COPY files/start.sh /home/uxbox/start-tmux.sh COPY files/start.sh /home/uxbox/start.sh
COPY files/tmux.conf /home/uxbox/.tmux.conf COPY files/tmux.conf /home/uxbox/.tmux.conf
COPY files/entrypoint.sh /home/uxbox/ COPY files/entrypoint.sh /home/uxbox/
COPY files/init.sh /home/uxbox/
ENTRYPOINT ["zsh", "/home/uxbox/entrypoint.sh"] ENTRYPOINT ["zsh", "/home/uxbox/entrypoint.sh"]
CMD ["/home/uxbox/start-tmux.sh"] CMD ["/home/uxbox/start.sh"]

View file

@ -0,0 +1,55 @@
version: '3'
networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.177.09.0/24
volumes:
postgres_data:
user_data:
services:
uxbox:
privileged: true
build:
context: ./
hostname: 'uxbox-devenv'
container_name: 'uxbox-devenv'
command: "/home/uxbox/init.sh"
stop_signal: SIGINT
depends_on:
- postgres
volumes:
- "user_data:/home/uxbox/local"
- "${PWD}:/home/uxbox/uxbox"
- "${HOME}/.m2:/home/uxbox/.m2"
- "${HOME}/.gitconfig:/home/uxbox/.gitconfig"
ports:
- 3449:3449
- 6060:6060
environment:
- UXBOX_HTTP_SERVER_DEBUG=false
- UXBOX_DATABASE_URI="jdbc:postgresql://postgres/uxbox"
- UXBOX_DATABASE_USERNAME="uxbox"
- UXBOX_DATABASE_PASSWORD="uxbox_postgres_password"
postgres:
image: postgres:11
hostname: 'uxbox-devenv-postgres'
container_name: 'uxbox-devenv-postgres'
restart: always
stop_signal: SIGINT
ports:
- 5432:5432
environment:
- POSTGRES_INITDB_ARGS="--data-checksums"
- POSTGRES_DB=uxbox
- POSTGRES_USER=uxbox
- POSTGRES_PASSWORD=uxbox_postgres_password
volumes:
- postgres_data:/var/lib/postgresql/data

View file

@ -1,5 +1,3 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
set -ex set -ex
sudo pg_ctlcluster 11 main start
exec "$@" exec "$@"

8
docker/devenv/files/init.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env zsh
set -e;
echo "[init.sh] Setting up local permissions."
sudo chown -R uxbox /home/uxbox/local
echo "[init.sh] Ready!"
tail -f /dev/null

View file

@ -10,7 +10,7 @@ tmux send-keys -t uxbox 'clojure -Adev tools.clj figwheel' enter
tmux new-window -t uxbox:2 -n 'backend' tmux new-window -t uxbox:2 -n 'backend'
tmux select-window -t uxbox:2 tmux select-window -t uxbox:2
tmux send-keys -t uxbox 'cd uxbox/backend' enter C-l tmux send-keys -t uxbox 'cd uxbox/backend' enter C-l
tmux send-keys -t uxbox 'clojure -Adev -m uxbox.fixtures' enter C-l # tmux send-keys -t uxbox 'clojure -Adev -m uxbox.fixtures' enter C-l
tmux send-keys -t uxbox 'clojure -Adev:repl' enter tmux send-keys -t uxbox 'clojure -Adev:repl' enter
tmux rename-window -t uxbox:0 'gulp' tmux rename-window -t uxbox:0 'gulp'

View file

@ -47,7 +47,7 @@ setopt NOBEEP
setopt INC_APPEND_HISTORY setopt INC_APPEND_HISTORY
export HISTSIZE=100000 export HISTSIZE=100000
export SAVEHIST=100000 export SAVEHIST=100000
export HISTFILE=~/.zhistory export HISTFILE=~/local/.zhistory
setopt hist_ignore_all_dups setopt hist_ignore_all_dups
setopt hist_ignore_space setopt hist_ignore_space

View file

@ -4,7 +4,5 @@ source ~/.bashrc
set -ex; set -ex;
npm ci npm ci
clojure -Adev tools.clj build:tests
clojure -Adev tools.clj build-tests
node ./target/tests/main node ./target/tests/main

View file

@ -179,109 +179,6 @@
(t/is (= result expected)) (t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes]))))) (t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move shape outside of group
(t/deftest drop-shape-test5
(let [initial {:workspace {:selected #{1}}
:pages {1 {:id 1 :shapes [1 3]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :group 1}
3 {:id 3 :page 1}}}
expected {:workspace {:selected #{}}
:pages {1 {:id 1, :shapes [3 2]}},
:shapes {2 {:id 2, :page 1},
3 {:id 3, :page 1}}}
result (impl/drop-shape initial 2 3 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move group inside group
(t/deftest drop-shape-test6
(let [initial {:pages {1 {:id 1 :shapes [1 2]}}
:shapes {1 {:id 1 :page 1 :type :group :items [3]}
2 {:id 2 :page 1 :type :group :items [4]}
3 {:id 3 :page 1 :group 1}
4 {:id 4 :page 1 :group 2}}}
expected {:pages {1 {:id 1, :shapes [1]}},
:shapes {1 {:id 1, :page 1, :type :group, :items [3 2]},
2 {:id 2, :page 1, :type :group, :items [4], :group 1},
3 {:id 3, :page 1, :group 1},
4 {:id 4, :page 1, :group 2}}}
result (impl/drop-shape initial 2 3 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move group outside group
(t/deftest drop-shape-test7
(let [initial {:workspace {:selected #{}}
:pages {1 {:id 1 :shapes [1 3]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :group 1 :type :group :items [4]}
3 {:id 3 :page 1}
4 {:id 4 :page 1 :group 2}}}
expected {:workspace {:selected #{}},
:pages {1 {:id 1, :shapes [2 3]}},
:shapes {2 {:id 2, :page 1, :type :group, :items [4]},
3 {:id 3, :page 1},
4 {:id 4, :page 1, :group 2}}}
result (impl/drop-shape initial 2 1 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move shape to neested group
(t/deftest drop-shape-test8
(let [initial {:pages {1 {:id 1 :shapes [1 5 6]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :type :group :group 1 :items [3 4]}
3 {:id 3 :page 1 :group 2}
4 {:id 4 :page 1 :group 2}
5 {:id 5 :page 1}
6 {:id 6 :page 1}}}
expected {:pages {1 {:id 1, :shapes [1 5]}},
:shapes {1 {:id 1, :page 1, :type :group, :items [2]},
2 {:id 2, :page 1, :type :group, :group 1, :items [3 4 6]},
3 {:id 3, :page 1, :group 2},
4 {:id 4, :page 1, :group 2},
5 {:id 5, :page 1},
6 {:id 6, :page 1, :group 2}}}
result (impl/drop-shape initial 6 4 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;; drop shape: move shape to neested group
(t/deftest drop-shape-test9
(let [initial {:pages {1 {:id 1 :shapes [1]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2 5 6]}
2 {:id 2 :page 1 :type :group :group 1 :items [3 4]}
3 {:id 3 :page 1 :group 2}
4 {:id 4 :page 1 :group 2}
5 {:id 5 :page 1 :group 1}
6 {:id 6 :page 1 :group 1}}}
expected {:pages {1 {:id 1, :shapes [1]}},
:shapes {1 {:id 1, :page 1, :type :group, :items [2 5]},
2 {:id 2, :page 1, :type :group, :group 1, :items [3 4 6]},
3 {:id 3, :page 1, :group 2},
4 {:id 4, :page 1, :group 2},
5 {:id 5, :page 1, :group 1},
6 {:id 6, :page 1, :group 2}}}
result (impl/drop-shape initial 6 4 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delete Shape ;; Delete Shape
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -306,244 +203,3 @@
;; (pprint expected) ;; (pprint expected)
;; (pprint result) ;; (pprint result)
(t/is (= result expected)))) (t/is (= result expected))))
;; delete shape: delete from group
(t/deftest delete-shape-test2
(let [initial {:workspace {:selected #{}}
:pages {1 {:id 1 :shapes [1 3 4]}}
:shapes {1 {:id 1 :page 1
:type :group
:items [2]}
2 {:id 2 :page 1 :group 1}
3 {:id 3 :page 1}
4 {:id 4 :page 1}}}
shape (get-in initial [:shapes 2])
expected {:workspace {:selected #{}}
:pages {1 {:id 1 :shapes [3 4]}}
:shapes {3 {:id 3 :page 1}
4 {:id 4 :page 1}}}
result (impl/dissoc-shape initial shape)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Group Shapes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; group a shape
(t/deftest group-shapes-1
(let [initial {:pages {1 {:id 1 :shapes [1 2 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}
3 {:id 3 :page 1}}}
expected {:pages {1 {:id 1 :shapes [1 4 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 4}
3 {:id 3 :page 1}
4 {:type :group :name "Group-1" :items [2] :id 4 :page 1}}
:workspace {:selected #{4}}}]
(with-redefs [uxbox.util.uuid/random (constantly 4)]
(let [result (impl/group-shapes initial [2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))))
;; group two shapes
(t/deftest group-shapes-2
(let [initial {:pages {1 {:id 1 :shapes [1 2 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}
3 {:id 3 :page 1}}}
expected {:pages {1 {:id 1 :shapes [1 4]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 4}
3 {:id 3 :page 1 :group 4}
4 {:type :group :name "Group-1" :items [2 3] :id 4 :page 1}}
:workspace {:selected #{4}}}]
(with-redefs [uxbox.util.uuid/random (constantly 4)]
(let [result (impl/group-shapes initial [2 3] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))))
;; group group
(t/deftest group-shapes-3
(let [initial {:pages {1 {:id 1 :shapes [1 2 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}
3 {:id 3 :page 1 :type :group}}}
expected {:pages {1 {:id 1 :shapes [1 4]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 4}
3 {:id 3 :page 1 :type :group :group 4}
4 {:type :group :name "Group-1" :items [2 3] :id 4 :page 1}}
:workspace {:selected #{4}}}]
(with-redefs [uxbox.util.uuid/random (constantly 4)]
(let [result (impl/group-shapes initial [2 3] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))))
;; group shapes inside a group
(t/deftest group-shapes-4
(let [initial {:pages {1 {:id 1 :shapes [1 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group}}}
expected {:pages {1 {:id 1 :shapes [1 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 4}
3 {:id 3 :page 1 :type :group :items [4]}
4 {:type :group
:name "Group-1"
:items [2]
:id 4
:page 1
:group 3}}
:workspace {:selected #{4}}}]
(with-redefs [uxbox.util.uuid/random (constantly 4)]
(let [result (impl/group-shapes initial [2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))))
;; group shapes in multiple groups
(t/deftest group-shapes-5
(let [initial {:pages {1 {:id 1 :shapes [3 4]}}
:shapes {1 {:id 1 :page 1 :group 4}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group :items [2]}
4 {:id 4 :page 1 :type :group :imtes [3]}}}
expected (-> initial
(assoc-in [:workspace :selected] #{5})
(assoc-in [:pages 1 :shapes] [5])
(assoc-in [:shapes 1 :group] 5)
(assoc-in [:shapes 2 :group] 5)
(assoc-in [:shapes 5] {:type :group :name "Group-1"
:items [1 2] :id 5 :page 1})
(update-in [:shapes] dissoc 3)
(update-in [:shapes] dissoc 4))]
(with-redefs [uxbox.util.uuid/random (constantly 5)]
(let [result (impl/group-shapes initial [1 2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Degroups
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; degroup a single group
(t/deftest degroup-shapes-1-0
(let [initial {:pages {1 {:id 1 :shapes [3]}}
:shapes {1 {:id 1 :page 1 :group 3}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group :items [1 2]}}}
expected {:workspace {:selected #{1 2}}
:pages {1 {:id 1 :shapes [1 2]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}}}]
(let [result (impl/degroup-shapes initial [3] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected)))))
;; degroup single shape from group
(t/deftest degroup-shapes-1-1
(let [initial {:pages {1 {:id 1 :shapes [3]}}
:shapes {1 {:id 1 :page 1 :group 3}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group :items [1 2]}}}
expected {:workspace {:selected #{1}}
:pages {1 {:id 1 :shapes [1 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group :items [2]}}}
result (impl/degroup-shapes initial [1] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;; degroup all shapes from group
(t/deftest degroup-shapes-1-2
(let [initial {:pages {1 {:id 1 :shapes [3]}}
:shapes {1 {:id 1 :page 1 :group 3}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :type :group :items [1 2]}}}
expected {:workspace {:selected #{1 2}}
:pages {1 {:id 1 :shapes [1 2]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}}}
result (impl/degroup-shapes initial [1 2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;; degroup all shapes from neested group
(t/deftest degroup-shapes-1-3
(let [initial {:pages {1 {:id 1 :shapes [4]}}
:shapes {1 {:id 1 :page 1 :group 3}
2 {:id 2 :page 1 :group 3}
3 {:id 3 :page 1 :group 4 :type :group :items [1 2]}
4 {:id 4 :page 1 :type :group :items [3]}}}
expected {:workspace {:selected #{1 2}}
:pages {1 {:id 1 :shapes [4]}}
:shapes {1 {:id 1 :page 1 :group 4}
2 {:id 2 :page 1 :group 4}
4 {:id 4 :page 1 :type :group :items [1 2]}}}
result (impl/degroup-shapes initial [1 2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))))
;; degroup group inside a group
(t/deftest degroup-shapes-2
(let [initial {:pages {1 {:id 1 :shapes [1]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :type :group :items [3] :group 1}
3 {:id 3 :page 1 :group 2}}}
expected {:pages {1 {:id 1 :shapes [1]}}
:shapes {1 {:id 1 :page 1 :type :group :items [3]}
3 {:id 3 :page 1 :group 1}}
:workspace {:selected #{3}}}]
(let [result (impl/degroup-shapes initial [2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected)))))
;; degroup multiple groups not nested
(t/deftest degroup-shapes-3
(let [initial {:pages {1 {:id 1 :shapes [1 2]}}
:shapes {1 {:id 1 :page 1 :type :group :items [3]}
2 {:id 2 :page 1 :type :group :items [4]}
3 {:id 3 :page 1 :group 1}
4 {:id 4 :page 1 :group 2}}}
expected {:pages {1 {:id 1 :shapes [3 4]}}
:shapes {3 {:id 3 :page 1} 4 {:id 4 :page 1}}
:workspace {:selected #{4 3}}}]
(let [result (impl/degroup-shapes initial [1 2] 1)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected)))))

View file

@ -130,12 +130,9 @@
(task ["dbg-dist:main"]) (task ["dbg-dist:main"])
(task ["dbg-dist:worker"])) (task ["dbg-dist:worker"]))
;; --- Tests Tasks ;; --- Tests Tasks
(defmethod task "build-tests" (defmethod task "build:tests"
[& args] [& args]
(api/build (api/inputs "src" "test") (api/build (api/inputs "src" "test")
(assoc default-build-options (assoc default-build-options
@ -147,6 +144,30 @@
:output-dir "target/tests/main" :output-dir "target/tests/main"
:optimizations :none))) :optimizations :none)))
(defmethod task "watch:tests"
[args]
(println "Start watch loop...")
(letfn [(run-tests []
(let [{:keys [out err]} (shell/sh "node" "target/tests/main.js")]
(println out err)))
(start-watch []
(try
(api/watch (api/inputs "src" "test")
(assoc default-build-options
:main 'uxbox.tests.main
:watch-fn run-tests
:target :nodejs
:source-map true
:output-to "target/tests/main.js"
:output-dir "target/tests/main"
:optimizations :none))
(catch Exception e
(println "ERROR:" e)
(Thread/sleep 2000)
start-watch)))]
(trampoline start-watch)))
;; --- Figwheel Config & Tasks ;; --- Figwheel Config & Tasks
(def figwheel-builds (def figwheel-builds

100
manage.sh
View file

@ -2,55 +2,42 @@
set -e set -e
REV=`git log -n 1 --pretty=format:%h -- docker/` REV=`git log -n 1 --pretty=format:%h -- docker/`
IMGNAME="uxbox-devenv" IMGNAME="devenv_uxbox"
function kill-devenv-container {
echo "Cleaning development container $IMGNAME:$REV..."
docker ps -a -f name=$IMGNAME -q | xargs --no-run-if-empty docker kill
}
function remove-devenv-images { function remove-devenv-images {
echo "Clean old development image $IMGNAME..." echo "Clean old development image $IMGNAME..."
docker images $IMGNAME -q | awk '{print $3}' | xargs --no-run-if-empty docker rmi docker images $IMGNAME -q | awk '{print $3}' | xargs --no-run-if-empty docker rmi
} }
function build-devenv-image { function build-devenv {
echo "Building development image $IMGNAME:latest with UID $EXTERNAL_UID..."
local EXTERNAL_UID=${1:-$(id -u)} local EXTERNAL_UID=${1:-$(id -u)}
echo "Building development image $IMGNAME:$REV with UID $EXTERNAL_UID..." docker-compose -f docker/devenv/docker-compose.yaml \
docker build --rm=true \ build --build-arg EXTERNAL_UID=$EXTERNAL_UID --force-rm;
-t $IMGNAME:$REV \
-t $IMGNAME:latest \
--build-arg EXTERNAL_UID=$EXTERNAL_UID \
--label="io.uxbox.devenv" \
docker/devenv
} }
function build-devenv-image-if-not-exists { function build-devenv-if-not-exists {
if [[ ! $(docker images $IMGNAME:$REV -q) ]]; then if [[ ! $(docker images $IMGNAME:latest -q) ]]; then
build-devenv-image $@ build-devenv $@
fi fi
} }
function start-devenv {
build-devenv-if-not-exists $@;
docker-compose -f docker/devenv/docker-compose.yaml up -d;
}
function stop-devenv {
docker-compose -f docker/devenv/docker-compose.yaml stop -t 2;
}
function run-devenv { function run-devenv {
kill-devenv-container; if [[ ! $(docker ps -f "name=uxbox-devenv" -q) ]]; then
build-devenv-image-if-not-exists $@; start-devenv
fi
mkdir -p $HOME/.m2 docker exec -ti uxbox-devenv /home/uxbox/start.sh;
rm -rf ./frontend/node_modules
mkdir -p \
./frontend/resources/public/css \
./frontend/resources/public/view/css
CONTAINER=$IMGNAME:latest
echo "Running development image $CONTAINER..."
docker run --rm -ti \
-v `pwd`:/home/uxbox/uxbox \
-v $HOME/.m2:/home/uxbox/.m2 \
-v $HOME/.gitconfig:/home/uxbox/.gitconfig \
-p 3449:3449 -p 6060:6060 -p 9090:9090 \
--name "uxbox-devenv" \
$CONTAINER
} }
function run-all-tests { function run-all-tests {
@ -61,7 +48,7 @@ function run-all-tests {
} }
function run-frontend-tests { function run-frontend-tests {
build-devenv-image-if-not-exists $@; build-devenv-if-not-exists $@;
CONTAINER=$IMGNAME:latest CONTAINER=$IMGNAME:latest
@ -74,7 +61,7 @@ function run-frontend-tests {
} }
function run-backend-tests { function run-backend-tests {
build-devenv-image-if-not-exists $@; build-devenv-if-not-exists $@;
CONTAINER=$IMGNAME:latest CONTAINER=$IMGNAME:latest
@ -86,7 +73,7 @@ function run-backend-tests {
} }
function build-frontend-local { function build-frontend-local {
build-devenv-image-if-not-exists $@; build-devenv-if-not-exists $@;
mkdir -p $HOME/.m2 mkdir -p $HOME/.m2
rm -rf ./frontend/node_modules rm -rf ./frontend/node_modules
@ -158,6 +145,8 @@ function build-backend-image {
} }
function build-images { function build-images {
build-devenv-if-not-exists $@;
echo "Building frontend image ..." echo "Building frontend image ..."
build-frontend-image || exit 1; build-frontend-image || exit 1;
echo "Building frontend dbg image ..." echo "Building frontend dbg image ..."
@ -197,11 +186,17 @@ function usage {
echo "USAGE: $0 OPTION" echo "USAGE: $0 OPTION"
echo "Options:" echo "Options:"
echo "- clean Stop and clean up docker containers" echo "- clean Stop and clean up docker containers"
echo "- build-devenv-image Build docker container for development with tmux. Can specify external user id in parameter" echo ""
echo "- run-devenv Run (and build if necessary) development container (frontend at localhost:3449, backend at localhost:6060). Can specify external user id in parameter" echo "- build-devenv Build docker development oriented image; (can specify external user id in parameter)"
echo "- run-all-tests Execute unit tests for both backend and frontend. Can specify external user id in parameter" echo "- start-devenv Start the development oriented docker-compose service."
echo "- run-frontend-tests Execute unit tests for frontend only. Can specify external user id in parameter" echo "- stop-devenv Stops the development oriented docker-compose service."
echo "- run-backend-tests Execute unit tests for backend only. Can specify external user id in parameter" echo "- run-devenv Attaches to the running devenv container and starts development environment"
echo " based on tmux (frontend at localhost:3449, backend at localhost:6060)."
echo ""
echo "- run-all-tests Execute unit tests for both backend and frontend."
echo "- run-frontend-tests Execute unit tests for frontend only."
echo "- run-backend-tests Execute unit tests for backend only."
echo ""
echo "- build-images Build a 'release ready' docker images for both backend and frontend" echo "- build-images Build a 'release ready' docker images for both backend and frontend"
echo "- build-frontend-image Build a 'release ready' docker image for frontend (debug version)" echo "- build-frontend-image Build a 'release ready' docker image for frontend (debug version)"
echo "- build-frontend-dbg-image Build a debug docker image for frontend" echo "- build-frontend-dbg-image Build a debug docker image for frontend"
@ -213,15 +208,26 @@ function usage {
case $1 in case $1 in
clean) clean)
kill-devenv-container
remove-devenv-images remove-devenv-images
;; ;;
build-devenv-image)
build-devenv-image ${@:2} ## devenv related commands
build-devenv)
build-devenv ${@:2}
;;
start-devenv)
start-devenv ${@:2}
;; ;;
run-devenv) run-devenv)
run-devenv ${@:2} run-devenv ${@:2}
;; ;;
stop-devenv)
stop-devenv ${@:2}
;;
## testin related commands
run-all-tests) run-all-tests)
run-all-tests ${@:2} run-all-tests ${@:2}
;; ;;
@ -232,6 +238,8 @@ case $1 in
run-backend-tests ${@:2} run-backend-tests ${@:2}
;; ;;
# production related comands
build-images) build-images)
build-images build-images
;; ;;