mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 02:28:54 +08:00
Compare commits
17 Commits
0.19.0
...
1a9d1b308b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a9d1b308b | ||
|
|
014ecf145e | ||
|
|
0044d82b6f | ||
|
|
998e8d66f7 | ||
|
|
db3dbdf993 | ||
|
|
4e13a16e33 | ||
|
|
9c0103fd05 | ||
|
|
e4b117ffb9 | ||
|
|
6320f818cb | ||
|
|
3c456ef135 | ||
|
|
c713c713b7 | ||
|
|
babf0074c0 | ||
|
|
e71e6700c1 | ||
|
|
84e66b8167 | ||
|
|
9e84cf269e | ||
|
|
e897b4913b | ||
|
|
0646f58ca0 |
21
.github/workflows/main.yml
vendored
21
.github/workflows/main.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Deploy to GitHub Pages and create release
|
name: Create release
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to GitHub Pages and create release
|
name: Create release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -24,10 +24,11 @@ jobs:
|
|||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Create archive
|
- name: Create archives
|
||||||
run: |
|
run: |
|
||||||
cd dist
|
cd dist
|
||||||
zip -9 -r dist.zip . -x "js/*.map" -x "/*.map"
|
zip -9 -r ../dist.zip . -x "js/*.map" -x "/*.map"
|
||||||
|
zip -9 -r ../dist_map.zip .
|
||||||
|
|
||||||
- name: Create Release Notes
|
- name: Create Release Notes
|
||||||
run: |
|
run: |
|
||||||
@@ -39,16 +40,6 @@ jobs:
|
|||||||
- name: Create release
|
- name: Create release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
artifacts: "dist/dist.zip"
|
artifacts: "dist.zip,dist_map.zip"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
bodyFile: "CHANGELOG.md"
|
bodyFile: "CHANGELOG.md"
|
||||||
|
|
||||||
- name: Deploy 🚀
|
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
branch: build # The branch the action should deploy to.
|
|
||||||
folder: dist/ # The folder the action should deploy.
|
|
||||||
clean: true # Automatically remove deleted files from the deploy branch
|
|
||||||
clean-exclude: .nojekyll
|
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
|
|||||||
24
Dockerfile.test
Normal file
24
Dockerfile.test
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# An easy way to run tests locally without Nodejs installed:
|
||||||
|
#
|
||||||
|
# docker build -t sqliteviz/test -f Dockerfile.test .
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM node:12
|
||||||
|
|
||||||
|
RUN set -ex; \
|
||||||
|
apt update; \
|
||||||
|
apt install -y chromium firefox-esr; \
|
||||||
|
npm install -g npm@7
|
||||||
|
|
||||||
|
WORKDIR /tmp/build
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY lib lib
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN set -ex; \
|
||||||
|
sed -i 's/browsers: \[.*\],/browsers: ['"'FirefoxHeadlessTouch'"'],/' karma.conf.js
|
||||||
|
|
||||||
|
RUN npm run lint -- --no-fix && npm run test
|
||||||
@@ -18,10 +18,10 @@ With sqliteviz you can:
|
|||||||
https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4
|
https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
The latest release of sqliteviz is deployed on GitHub Pages at [lana-k.github.io/sqliteviz][6].
|
The latest release of sqliteviz is deployed on [sqliteviz.com/app][6].
|
||||||
|
|
||||||
## Wiki
|
## Wiki
|
||||||
For user documentation, check out sqliteviz [Wiki][7].
|
For user documentation, check out sqliteviz [documentation][7].
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
|
It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
|
||||||
@@ -34,8 +34,8 @@ It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4]
|
|||||||
[3]: https://github.com/plotly/react-chart-editor
|
[3]: https://github.com/plotly/react-chart-editor
|
||||||
[4]: https://github.com/sql-js/sql.js
|
[4]: https://github.com/sql-js/sql.js
|
||||||
[5]: https://github.com/vuejs/vue
|
[5]: https://github.com/vuejs/vue
|
||||||
[6]: https://lana-k.github.io/sqliteviz/
|
[6]: https://sqliteviz.com/app/
|
||||||
[7]: https://github.com/lana-k/sqliteviz/wiki
|
[7]: https://sqliteviz.com/docs
|
||||||
[8]: https://github.com/surmon-china/vue-codemirror#readme
|
[8]: https://github.com/surmon-china/vue-codemirror#readme
|
||||||
[9]: https://www.papaparse.com/
|
[9]: https://www.papaparse.com/
|
||||||
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM emscripten/emsdk:2.0.24
|
FROM emscripten/emsdk:3.0.1
|
||||||
|
|
||||||
WORKDIR /tmp/build
|
WORKDIR /tmp/build
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ SQLite [miscellaneous extensions][3] included:
|
|||||||
SQLite 3rd party extensions included:
|
SQLite 3rd party extensions included:
|
||||||
|
|
||||||
1. [pivot_vtab][5] -- a pivot virtual table
|
1. [pivot_vtab][5] -- a pivot virtual table
|
||||||
|
2. `pearson` correlation coefficient function extension from [sqlean][21]
|
||||||
|
(which is part of [squib][20])
|
||||||
|
|
||||||
To ease the step to have working clone locally, the build is committed into
|
To ease the step to have working clone locally, the build is committed into
|
||||||
the repository.
|
the repository.
|
||||||
@@ -99,3 +101,5 @@ described in [this message from SQLite Forum][12]:
|
|||||||
[17]: https://sqlite.org/contrib/
|
[17]: https://sqlite.org/contrib/
|
||||||
[18]: https://sqlite.org/contrib//download/extension-functions.c?get=25
|
[18]: https://sqlite.org/contrib//download/extension-functions.c?get=25
|
||||||
[19]: https://github.com/lana-k/sqliteviz/blob/master/tests/lib/database/sqliteExtensions.spec.js
|
[19]: https://github.com/lana-k/sqliteviz/blob/master/tests/lib/database/sqliteExtensions.spec.js
|
||||||
|
[20]: https://github.com/mrwilson/squib/blob/master/pearson.c
|
||||||
|
[21]: https://github.com/nalgeon/sqlean/blob/incubator/src/pearson.c
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
# SQLite WebAssembly build micro-benchmark
|
# SQLite WebAssembly build micro-benchmark
|
||||||
|
|
||||||
This directory contains a micro-benchmark for evaluating SQLite
|
This directory contains a micro-benchmark for evaluating SQLite WebAssembly
|
||||||
WebAssembly builds performance on typical SQL queries, run from
|
builds performance on read and write SQL queries, run from `make.sh` script. If
|
||||||
`make.sh` script. It can also serve as a smoke test.
|
the script has permission to `nice` processes and [Procpath][1] is installed,
|
||||||
|
e.g. it is run with `sudo -E env PATH=$PATH ./make.sh`, it'll `renice` all
|
||||||
|
processes running inside the benchmark containers. It can also serve as a smoke
|
||||||
|
test (e.g. for memory leaks).
|
||||||
|
|
||||||
The benchmark operates on a set of SQLite WebAssembly builds expected
|
The benchmark operates on a set of SQLite WebAssembly builds expected in
|
||||||
in `lib/build-$NAME` directories each containing `sql-wasm.js` and
|
`lib/build-$NAME` directories each containing `sql-wasm.js` and
|
||||||
`sql-wasm.wasm`. Then it creates a Docker image for each, and runs
|
`sql-wasm.wasm`. Then it creates a Docker image for each, and runs the
|
||||||
the benchmark in Firefox and Chromium using Karma in the container.
|
benchmark in Firefox and Chromium using Karma in the container.
|
||||||
|
|
||||||
After successful run, the benchmark result of each build is contained
|
After successful run, the benchmark produces the following per each build:
|
||||||
in `build-$NAME-result.json`. The JSON result files can be analysed
|
|
||||||
using `result-analysis.ipynb` Jupyter notebook.
|
- `build-$NAME-result.json`
|
||||||
|
- `build-$NAME.sqlite` (if Procpath is installed)
|
||||||
|
- `build-$NAME.svg` (if Procpath is installed)
|
||||||
|
|
||||||
|
These files can be analysed using `result-analysis.ipynb` Jupyter notebook.
|
||||||
|
The SVG is a chart with CPU and RSS usage of each test container (i.e. Chromium
|
||||||
|
run, then Firefox run per container).
|
||||||
|
|
||||||
|
[1]: https://pypi.org/project/Procpath/
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
cleanup () {
|
cleanup () {
|
||||||
rm -rf lib/dist $flag_file
|
rm -rf lib/dist "$renice_flag_file"
|
||||||
|
docker rm -f sqljs-benchmark-run 2> /dev/null || true
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
@@ -11,34 +12,36 @@ if [ ! -f sample.csv ]; then
|
|||||||
| gunzip -c > sample.csv
|
| gunzip -c > sample.csv
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
PLAYBOOK=procpath/karma_docker.procpath
|
||||||
|
|
||||||
# for renice to work run like "sudo -E env PATH=$PATH ./make.sh"
|
# for renice to work run like "sudo -E env PATH=$PATH ./make.sh"
|
||||||
test_ni=$(nice -n -1 nice)
|
test_ni=$(nice -n -5 nice)
|
||||||
if [ $test_ni == -1 ]; then
|
if [ $test_ni == -5 ]; then
|
||||||
flag_file=$(mktemp)
|
renice_flag_file=$(mktemp)
|
||||||
fi
|
fi
|
||||||
(
|
{
|
||||||
while [ -f $flag_file ]; do
|
while [ -f $renice_flag_file ]; do
|
||||||
root_pid=$(
|
procpath --logging-level ERROR play -f $PLAYBOOK renice:watch
|
||||||
docker ps -f status=running -f name='^sqljs-benchmark-' -q \
|
done
|
||||||
| xargs -r -I{} -- docker inspect -f '{{.State.Pid}}' {}
|
} &
|
||||||
)
|
|
||||||
if [ ! -z $root_pid ]; then
|
|
||||||
procpath query -d $'\n' "$..children[?(@.stat.pid == $root_pid)]..pid" \
|
|
||||||
| xargs -I{} -- renice -n -1 -p {} > /dev/null
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done &
|
|
||||||
)
|
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
for d in lib/build-* ; do
|
for d in lib/build-* ; do
|
||||||
rm -rf lib/dist
|
rm -rf lib/dist
|
||||||
cp -r $d lib/dist
|
cp -r $d lib/dist
|
||||||
|
sample_name=$(basename $d)
|
||||||
|
|
||||||
name=$(basename $d)
|
docker build -t sqliteviz/sqljs-benchmark .
|
||||||
docker build -t sqliteviz/sqljs-benchmark:$name .
|
docker rm sqljs-benchmark-run 2> /dev/null || true
|
||||||
docker rm sqljs-benchmark-$name 2> /dev/null || true
|
docker run -d -it --cpus 2 --name sqljs-benchmark-run sqliteviz/sqljs-benchmark
|
||||||
docker run -it --cpus 2 --name sqljs-benchmark-$name sqliteviz/sqljs-benchmark:$name
|
{
|
||||||
docker cp sqljs-benchmark-$name:/tmp/build/suite-result.json ${name}-result.json
|
rm -f ${sample_name}.sqlite
|
||||||
docker rm sqljs-benchmark-$name
|
procpath play -f $PLAYBOOK -o database_file=${sample_name}.sqlite track:record
|
||||||
|
procpath play -f $PLAYBOOK -o database_file=${sample_name}.sqlite \
|
||||||
|
-o plot_file=${sample_name}.svg track:plot
|
||||||
|
} &
|
||||||
|
|
||||||
|
docker attach sqljs-benchmark-run
|
||||||
|
docker cp sqljs-benchmark-run:/tmp/build/suite-result.json ${sample_name}-result.json
|
||||||
|
docker rm sqljs-benchmark-run
|
||||||
done
|
done
|
||||||
|
|||||||
28
lib/sql-js/benchmark/procpath/karma_docker.procpath
Normal file
28
lib/sql-js/benchmark/procpath/karma_docker.procpath
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# This command may run when "sqljs-benchmark-run" does not yet exist or run
|
||||||
|
[renice:watch]
|
||||||
|
interval: 2
|
||||||
|
repeat: 30
|
||||||
|
environment:
|
||||||
|
ROOT_PID=docker inspect -f "{{.State.Pid}}" sqljs-benchmark-run 2> /dev/null || true
|
||||||
|
query:
|
||||||
|
PIDS=$..children[?(@.stat.pid in [$ROOT_PID])]..pid
|
||||||
|
command:
|
||||||
|
echo $PIDS | tr , '\n' | xargs --no-run-if-empty -I{} -- renice -n -5 -p {}
|
||||||
|
|
||||||
|
# Expected input arguments: database_file
|
||||||
|
[track:record]
|
||||||
|
interval: 1
|
||||||
|
stop_without_result: 1
|
||||||
|
environment:
|
||||||
|
ROOT_PID=docker inspect -f "{{.State.Pid}}" sqljs-benchmark-run
|
||||||
|
query:
|
||||||
|
$..children[?(@.stat.pid == $ROOT_PID)]
|
||||||
|
pid_list: $ROOT_PID
|
||||||
|
|
||||||
|
# Expected input arguments: database_file, plot_file
|
||||||
|
[track:plot]
|
||||||
|
moving_average_window: 5
|
||||||
|
title: Chromium vs Firefox (№1 RSS, №2 CPU)
|
||||||
|
custom_query_file:
|
||||||
|
procpath/top2_rss.sql
|
||||||
|
procpath/top2_cpu.sql
|
||||||
29
lib/sql-js/benchmark/procpath/top2_cpu.sql
Normal file
29
lib/sql-js/benchmark/procpath/top2_cpu.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
WITH diff_all AS (
|
||||||
|
SELECT
|
||||||
|
record_id,
|
||||||
|
ts,
|
||||||
|
stat_pid,
|
||||||
|
stat_utime + stat_stime - LAG(stat_utime + stat_stime) OVER (
|
||||||
|
PARTITION BY stat_pid
|
||||||
|
ORDER BY record_id
|
||||||
|
) tick_diff,
|
||||||
|
ts - LAG(ts) OVER (
|
||||||
|
PARTITION BY stat_pid
|
||||||
|
ORDER BY record_id
|
||||||
|
) ts_diff
|
||||||
|
FROM record
|
||||||
|
), diff AS (
|
||||||
|
SELECT * FROM diff_all WHERE tick_diff IS NOT NULL
|
||||||
|
), one_time_pid_condition AS (
|
||||||
|
SELECT stat_pid
|
||||||
|
FROM record
|
||||||
|
GROUP BY 1
|
||||||
|
ORDER BY SUM(stat_utime + stat_stime) DESC
|
||||||
|
LIMIT 2
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
ts,
|
||||||
|
stat_pid pid,
|
||||||
|
100.0 * tick_diff / (SELECT value FROM meta WHERE key = 'clock_ticks') / ts_diff value
|
||||||
|
FROM diff
|
||||||
|
JOIN one_time_pid_condition USING(stat_pid)
|
||||||
13
lib/sql-js/benchmark/procpath/top2_rss.sql
Normal file
13
lib/sql-js/benchmark/procpath/top2_rss.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
WITH one_time_pid_condition AS (
|
||||||
|
SELECT stat_pid
|
||||||
|
FROM record
|
||||||
|
GROUP BY 1
|
||||||
|
ORDER BY SUM(stat_rss) DESC
|
||||||
|
LIMIT 2
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
ts,
|
||||||
|
stat_pid pid,
|
||||||
|
stat_rss / 1024.0 / 1024 * (SELECT value FROM meta WHERE key = 'page_size') value
|
||||||
|
FROM record
|
||||||
|
JOIN one_time_pid_condition USING(stat_pid)
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,9 +2,11 @@ import logging
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# See the setting descriptions on these pages:
|
||||||
|
# - https://emscripten.org/docs/optimizing/Optimizing-Code.html
|
||||||
|
# - https://github.com/emscripten-core/emscripten/blob/main/src/settings.js
|
||||||
cflags = (
|
cflags = (
|
||||||
'-O2',
|
# SQLite configuration
|
||||||
'-DSQLITE_DEFAULT_CACHE_SIZE=-65536', # 64 MiB
|
'-DSQLITE_DEFAULT_CACHE_SIZE=-65536', # 64 MiB
|
||||||
'-DSQLITE_DEFAULT_MEMSTATUS=0',
|
'-DSQLITE_DEFAULT_MEMSTATUS=0',
|
||||||
'-DSQLITE_DEFAULT_SYNCHRONOUS=0',
|
'-DSQLITE_DEFAULT_SYNCHRONOUS=0',
|
||||||
@@ -13,26 +15,26 @@ cflags = (
|
|||||||
'-DSQLITE_ENABLE_FTS3',
|
'-DSQLITE_ENABLE_FTS3',
|
||||||
'-DSQLITE_ENABLE_FTS3_PARENTHESIS',
|
'-DSQLITE_ENABLE_FTS3_PARENTHESIS',
|
||||||
'-DSQLITE_ENABLE_FTS5',
|
'-DSQLITE_ENABLE_FTS5',
|
||||||
'-DSQLITE_ENABLE_JSON1',
|
|
||||||
'-DSQLITE_ENABLE_NORMALIZE',
|
'-DSQLITE_ENABLE_NORMALIZE',
|
||||||
'-DSQLITE_EXTRA_INIT=extra_init',
|
'-DSQLITE_EXTRA_INIT=extra_init',
|
||||||
'-DSQLITE_OMIT_DEPRECATED',
|
'-DSQLITE_OMIT_DEPRECATED',
|
||||||
'-DSQLITE_OMIT_LOAD_EXTENSION',
|
'-DSQLITE_OMIT_LOAD_EXTENSION',
|
||||||
'-DSQLITE_OMIT_SHARED_CACHE',
|
'-DSQLITE_OMIT_SHARED_CACHE',
|
||||||
'-DSQLITE_THREADSAFE=0',
|
'-DSQLITE_THREADSAFE=0',
|
||||||
|
# Compile-time optimisation
|
||||||
|
'-Os', # reduces the code size about in half comparing to -O2
|
||||||
|
'-flto',
|
||||||
)
|
)
|
||||||
emflags = (
|
emflags = (
|
||||||
# Base
|
# Base
|
||||||
'--memory-init-file', '0',
|
'--memory-init-file', '0',
|
||||||
'-s', 'RESERVED_FUNCTION_POINTERS=64',
|
|
||||||
'-s', 'ALLOW_TABLE_GROWTH=1',
|
'-s', 'ALLOW_TABLE_GROWTH=1',
|
||||||
'-s', 'SINGLE_FILE=0',
|
|
||||||
# WASM
|
# WASM
|
||||||
'-s', 'WASM=1',
|
'-s', 'WASM=1',
|
||||||
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
||||||
# Optimisation
|
'-s', 'ENVIRONMENT=web,worker',
|
||||||
'-s', 'INLINING_LIMIT=50',
|
# Link-time optimisation
|
||||||
'-O3',
|
'-Os',
|
||||||
'-flto',
|
'-flto',
|
||||||
# sql.js
|
# sql.js
|
||||||
'-s', 'EXPORTED_FUNCTIONS=@src/sqljs/exported_functions.json',
|
'-s', 'EXPORTED_FUNCTIONS=@src/sqljs/exported_functions.json',
|
||||||
@@ -50,22 +52,22 @@ def build(src: Path, dst: Path):
|
|||||||
'emcc',
|
'emcc',
|
||||||
*cflags,
|
*cflags,
|
||||||
'-c', src / 'sqlite3.c',
|
'-c', src / 'sqlite3.c',
|
||||||
'-o', out / 'sqlite3.bc',
|
'-o', out / 'sqlite3.o',
|
||||||
])
|
])
|
||||||
logging.info('Building LLVM bitcode for extension-functions.c')
|
logging.info('Building LLVM bitcode for extension-functions.c')
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
'emcc',
|
'emcc',
|
||||||
*cflags,
|
*cflags,
|
||||||
'-c', src / 'extension-functions.c',
|
'-c', src / 'extension-functions.c',
|
||||||
'-o', out / 'extension-functions.bc',
|
'-o', out / 'extension-functions.o',
|
||||||
])
|
])
|
||||||
|
|
||||||
logging.info('Building WASM from bitcode')
|
logging.info('Building WASM from bitcode')
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
'emcc',
|
'emcc',
|
||||||
*emflags,
|
*emflags,
|
||||||
out / 'sqlite3.bc',
|
out / 'sqlite3.o',
|
||||||
out / 'extension-functions.bc',
|
out / 'extension-functions.o',
|
||||||
'-o', out / 'sql-wasm.js',
|
'-o', out / 'sql-wasm.js',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
from urllib import request
|
from urllib import request
|
||||||
|
|
||||||
|
|
||||||
amalgamation_url = 'https://sqlite.org/2022/sqlite-amalgamation-3380500.zip'
|
amalgamation_url = 'https://sqlite.org/2023/sqlite-amalgamation-3410000.zip'
|
||||||
|
|
||||||
# Extension-functions
|
# Extension-functions
|
||||||
# ===================
|
# ===================
|
||||||
@@ -28,10 +28,11 @@ extension_urls = (
|
|||||||
('https://sqlite.org/src/raw/09f967dc?at=decimal.c', 'sqlite3_decimal_init'),
|
('https://sqlite.org/src/raw/09f967dc?at=decimal.c', 'sqlite3_decimal_init'),
|
||||||
# Third-party extension
|
# Third-party extension
|
||||||
# =====================
|
# =====================
|
||||||
('https://github.com/jakethaw/pivot_vtab/raw/08ab0797/pivot_vtab.c', 'sqlite3_pivotvtab_init'),
|
('https://github.com/jakethaw/pivot_vtab/raw/9323ef93/pivot_vtab.c', 'sqlite3_pivotvtab_init'),
|
||||||
|
('https://github.com/nalgeon/sqlean/raw/95e8d21a/src/pearson.c', 'sqlite3_pearson_init'),
|
||||||
)
|
)
|
||||||
|
|
||||||
sqljs_url = 'https://github.com/sql-js/sql.js/archive/refs/tags/v1.5.0.zip'
|
sqljs_url = 'https://github.com/sql-js/sql.js/archive/refs/tags/v1.7.0.zip'
|
||||||
|
|
||||||
|
|
||||||
def _generate_extra_init_c_function(init_function_names):
|
def _generate_extra_init_c_function(init_function_names):
|
||||||
|
|||||||
2
lib/sql-js/dist/sql-wasm.js
vendored
2
lib/sql-js/dist/sql-wasm.js
vendored
File diff suppressed because one or more lines are too long
BIN
lib/sql-js/dist/sql-wasm.wasm
vendored
BIN
lib/sql-js/dist/sql-wasm.wasm
vendored
Binary file not shown.
35
package-lock.json
generated
35
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sqliteviz",
|
"name": "sqliteviz",
|
||||||
"version": "0.18.1",
|
"version": "0.23.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sqliteviz",
|
"name": "sqliteviz",
|
||||||
"version": "0.18.1",
|
"version": "0.23.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"codemirror": "^5.57.0",
|
"codemirror": "^5.57.0",
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"flush-promises": "^1.0.2",
|
||||||
"karma": "^3.1.4",
|
"karma": "^3.1.4",
|
||||||
"karma-firefox-launcher": "^2.1.0",
|
"karma-firefox-launcher": "^2.1.0",
|
||||||
"karma-webpack": "^4.0.2",
|
"karma-webpack": "^4.0.2",
|
||||||
@@ -5248,9 +5249,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001352",
|
"version": "1.0.30001488",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz",
|
||||||
"integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==",
|
"integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -5260,6 +5261,10 @@
|
|||||||
{
|
{
|
||||||
"type": "tidelift",
|
"type": "tidelift",
|
||||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -10086,6 +10091,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
|
||||||
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
|
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/flush-promises": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/flush-write-stream": {
|
"node_modules/flush-write-stream": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
||||||
@@ -27489,7 +27500,6 @@
|
|||||||
"integrity": "sha512-iFv9J3F5VKUPcbx+TqW5qhGmAVyXQxPRpKpPOuTLFIVTzg+iwJnrqVbL4kJU5ECGDxPESW2oCVgxv9bTlDPu7w==",
|
"integrity": "sha512-iFv9J3F5VKUPcbx+TqW5qhGmAVyXQxPRpKpPOuTLFIVTzg+iwJnrqVbL4kJU5ECGDxPESW2oCVgxv9bTlDPu7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/core": "^7.11.0",
|
|
||||||
"@babel/helper-compilation-targets": "^7.9.6",
|
"@babel/helper-compilation-targets": "^7.9.6",
|
||||||
"@babel/helper-module-imports": "^7.8.3",
|
"@babel/helper-module-imports": "^7.8.3",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
@@ -27502,7 +27512,6 @@
|
|||||||
"@vue/babel-plugin-jsx": "^1.0.3",
|
"@vue/babel-plugin-jsx": "^1.0.3",
|
||||||
"@vue/babel-preset-jsx": "^1.2.4",
|
"@vue/babel-preset-jsx": "^1.2.4",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"core-js": "^3.6.5",
|
|
||||||
"core-js-compat": "^3.6.5",
|
"core-js-compat": "^3.6.5",
|
||||||
"semver": "^6.1.0"
|
"semver": "^6.1.0"
|
||||||
}
|
}
|
||||||
@@ -29627,9 +29636,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001352",
|
"version": "1.0.30001488",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz",
|
||||||
"integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==",
|
"integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"canvas-fit": {
|
"canvas-fit": {
|
||||||
@@ -33656,6 +33665,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
|
||||||
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
|
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
|
||||||
},
|
},
|
||||||
|
"flush-promises": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"flush-write-stream": {
|
"flush-write-stream": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sqliteviz",
|
"name": "sqliteviz",
|
||||||
"version": "0.19.0",
|
"version": "0.23.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"flush-promises": "^1.0.2",
|
||||||
"karma": "^3.1.4",
|
"karma": "^3.1.4",
|
||||||
"karma-firefox-launcher": "^2.1.0",
|
"karma-firefox-launcher": "^2.1.0",
|
||||||
"karma-webpack": "^4.0.2",
|
"karma-webpack": "^4.0.2",
|
||||||
|
|||||||
47
src/App.vue
47
src/App.vue
@@ -1,35 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div v-if="showBanner" id="banner" class="warning">
|
|
||||||
<close-icon @click="showBanner = false"/>
|
|
||||||
Sqliteviz has got own space on the web,
|
|
||||||
<a href="https://sqliteviz.com/" target="_blank">sqliteviz.com</a>!
|
|
||||||
New builds will be published there, so we recommend you to switch to the
|
|
||||||
<a href="https://sqliteviz.com/app/#" target="_blank">
|
|
||||||
new link</a> to not miss new features.
|
|
||||||
This
|
|
||||||
<a
|
|
||||||
href="https://sqliteviz.com/docs/how-to-migrate-to-sqliteviz-dot-com/"
|
|
||||||
target="_blank"
|
|
||||||
>migration guide</a> will help if you're not sure how to migrate.
|
|
||||||
</div>
|
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import CloseIcon from '@/components/svg/close'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { CloseIcon },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showBanner: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Open Sans";
|
font-family: "Open Sans";
|
||||||
@@ -89,25 +63,4 @@ body {
|
|||||||
.CodeMirror-hints {
|
.CodeMirror-hints {
|
||||||
z-index: 999 !important;
|
z-index: 999 !important;
|
||||||
}
|
}
|
||||||
#banner {
|
|
||||||
position: fixed;
|
|
||||||
max-width: 500px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 1000;
|
|
||||||
top: 8px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
padding: 32px;
|
|
||||||
border-radius: var(--border-radius-big);
|
|
||||||
box-shadow: 0px 2px 9px rgba(80, 103, 132, 0.4);
|
|
||||||
}
|
|
||||||
#banner svg {
|
|
||||||
padding: 4px;
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
}
|
|
||||||
#banner a {
|
|
||||||
color: var(--color-text-active);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ import SqlTable from '@/components/SqlTable'
|
|||||||
import Logs from '@/components/Logs'
|
import Logs from '@/components/Logs'
|
||||||
import time from '@/lib/utils/time'
|
import time from '@/lib/utils/time'
|
||||||
import fIo from '@/lib/utils/fileIo'
|
import fIo from '@/lib/utils/fileIo'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CsvImport',
|
name: 'CsvImport',
|
||||||
@@ -336,7 +336,7 @@ export default {
|
|||||||
this.$store.commit('setCurrentTabId', tabId)
|
this.$store.commit('setCurrentTabId', tabId)
|
||||||
this.importCsvCompleted = false
|
this.importCsvCompleted = false
|
||||||
this.$emit('finish')
|
this.$emit('finish')
|
||||||
send('inquiry.create', undefined, { auto: true })
|
events.send('inquiry.create', null, { auto: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import fIo from '@/lib/utils/fileIo'
|
|||||||
import ChangeDbIcon from '@/components/svg/changeDb'
|
import ChangeDbIcon from '@/components/svg/changeDb'
|
||||||
import database from '@/lib/database'
|
import database from '@/lib/database'
|
||||||
import CsvImport from '@/components/CsvImport'
|
import CsvImport from '@/components/CsvImport'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DbUploader',
|
name: 'DbUploader',
|
||||||
@@ -128,7 +128,7 @@ export default {
|
|||||||
if (fIo.isDatabase(file)) {
|
if (fIo.isDatabase(file)) {
|
||||||
this.loadDb(file)
|
this.loadDb(file)
|
||||||
} else {
|
} else {
|
||||||
send('database.import', file.size, {
|
events.send('database.import', file.size, {
|
||||||
from: 'csv',
|
from: 'csv',
|
||||||
new_db: true
|
new_db: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,14 +75,23 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
horizontal: { type: Boolean, default: false },
|
horizontal: { type: Boolean, default: false },
|
||||||
before: { type: Object },
|
before: { type: Object },
|
||||||
after: { type: Object }
|
after: { type: Object },
|
||||||
|
default: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
before: 50,
|
||||||
|
after: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
container: null,
|
container: null,
|
||||||
paneBefore: this.before,
|
paneBefore: this.before,
|
||||||
paneAfter: this.after,
|
paneAfter: this.after,
|
||||||
beforeMinimising: {
|
beforeMinimising: !this.after.size || !this.before.size ? this.default : {
|
||||||
before: this.before.size,
|
before: this.before.size,
|
||||||
after: this.after.size
|
after: this.after.size
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Worker from './_worker.js'
|
|||||||
// https://github.com/nolanlawson/promise-worker
|
// https://github.com/nolanlawson/promise-worker
|
||||||
import PromiseWorker from 'promise-worker'
|
import PromiseWorker from 'promise-worker'
|
||||||
|
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
function getNewDatabase () {
|
function getNewDatabase () {
|
||||||
const worker = new Worker()
|
const worker = new Worker()
|
||||||
@@ -77,9 +77,9 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dbName = file ? fu.getFileName(file) : 'database'
|
this.dbName = file ? fu.getFileName(file) : 'database'
|
||||||
this.refreshSchema()
|
await this.refreshSchema()
|
||||||
|
|
||||||
send('database.import', file ? file.size : 0, {
|
events.send('database.import', file ? file.size : 0, {
|
||||||
from: file ? 'sqlite' : 'none',
|
from: file ? 'sqlite' : 'none',
|
||||||
new_db: true
|
new_db: true
|
||||||
})
|
})
|
||||||
@@ -121,7 +121,7 @@ class Database {
|
|||||||
throw new Error(data.error)
|
throw new Error(data.error)
|
||||||
}
|
}
|
||||||
fu.exportToFile(data, fileName)
|
fu.exportToFile(data, fileName)
|
||||||
send('database.export', data.byteLength, { to: 'sqlite' })
|
events.send('database.export', data.byteLength, { to: 'sqlite' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateTableName (name) {
|
async validateTableName (name) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import fu from '@/lib/utils/fileIo'
|
import fu from '@/lib/utils/fileIo'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
import migration from './_migrations'
|
import migration from './_migrations'
|
||||||
|
|
||||||
const migrate = migration._migrate
|
const migrate = migration._migrate
|
||||||
@@ -33,17 +33,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isTabNeedName (inquiryTab) {
|
isTabNeedName (inquiryTab) {
|
||||||
const isFromScratch = !inquiryTab.initName
|
return inquiryTab.isPredefined || !inquiryTab.name
|
||||||
return inquiryTab.isPredefined || isFromScratch
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save (inquiryTab, newName) {
|
save (inquiryTab, newName) {
|
||||||
const value = {
|
const value = {
|
||||||
id: inquiryTab.isPredefined ? nanoid() : inquiryTab.id,
|
id: inquiryTab.isPredefined ? nanoid() : inquiryTab.id,
|
||||||
query: inquiryTab.query,
|
query: inquiryTab.query,
|
||||||
viewType: inquiryTab.$refs.dataView.mode,
|
viewType: inquiryTab.dataView.mode,
|
||||||
viewOptions: inquiryTab.$refs.dataView.getOptionsForSave(),
|
viewOptions: inquiryTab.dataView.getOptionsForSave(),
|
||||||
name: newName || inquiryTab.initName
|
name: newName || inquiryTab.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get inquiries from local storage
|
// Get inquiries from local storage
|
||||||
@@ -106,7 +105,7 @@ export default {
|
|||||||
.then(str => {
|
.then(str => {
|
||||||
const inquires = this.deserialiseInquiries(str)
|
const inquires = this.deserialiseInquiries(str)
|
||||||
|
|
||||||
send('inquiry.import', inquires.length)
|
events.send('inquiry.import', inquires.length)
|
||||||
|
|
||||||
return inquires
|
return inquires
|
||||||
})
|
})
|
||||||
@@ -115,7 +114,7 @@ export default {
|
|||||||
const jsonStr = this.serialiseInquiries(inquiryList)
|
const jsonStr = this.serialiseInquiries(inquiryList)
|
||||||
fu.exportToFile(jsonStr, fileName)
|
fu.exportToFile(jsonStr, fileName)
|
||||||
|
|
||||||
send('inquiry.export', inquiryList.length)
|
events.send('inquiry.export', inquiryList.length)
|
||||||
},
|
},
|
||||||
|
|
||||||
async readPredefinedInquiries () {
|
async readPredefinedInquiries () {
|
||||||
|
|||||||
59
src/lib/tab.js
Normal file
59
src/lib/tab.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
import time from '@/lib/utils/time'
|
||||||
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
|
export default class Tab {
|
||||||
|
constructor (state, inquiry = {}) {
|
||||||
|
this.id = inquiry.id || nanoid()
|
||||||
|
this.name = inquiry.id ? inquiry.name : null
|
||||||
|
this.tempName = inquiry.name || (state.untitledLastIndex
|
||||||
|
? `Untitled ${state.untitledLastIndex}`
|
||||||
|
: 'Untitled')
|
||||||
|
this.query = inquiry.query
|
||||||
|
this.viewOptions = inquiry.viewOptions || undefined
|
||||||
|
this.isPredefined = inquiry.isPredefined
|
||||||
|
this.viewType = inquiry.viewType || 'chart'
|
||||||
|
this.result = null
|
||||||
|
this.isGettingResults = false
|
||||||
|
this.error = null
|
||||||
|
this.time = 0
|
||||||
|
this.layout = inquiry.layout || {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
}
|
||||||
|
this.maximize = inquiry.maximize
|
||||||
|
|
||||||
|
this.isSaved = !!inquiry.id
|
||||||
|
this.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute () {
|
||||||
|
this.isGettingResults = true
|
||||||
|
this.result = null
|
||||||
|
this.error = null
|
||||||
|
const db = this.state.db
|
||||||
|
try {
|
||||||
|
const start = new Date()
|
||||||
|
this.result = await db.execute(this.query + ';')
|
||||||
|
this.time = time.getPeriod(start, new Date())
|
||||||
|
|
||||||
|
if (this.result && this.result.values) {
|
||||||
|
events.send('resultset.create',
|
||||||
|
this.result.values[this.result.columns[0]].length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
events.send('query.run', parseFloat(this.time), { status: 'success' })
|
||||||
|
} catch (err) {
|
||||||
|
this.error = {
|
||||||
|
type: 'error',
|
||||||
|
message: err
|
||||||
|
}
|
||||||
|
|
||||||
|
events.send('query.run', 0, { status: 'error' })
|
||||||
|
}
|
||||||
|
db.refreshSchema()
|
||||||
|
this.isGettingResults = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
export function send (name, value, labels) {
|
export default {
|
||||||
const event = new CustomEvent('sqliteviz-app-event', {
|
send (name, value, labels) {
|
||||||
detail: {
|
const event = new CustomEvent('sqliteviz-app-event', {
|
||||||
name,
|
detail: {
|
||||||
value,
|
name,
|
||||||
labels
|
value,
|
||||||
}
|
labels: labels || {}
|
||||||
})
|
}
|
||||||
window.dispatchEvent(event)
|
})
|
||||||
|
window.dispatchEvent(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
let refresh = false
|
let refresh = false
|
||||||
|
|
||||||
function invokeServiceWorkerUpdateFlow (registration) {
|
function invokeServiceWorkerUpdateFlow (registration) {
|
||||||
@@ -44,6 +44,6 @@ if ('serviceWorker' in navigator) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('appinstalled', () => {
|
window.addEventListener('appinstalled', () => {
|
||||||
send('pwa.install')
|
events.send('pwa.install')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Workspace from '@/views/Main/Workspace'
|
|||||||
import Inquiries from '@/views/Main/Inquiries'
|
import Inquiries from '@/views/Main/Inquiries'
|
||||||
import Welcome from '@/views/Welcome'
|
import Welcome from '@/views/Welcome'
|
||||||
import Main from '@/views/Main'
|
import Main from '@/views/Main'
|
||||||
|
import LoadView from '@/views/LoadView'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import database from '@/lib/database'
|
import database from '@/lib/database'
|
||||||
|
|
||||||
@@ -31,6 +32,11 @@ const routes = [
|
|||||||
component: Inquiries
|
component: Inquiries
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/load',
|
||||||
|
name: 'Load',
|
||||||
|
component: LoadView
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -39,7 +45,7 @@ const router = new VueRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (!store.state.db) {
|
if (!store.state.db && to.name !== 'Load') {
|
||||||
const newDb = database.getNewDatabase()
|
const newDb = database.getNewDatabase()
|
||||||
await newDb.loadDb()
|
await newDb.loadDb()
|
||||||
store.commit('setDb', newDb)
|
store.commit('setDb', newDb)
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
import { nanoid } from 'nanoid'
|
import Tab from '@/lib/tab'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async addTab ({ state }, data) {
|
async addTab ({ state }, inquiry = {}) {
|
||||||
const tab = data ? JSON.parse(JSON.stringify(data)) : {}
|
// add new tab only if it was not already opened
|
||||||
// If no data then create a new blank one...
|
if (!state.tabs.some(openedTab => openedTab.id === inquiry.id)) {
|
||||||
// No data.id means to create new tab, but not blank,
|
const tab = new Tab(state, JSON.parse(JSON.stringify(inquiry)))
|
||||||
// e.g. with 'select * from csv_import' inquiry after csv import
|
|
||||||
if (!data || !data.id) {
|
|
||||||
tab.id = nanoid()
|
|
||||||
tab.name = null
|
|
||||||
tab.tempName = state.untitledLastIndex
|
|
||||||
? `Untitled ${state.untitledLastIndex}`
|
|
||||||
: 'Untitled'
|
|
||||||
tab.viewType = 'chart'
|
|
||||||
tab.viewOptions = undefined
|
|
||||||
tab.isSaved = false
|
|
||||||
} else {
|
|
||||||
tab.isSaved = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new tab only if was not already opened
|
|
||||||
if (!state.tabs.some(openedTab => openedTab.id === tab.id)) {
|
|
||||||
state.tabs.push(tab)
|
state.tabs.push(tab)
|
||||||
if (!tab.name) {
|
if (!tab.name) {
|
||||||
state.untitledLastIndex += 1
|
state.untitledLastIndex += 1
|
||||||
}
|
}
|
||||||
|
return tab.id
|
||||||
}
|
}
|
||||||
|
|
||||||
return tab.id
|
return inquiry.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setDb (state, db) {
|
setDb (state, db) {
|
||||||
if (state.db) {
|
if (state.db) {
|
||||||
@@ -8,8 +6,8 @@ export default {
|
|||||||
state.db = db
|
state.db = db
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTab (state, { index, name, id, query, viewType, viewOptions, isSaved }) {
|
updateTab (state, { tab, newValues }) {
|
||||||
const tab = state.tabs[index]
|
const { name, id, query, viewType, viewOptions, isSaved } = newValues
|
||||||
const oldId = tab.id
|
const oldId = tab.id
|
||||||
|
|
||||||
if (id && state.currentTabId === oldId) {
|
if (id && state.currentTabId === oldId) {
|
||||||
@@ -26,13 +24,12 @@ export default {
|
|||||||
// Saved inquiry is not predefined
|
// Saved inquiry is not predefined
|
||||||
delete tab.isPredefined
|
delete tab.isPredefined
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.set(state.tabs, index, tab)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTab (state, index) {
|
deleteTab (state, tab) {
|
||||||
|
const index = state.tabs.indexOf(tab)
|
||||||
// If closing tab is the current opened
|
// If closing tab is the current opened
|
||||||
if (state.tabs[index].id === state.currentTabId) {
|
if (tab.id === state.currentTabId) {
|
||||||
if (index < state.tabs.length - 1) {
|
if (index < state.tabs.length - 1) {
|
||||||
state.currentTabId = state.tabs[index + 1].id
|
state.currentTabId = state.tabs[index + 1].id
|
||||||
} else if (index > 0) {
|
} else if (index > 0) {
|
||||||
@@ -46,12 +43,20 @@ export default {
|
|||||||
state.tabs.splice(index, 1)
|
state.tabs.splice(index, 1)
|
||||||
},
|
},
|
||||||
setCurrentTabId (state, id) {
|
setCurrentTabId (state, id) {
|
||||||
state.currentTabId = id
|
try {
|
||||||
},
|
state.currentTabId = id
|
||||||
setCurrentTab (state, tab) {
|
state.currentTab = state.tabs.find(tab => tab.id === id)
|
||||||
state.currentTab = tab
|
} catch (e) {
|
||||||
|
console.error('Can\'t open a tab id:' + id)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updatePredefinedInquiries (state, inquiries) {
|
updatePredefinedInquiries (state, inquiries) {
|
||||||
state.predefinedInquiries = Array.isArray(inquiries) ? inquiries : [inquiries]
|
state.predefinedInquiries = Array.isArray(inquiries) ? inquiries : [inquiries]
|
||||||
|
},
|
||||||
|
setLoadingPredefinedInquiries (state, value) {
|
||||||
|
state.loadingPredefinedInquiries = value
|
||||||
|
},
|
||||||
|
setPredefinedInquiriesLoaded (state, value) {
|
||||||
|
state.predefinedInquiriesLoaded = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ export default {
|
|||||||
currentTabId: null,
|
currentTabId: null,
|
||||||
untitledLastIndex: 0,
|
untitledLastIndex: 0,
|
||||||
predefinedInquiries: [],
|
predefinedInquiries: [],
|
||||||
|
loadingPredefinedInquiries: false,
|
||||||
|
predefinedInquiriesLoaded: false,
|
||||||
db: null
|
db: null
|
||||||
}
|
}
|
||||||
|
|||||||
191
src/views/LoadView.vue
Normal file
191
src/views/LoadView.vue
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<logs
|
||||||
|
id="logs"
|
||||||
|
:messages="messages"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="hasErrors"
|
||||||
|
id="open-workspace-btn"
|
||||||
|
class="secondary"
|
||||||
|
@click="$router.push('/workspace?hide_schema=1')">
|
||||||
|
Open workspace
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
import database from '@/lib/database'
|
||||||
|
import Logs from '@/components/Logs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LoadView',
|
||||||
|
components: {
|
||||||
|
Logs
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
newDb: null,
|
||||||
|
messages: [],
|
||||||
|
dataMsg: {},
|
||||||
|
inquiryMsg: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasErrors () {
|
||||||
|
return this.dataMsg.type === 'error' || this.inquiryMsg.type === 'error'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
const {
|
||||||
|
data_url: dataUrl,
|
||||||
|
data_format: dataFormat,
|
||||||
|
inquiry_url: inquiryUrl,
|
||||||
|
inquiry_id: inquiryIds,
|
||||||
|
maximize
|
||||||
|
} = this.$route.query
|
||||||
|
|
||||||
|
await this.loadData(dataUrl, dataFormat)
|
||||||
|
const inquiries = await this.loadInquiries(inquiryUrl, inquiryIds)
|
||||||
|
if (inquiries && inquiries.length > 0) {
|
||||||
|
await this.openInquiries(inquiries, maximize)
|
||||||
|
}
|
||||||
|
if (!this.hasErrors) {
|
||||||
|
this.$router.push('/workspace?hide_schema=1')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadData (dataUrl, dataFormat) {
|
||||||
|
this.newDb = database.getNewDatabase()
|
||||||
|
if (dataUrl) {
|
||||||
|
this.dataMsg = {
|
||||||
|
message: 'Preparing data...',
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
this.messages.push(this.dataMsg)
|
||||||
|
|
||||||
|
// Show loading indicator after 1 second
|
||||||
|
const loadingDataIndicator = setTimeout(() => {
|
||||||
|
if (this.dataMsg.type === 'info') {
|
||||||
|
this.dataMsg.type = 'loading'
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
if (dataFormat === 'sqlite') {
|
||||||
|
await this.getSqliteDb(dataUrl)
|
||||||
|
} else {
|
||||||
|
this.dataMsg.message = 'Unknown data format'
|
||||||
|
this.dataMsg.type = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading indicator is not needed anymore
|
||||||
|
clearTimeout(loadingDataIndicator)
|
||||||
|
} else {
|
||||||
|
await this.newDb.loadDb()
|
||||||
|
}
|
||||||
|
this.$store.commit('setDb', this.newDb)
|
||||||
|
},
|
||||||
|
async getSqliteDb (dataUrl) {
|
||||||
|
try {
|
||||||
|
const filename = new URL(dataUrl).pathname.split('/').pop()
|
||||||
|
const res = await fu.readFile(dataUrl)
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Fetching DB failed')
|
||||||
|
}
|
||||||
|
const file = await res.blob()
|
||||||
|
file.name = filename
|
||||||
|
|
||||||
|
await this.newDb.loadDb(file)
|
||||||
|
this.dataMsg.message = 'Data is ready'
|
||||||
|
this.dataMsg.type = 'success'
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
this.dataMsg.message = error
|
||||||
|
this.dataMsg.type = 'error'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadInquiries (inquiryUrl, inquiryIds = []) {
|
||||||
|
if (!inquiryUrl) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// Show loading indicator after 1 second
|
||||||
|
const loadingInquiriesIndicator = setTimeout(() => {
|
||||||
|
if (this.inquiryMsg.type === 'info') {
|
||||||
|
this.inquiryMsg.type = 'loading'
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
try {
|
||||||
|
this.inquiryMsg = {
|
||||||
|
message: 'Preparing inquiries...',
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
this.messages.push(this.inquiryMsg)
|
||||||
|
|
||||||
|
const res = await fu.readFile(inquiryUrl)
|
||||||
|
const file = await res.json()
|
||||||
|
|
||||||
|
this.inquiryMsg.message = 'Inquiries are ready'
|
||||||
|
this.inquiryMsg.type = 'success'
|
||||||
|
|
||||||
|
return inquiryIds.length > 0
|
||||||
|
? file.inquiries.filter(inquiry => inquiryIds.includes(inquiry.id))
|
||||||
|
: file.inquiries
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
this.inquiryMsg.message = error
|
||||||
|
this.inquiryMsg.type = 'error'
|
||||||
|
}
|
||||||
|
// Loading indicator is not needed anymore
|
||||||
|
clearTimeout(loadingInquiriesIndicator)
|
||||||
|
},
|
||||||
|
async openInquiries (inquiries, maximize) {
|
||||||
|
let tabToOpen = null
|
||||||
|
const layout = maximize ? this.getLayout(maximize) : undefined
|
||||||
|
for (const inquiry of inquiries) {
|
||||||
|
const tabId = await this.$store.dispatch('addTab', {
|
||||||
|
...inquiry,
|
||||||
|
id: undefined,
|
||||||
|
layout,
|
||||||
|
maximize
|
||||||
|
})
|
||||||
|
if (!tabToOpen) {
|
||||||
|
tabToOpen = tabId
|
||||||
|
this.$store.commit('setCurrentTabId', tabToOpen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.state.currentTab.execute()
|
||||||
|
},
|
||||||
|
|
||||||
|
getLayout (panelToMaximize) {
|
||||||
|
if (panelToMaximize === 'dataView') {
|
||||||
|
return {
|
||||||
|
sqlEditor: 'hidden',
|
||||||
|
table: 'above',
|
||||||
|
dataView: 'bottom'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#logs {
|
||||||
|
margin: 8px auto;
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#open-workspace-btn {
|
||||||
|
margin: 16px auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div id="my-inquiries-container">
|
||||||
<div id="start-guide" v-if="allInquiries.length === 0">
|
<div id="start-guide" v-if="allInquiries.length === 0">
|
||||||
You don't have saved inquiries so far.
|
You don't have saved inquiries so far.
|
||||||
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
|
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
|
||||||
the one from scratch or
|
the one from scratch or
|
||||||
<span @click="importInquiries" class="link">import</span> from a file.
|
<span @click="importInquiries" class="link">import</span> from a file.
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="loading-predefined-status"
|
||||||
|
v-if="$store.state.loadingPredefinedInquiries"
|
||||||
|
>
|
||||||
|
<loading-indicator/>
|
||||||
|
Loading predefined inquiries...
|
||||||
|
</div>
|
||||||
<div id="my-inquiries-content" ref="my-inquiries-content" v-show="allInquiries.length > 0">
|
<div id="my-inquiries-content" ref="my-inquiries-content" v-show="allInquiries.length > 0">
|
||||||
<div id="my-inquiries-toolbar">
|
<div id="my-inquiries-toolbar">
|
||||||
<div id="toolbar-buttons">
|
<div id="toolbar-buttons">
|
||||||
@@ -157,6 +164,7 @@ import DeleteIcon from './svg/delete'
|
|||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import CheckBox from '@/components/CheckBox'
|
import CheckBox from '@/components/CheckBox'
|
||||||
|
import LoadingIndicator from '@/components/LoadingIndicator'
|
||||||
import tooltipMixin from '@/tooltipMixin'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
import storedInquiries from '@/lib/storedInquiries'
|
import storedInquiries from '@/lib/storedInquiries'
|
||||||
|
|
||||||
@@ -169,7 +177,8 @@ export default {
|
|||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
CloseIcon,
|
CloseIcon,
|
||||||
TextField,
|
TextField,
|
||||||
CheckBox
|
CheckBox,
|
||||||
|
LoadingIndicator
|
||||||
},
|
},
|
||||||
mixins: [tooltipMixin],
|
mixins: [tooltipMixin],
|
||||||
data () {
|
data () {
|
||||||
@@ -248,15 +257,21 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
async created () {
|
||||||
storedInquiries.readPredefinedInquiries()
|
this.inquiries = storedInquiries.getStoredInquiries()
|
||||||
.then(inquiries => {
|
const loadingPredefinedInquiries = this.$store.state.loadingPredefinedInquiries
|
||||||
|
const predefinedInquiriesLoaded = this.$store.state.predefinedInquiriesLoaded
|
||||||
|
if (!predefinedInquiriesLoaded && !loadingPredefinedInquiries) {
|
||||||
|
try {
|
||||||
|
this.$store.commit('setLoadingPredefinedInquiries', true)
|
||||||
|
const inquiries = await storedInquiries.readPredefinedInquiries()
|
||||||
this.$store.commit('updatePredefinedInquiries', inquiries)
|
this.$store.commit('updatePredefinedInquiries', inquiries)
|
||||||
})
|
this.$store.commit('setPredefinedInquiriesLoaded', true)
|
||||||
.catch(console.error)
|
} catch (e) {
|
||||||
.finally(() => {
|
console.error(e)
|
||||||
this.inquiries = storedInquiries.getStoredInquiries()
|
}
|
||||||
})
|
this.$store.commit('setLoadingPredefinedInquiries', false)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.resizeObserver = new ResizeObserver(this.calcMaxTableHeight)
|
this.resizeObserver = new ResizeObserver(this.calcMaxTableHeight)
|
||||||
@@ -323,12 +338,14 @@ export default {
|
|||||||
storedInquiries.updateStorage(this.inquiries)
|
storedInquiries.updateStorage(this.inquiries)
|
||||||
|
|
||||||
// update tab, if renamed inquiry is opened
|
// update tab, if renamed inquiry is opened
|
||||||
const tabIndex = this.findTabIndex(processedInquiry.id)
|
const tab = this.$store.state.tabs
|
||||||
if (tabIndex >= 0) {
|
.find(tab => tab.id === processedInquiry.id)
|
||||||
|
if (tab) {
|
||||||
this.$store.commit('updateTab', {
|
this.$store.commit('updateTab', {
|
||||||
index: tabIndex,
|
tab,
|
||||||
name: this.newName,
|
newValues: {
|
||||||
id: processedInquiry.id
|
name: this.newName
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// hide dialog
|
// hide dialog
|
||||||
@@ -352,9 +369,10 @@ export default {
|
|||||||
this.inquiries.splice(this.processedInquiryIndex, 1)
|
this.inquiries.splice(this.processedInquiryIndex, 1)
|
||||||
|
|
||||||
// Close deleted inquiry tab if it was opened
|
// Close deleted inquiry tab if it was opened
|
||||||
const tabIndex = this.findTabIndex(this.processedInquiryId)
|
const tab = this.$store.state.tabs
|
||||||
if (tabIndex >= 0) {
|
.find(tab => tab.id === this.processedInquiryId)
|
||||||
this.$store.commit('deleteTab', tabIndex)
|
if (tab) {
|
||||||
|
this.$store.commit('deleteTab', tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear checkbox
|
// Clear checkbox
|
||||||
@@ -368,10 +386,12 @@ export default {
|
|||||||
|
|
||||||
// Close deleted inquiries if it was opened
|
// Close deleted inquiries if it was opened
|
||||||
const tabs = this.$store.state.tabs
|
const tabs = this.$store.state.tabs
|
||||||
for (let i = tabs.length - 1; i >= 0; i--) {
|
let i = tabs.length - 1
|
||||||
|
while (i > -1) {
|
||||||
if (this.selectedInquiriesIds.has(tabs[i].id)) {
|
if (this.selectedInquiriesIds.has(tabs[i].id)) {
|
||||||
this.$store.commit('deleteTab', i)
|
this.$store.commit('deleteTab', tabs[i])
|
||||||
}
|
}
|
||||||
|
i--
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear checkboxes
|
// Clear checkboxes
|
||||||
@@ -380,9 +400,6 @@ export default {
|
|||||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||||
storedInquiries.updateStorage(this.inquiries)
|
storedInquiries.updateStorage(this.inquiries)
|
||||||
},
|
},
|
||||||
findTabIndex (id) {
|
|
||||||
return this.$store.state.tabs.findIndex(tab => tab.id === id)
|
|
||||||
},
|
|
||||||
exportToFile (inquiryList, fileName) {
|
exportToFile (inquiryList, fileName) {
|
||||||
storedInquiries.export(inquiryList, fileName)
|
storedInquiries.export(inquiryList, fileName)
|
||||||
},
|
},
|
||||||
@@ -441,6 +458,21 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
#my-inquiries-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading-predefined-status {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-light-2);
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
#start-guide {
|
#start-guide {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import TextField from '@/components/TextField'
|
|||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
import storedInquiries from '@/lib/storedInquiries'
|
import storedInquiries from '@/lib/storedInquiries'
|
||||||
import AppDiagnosticInfo from './AppDiagnosticInfo'
|
import AppDiagnosticInfo from './AppDiagnosticInfo'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainMenu',
|
name: 'MainMenu',
|
||||||
@@ -80,19 +80,10 @@ export default {
|
|||||||
return this.$store.state.currentTab
|
return this.$store.state.currentTab
|
||||||
},
|
},
|
||||||
isSaved () {
|
isSaved () {
|
||||||
if (!this.currentInquiry) {
|
return this.currentInquiry && this.currentInquiry.isSaved
|
||||||
return false
|
|
||||||
}
|
|
||||||
const tabIndex = this.currentInquiry.tabIndex
|
|
||||||
const tab = this.$store.state.tabs[tabIndex]
|
|
||||||
return tab && tab.isSaved
|
|
||||||
},
|
},
|
||||||
isPredefined () {
|
isPredefined () {
|
||||||
if (this.currentInquiry) {
|
return this.currentInquiry && this.currentInquiry.isPredefined
|
||||||
return this.currentInquiry.isPredefined
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
runDisabled () {
|
runDisabled () {
|
||||||
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query)
|
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query)
|
||||||
@@ -115,7 +106,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
send('inquiry.create', undefined, { auto: false })
|
events.send('inquiry.create', null, { auto: false })
|
||||||
},
|
},
|
||||||
cancelSave () {
|
cancelSave () {
|
||||||
this.$modal.hide('save')
|
this.$modal.hide('save')
|
||||||
@@ -145,13 +136,15 @@ export default {
|
|||||||
|
|
||||||
// Update tab in store
|
// Update tab in store
|
||||||
this.$store.commit('updateTab', {
|
this.$store.commit('updateTab', {
|
||||||
index: this.currentInquiry.tabIndex,
|
tab: this.currentInquiry,
|
||||||
name: value.name,
|
newValues: {
|
||||||
id: value.id,
|
name: value.name,
|
||||||
query: value.query,
|
id: value.id,
|
||||||
viewType: value.viewType,
|
query: value.query,
|
||||||
viewOptions: value.viewOptions,
|
viewType: value.viewType,
|
||||||
isSaved: true
|
viewOptions: value.viewOptions,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Restore data:
|
// Restore data:
|
||||||
@@ -169,7 +162,7 @@ export default {
|
|||||||
|
|
||||||
// Signal about saving
|
// Signal about saving
|
||||||
this.$root.$emit('inquirySaved')
|
this.$root.$emit('inquirySaved')
|
||||||
send('inquiry.save')
|
events.send('inquiry.save')
|
||||||
},
|
},
|
||||||
_keyListener (e) {
|
_keyListener (e) {
|
||||||
if (this.$route.path === '/workspace') {
|
if (this.$route.path === '/workspace') {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fIo from '@/lib/utils/fileIo'
|
import fIo from '@/lib/utils/fileIo'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
import TableDescription from './TableDescription'
|
import TableDescription from './TableDescription'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import TreeChevron from '@/components/svg/treeChevron'
|
import TreeChevron from '@/components/svg/treeChevron'
|
||||||
@@ -88,7 +88,7 @@ export default {
|
|||||||
await csvImport.previewCsv()
|
await csvImport.previewCsv()
|
||||||
csvImport.open()
|
csvImport.open()
|
||||||
|
|
||||||
send('database.import', this.file.size, {
|
events.send('database.import', this.file.size, {
|
||||||
from: 'csv',
|
from: 'csv',
|
||||||
new_db: false
|
new_db: false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import PlotlyEditor from 'react-chart-editor'
|
|||||||
import chartHelper from '@/lib/chartHelper'
|
import chartHelper from '@/lib/chartHelper'
|
||||||
import dereference from 'react-chart-editor/lib/lib/dereference'
|
import dereference from 'react-chart-editor/lib/lib/dereference'
|
||||||
import fIo from '@/lib/utils/fileIo'
|
import fIo from '@/lib/utils/fileIo'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Chart',
|
name: 'Chart',
|
||||||
@@ -66,11 +66,11 @@ export default {
|
|||||||
notifyOnLogging: 1
|
notifyOnLogging: 1
|
||||||
})
|
})
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => JSON.stringify(
|
() => this.state && this.state.data && this.state.data
|
||||||
this.state.data.map(trace => `${trace.type}-${trace.mode}`)
|
.map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`)
|
||||||
),
|
.join(','),
|
||||||
(value) => {
|
(value) => {
|
||||||
send('viz_plotly.render', undefined, {
|
events.send('viz_plotly.render', null, {
|
||||||
type: value,
|
type: value,
|
||||||
pivot: !!this.forPivot
|
pivot: !!this.forPivot
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import pivotHelper from './pivotHelper'
|
|||||||
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
|
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
|
||||||
import chartHelper from '@/lib/chartHelper'
|
import chartHelper from '@/lib/chartHelper'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
const ChartClass = Vue.extend(Chart)
|
const ChartClass = Vue.extend(Chart)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -96,7 +96,7 @@ export default {
|
|||||||
'update:importToSvgEnabled',
|
'update:importToSvgEnabled',
|
||||||
this.viewStandartChart || this.viewCustomChart
|
this.viewStandartChart || this.viewCustomChart
|
||||||
)
|
)
|
||||||
send('viz_pivot.render', undefined, {
|
events.send('viz_pivot.render', null, {
|
||||||
type: this.pivotOptions.rendererName
|
type: this.pivotOptions.rendererName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ import ClipboardIcon from '@/components/svg/clipboard'
|
|||||||
import cIo from '@/lib/utils/clipboardIo'
|
import cIo from '@/lib/utils/clipboardIo'
|
||||||
import loadingDialog from '@/components/LoadingDialog'
|
import loadingDialog from '@/components/LoadingDialog'
|
||||||
import time from '@/lib/utils/time'
|
import time from '@/lib/utils/time'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DataView',
|
name: 'DataView',
|
||||||
@@ -207,11 +207,11 @@ export default {
|
|||||||
eventLabels.pivot = this.plotlyInPivot
|
eventLabels.pivot = this.plotlyInPivot
|
||||||
}
|
}
|
||||||
|
|
||||||
send(
|
events.send(
|
||||||
this.mode === 'chart' || this.plotlyInPivot
|
this.mode === 'chart' || this.plotlyInPivot
|
||||||
? 'viz_plotly.export'
|
? 'viz_plotly.export'
|
||||||
: 'viz_pivot.export',
|
: 'viz_pivot.export',
|
||||||
undefined,
|
null,
|
||||||
eventLabels
|
eventLabels
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ import fIo from '@/lib/utils/fileIo'
|
|||||||
import cIo from '@/lib/utils/clipboardIo'
|
import cIo from '@/lib/utils/clipboardIo'
|
||||||
import time from '@/lib/utils/time'
|
import time from '@/lib/utils/time'
|
||||||
import loadingDialog from '@/components/LoadingDialog'
|
import loadingDialog from '@/components/LoadingDialog'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RunResult',
|
name: 'RunResult',
|
||||||
@@ -119,7 +119,7 @@ export default {
|
|||||||
|
|
||||||
exportToCsv () {
|
exportToCsv () {
|
||||||
if (this.result && this.result.values) {
|
if (this.result && this.result.values) {
|
||||||
send('resultset.export',
|
events.send('resultset.export',
|
||||||
this.result.values[this.result.columns[0]].length,
|
this.result.values[this.result.columns[0]].length,
|
||||||
{ to: 'csv' }
|
{ to: 'csv' }
|
||||||
)
|
)
|
||||||
@@ -130,7 +130,7 @@ export default {
|
|||||||
|
|
||||||
async prepareCopy () {
|
async prepareCopy () {
|
||||||
if (this.result && this.result.values) {
|
if (this.result && this.result.values) {
|
||||||
send('resultset.export',
|
events.send('resultset.export',
|
||||||
this.result.values[this.result.columns[0]].length,
|
this.result.values[this.result.columns[0]].length,
|
||||||
{ to: 'clipboard' }
|
{ to: 'clipboard' }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,14 +3,20 @@ import 'codemirror/addon/hint/show-hint.js'
|
|||||||
import 'codemirror/addon/hint/sql-hint.js'
|
import 'codemirror/addon/hint/sql-hint.js'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
|
function _getHintText (hint) {
|
||||||
|
return typeof hint === 'string' ? hint : hint.text
|
||||||
|
}
|
||||||
export function getHints (cm, options) {
|
export function getHints (cm, options) {
|
||||||
const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase()
|
|
||||||
const result = CM.hint.sql(cm, options)
|
const result = CM.hint.sql(cm, options)
|
||||||
|
|
||||||
// Don't show the hint if there is only one option
|
// Don't show the hint if there is only one option
|
||||||
// and the token is already completed with this option
|
// and the replacingText is already equals to this option
|
||||||
if (result.list.length === 1 && result.list[0].text.toUpperCase() === token) {
|
const replacedText = cm.getRange(result.from, result.to).toUpperCase()
|
||||||
|
if (result.list.length === 1 &&
|
||||||
|
_getHintText(result.list[0]).toUpperCase() === replacedText) {
|
||||||
result.list = []
|
result.list = []
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,44 +3,45 @@
|
|||||||
<splitpanes
|
<splitpanes
|
||||||
class="query-results-splitter"
|
class="query-results-splitter"
|
||||||
horizontal
|
horizontal
|
||||||
:before="{ size: 50, max: 100 }"
|
:before="{ size: topPaneSize, max: 100 }"
|
||||||
:after="{ size: 50, max: 100 }"
|
:after="{ size: 100 - topPaneSize, max: 100 }"
|
||||||
|
:default="{ before: 50, after: 50 }"
|
||||||
>
|
>
|
||||||
<template #left-pane>
|
<template #left-pane>
|
||||||
<div :id="'above-' + tabIndex" class="above" />
|
<div :id="'above-' + tab.id" class="above" />
|
||||||
</template>
|
</template>
|
||||||
<template #right-pane>
|
<template #right-pane>
|
||||||
<div :id="'bottom-'+ tabIndex" ref="bottomPane" class="bottomPane" />
|
<div :id="'bottom-'+ tab.id" ref="bottomPane" class="bottomPane" />
|
||||||
</template>
|
</template>
|
||||||
</splitpanes>
|
</splitpanes>
|
||||||
|
|
||||||
<div :id="'hidden-'+ tabIndex" class="hidden-part" />
|
<div :id="'hidden-'+ tab.id" class="hidden-part" />
|
||||||
|
|
||||||
<teleport :to="`#${layout.sqlEditor}-${tabIndex}`">
|
<teleport :to="`#${tab.layout.sqlEditor}-${tab.id}`">
|
||||||
<sql-editor
|
<sql-editor
|
||||||
ref="sqlEditor"
|
ref="sqlEditor"
|
||||||
v-model="query"
|
v-model="tab.query"
|
||||||
:is-getting-results="isGettingResults"
|
:is-getting-results="tab.isGettingResults"
|
||||||
@switchTo="onSwitchView('sqlEditor', $event)"
|
@switchTo="onSwitchView('sqlEditor', $event)"
|
||||||
@run="execute"
|
@run="tab.execute()"
|
||||||
/>
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
|
||||||
<teleport :to="`#${layout.table}-${tabIndex}`">
|
<teleport :to="`#${tab.layout.table}-${tab.id}`">
|
||||||
<run-result
|
<run-result
|
||||||
:result="result"
|
:result="tab.result"
|
||||||
:is-getting-results="isGettingResults"
|
:is-getting-results="tab.isGettingResults"
|
||||||
:error="error"
|
:error="tab.error"
|
||||||
:time="time"
|
:time="tab.time"
|
||||||
@switchTo="onSwitchView('table', $event)"
|
@switchTo="onSwitchView('table', $event)"
|
||||||
/>
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
|
||||||
<teleport :to="`#${layout.dataView}-${tabIndex}`">
|
<teleport :to="`#${tab.layout.dataView}-${tab.id}`">
|
||||||
<data-view
|
<data-view
|
||||||
:data-source="(result && result.values) || null"
|
:data-source="(tab.result && tab.result.values) || null"
|
||||||
:init-options="initViewOptions"
|
:init-options="tab.viewOptions"
|
||||||
:init-mode="initViewType"
|
:init-mode="tab.viewType"
|
||||||
ref="dataView"
|
ref="dataView"
|
||||||
@switchTo="onSwitchView('dataView', $event)"
|
@switchTo="onSwitchView('dataView', $event)"
|
||||||
@update="onDataViewUpdate"
|
@update="onDataViewUpdate"
|
||||||
@@ -54,15 +55,15 @@ import Splitpanes from '@/components/Splitpanes'
|
|||||||
import SqlEditor from './SqlEditor'
|
import SqlEditor from './SqlEditor'
|
||||||
import DataView from './DataView'
|
import DataView from './DataView'
|
||||||
import RunResult from './RunResult'
|
import RunResult from './RunResult'
|
||||||
import time from '@/lib/utils/time'
|
|
||||||
import Teleport from 'vue2-teleport'
|
import Teleport from 'vue2-teleport'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tab',
|
name: 'Tab',
|
||||||
props: [
|
props: {
|
||||||
'id', 'initName', 'initQuery', 'initViewOptions', 'tabIndex', 'isPredefined', 'initViewType'
|
tab: Object
|
||||||
],
|
},
|
||||||
components: {
|
components: {
|
||||||
SqlEditor,
|
SqlEditor,
|
||||||
DataView,
|
DataView,
|
||||||
@@ -72,21 +73,14 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
query: this.initQuery,
|
topPaneSize: this.tab.maximize
|
||||||
result: null,
|
? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0
|
||||||
isGettingResults: false,
|
: 50
|
||||||
error: null,
|
|
||||||
time: 0,
|
|
||||||
layout: {
|
|
||||||
sqlEditor: 'above',
|
|
||||||
table: 'bottom',
|
|
||||||
dataView: 'hidden'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isActive () {
|
isActive () {
|
||||||
return this.id === this.$store.state.currentTabId
|
return this.tab.id === this.$store.state.currentTabId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -94,54 +88,34 @@ export default {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
async handler () {
|
async handler () {
|
||||||
if (this.isActive) {
|
if (this.isActive) {
|
||||||
this.$store.commit('setCurrentTab', this)
|
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
this.$refs.sqlEditor.focus()
|
this.$refs.sqlEditor.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
query () {
|
'tab.query' () {
|
||||||
this.$store.commit('updateTab', { index: this.tabIndex, isSaved: false })
|
this.$store.commit('updateTab', {
|
||||||
|
tab: this.tab,
|
||||||
|
newValues: { isSaved: false }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.tab.dataView = this.$refs.dataView
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSwitchView (from, to) {
|
onSwitchView (from, to) {
|
||||||
const fromPosition = this.layout[from]
|
const fromPosition = this.tab.layout[from]
|
||||||
this.layout[from] = this.layout[to]
|
this.tab.layout[from] = this.tab.layout[to]
|
||||||
this.layout[to] = fromPosition
|
this.tab.layout[to] = fromPosition
|
||||||
|
|
||||||
send('inquiry.panel', undefined, { panel: to })
|
events.send('inquiry.panel', null, { panel: to })
|
||||||
},
|
},
|
||||||
onDataViewUpdate () {
|
onDataViewUpdate () {
|
||||||
this.$store.commit('updateTab', { index: this.tabIndex, isSaved: false })
|
this.$store.commit('updateTab', {
|
||||||
},
|
tab: this.tab,
|
||||||
async execute () {
|
newValues: { isSaved: false }
|
||||||
this.isGettingResults = true
|
})
|
||||||
this.result = null
|
|
||||||
this.error = null
|
|
||||||
const state = this.$store.state
|
|
||||||
try {
|
|
||||||
const start = new Date()
|
|
||||||
this.result = await state.db.execute(this.query + ';')
|
|
||||||
this.time = time.getPeriod(start, new Date())
|
|
||||||
|
|
||||||
if (this.result && this.result.values) {
|
|
||||||
send('resultset.create',
|
|
||||||
this.result.values[this.result.columns[0]].length
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
send('query.run', parseFloat(this.time), { status: 'success' })
|
|
||||||
} catch (err) {
|
|
||||||
this.error = {
|
|
||||||
type: 'error',
|
|
||||||
message: err
|
|
||||||
}
|
|
||||||
|
|
||||||
send('query.run', 0, { status: 'error' })
|
|
||||||
}
|
|
||||||
state.db.refreshSchema()
|
|
||||||
this.isGettingResults = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="selectTab(tab.id)"
|
@click="selectTab(tab.id)"
|
||||||
:class="[{'tab-selected': (tab.id === selectedIndex)}, 'tab']"
|
:class="[{'tab-selected': (tab.id === selectedTabId)}, 'tab']"
|
||||||
>
|
>
|
||||||
<div class="tab-name">
|
<div class="tab-name">
|
||||||
<span v-show="!tab.isSaved" class="star">*</span>
|
<span v-show="!tab.isSaved" class="star">*</span>
|
||||||
@@ -13,20 +13,14 @@
|
|||||||
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
|
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(index)"/>
|
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(tab)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<tab
|
<tab
|
||||||
v-for="(tab, index) in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:id="tab.id"
|
:tab="tab"
|
||||||
:init-name="tab.name"
|
|
||||||
:init-query="tab.query"
|
|
||||||
:init-view-options="tab.viewOptions"
|
|
||||||
:init-view-type="tab.viewType"
|
|
||||||
:is-predefined="tab.isPredefined"
|
|
||||||
:tab-index="index"
|
|
||||||
/>
|
/>
|
||||||
<div v-show="tabs.length === 0" id="start-guide">
|
<div v-show="tabs.length === 0" id="start-guide">
|
||||||
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
|
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
|
||||||
@@ -38,25 +32,25 @@
|
|||||||
<modal name="close-warn" classes="dialog" height="auto">
|
<modal name="close-warn" classes="dialog" height="auto">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
Close tab {{
|
Close tab {{
|
||||||
closingTabIndex !== null
|
closingTab !== null
|
||||||
? (tabs[closingTabIndex].name || `[${tabs[closingTabIndex].tempName}]`)
|
? (closingTab.name || `[${closingTab.tempName}]`)
|
||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
<close-icon @click="$modal.hide('close-warn')"/>
|
<close-icon @click="$modal.hide('close-warn')"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
You have unsaved changes. Save changes in {{
|
You have unsaved changes. Save changes in {{
|
||||||
closingTabIndex !== null
|
closingTab !== null
|
||||||
? (tabs[closingTabIndex].name || `[${tabs[closingTabIndex].tempName}]`)
|
? (closingTab.name || `[${closingTab.tempName}]`)
|
||||||
: ''
|
: ''
|
||||||
}} before closing?
|
}} before closing?
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-buttons-container">
|
<div class="dialog-buttons-container">
|
||||||
<button class="secondary" @click="closeTab(closingTabIndex)">
|
<button class="secondary" @click="closeTab(closingTab)">
|
||||||
Close without saving
|
Close without saving
|
||||||
</button>
|
</button>
|
||||||
<button class="secondary" @click="$modal.hide('close-warn')">Cancel</button>
|
<button class="secondary" @click="$modal.hide('close-warn')">Cancel</button>
|
||||||
<button class="primary" @click="saveAndClose(closingTabIndex)">Save and close</button>
|
<button class="primary" @click="saveAndClose(closingTab)">Save and close</button>
|
||||||
</div>
|
</div>
|
||||||
</modal>
|
</modal>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,14 +67,14 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
closingTabIndex: null
|
closingTab: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tabs () {
|
tabs () {
|
||||||
return this.$store.state.tabs
|
return this.$store.state.tabs
|
||||||
},
|
},
|
||||||
selectedIndex () {
|
selectedTabId () {
|
||||||
return this.$store.state.currentTabId
|
return this.$store.state.currentTabId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,25 +91,24 @@ export default {
|
|||||||
selectTab (id) {
|
selectTab (id) {
|
||||||
this.$store.commit('setCurrentTabId', id)
|
this.$store.commit('setCurrentTabId', id)
|
||||||
},
|
},
|
||||||
beforeCloseTab (index) {
|
beforeCloseTab (tab) {
|
||||||
this.closingTabIndex = index
|
this.closingTab = tab
|
||||||
if (!this.tabs[index].isSaved) {
|
if (!tab.isSaved) {
|
||||||
this.$modal.show('close-warn')
|
this.$modal.show('close-warn')
|
||||||
} else {
|
} else {
|
||||||
this.closeTab(index)
|
this.closeTab(tab)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeTab (index) {
|
closeTab (tab) {
|
||||||
this.$modal.hide('close-warn')
|
this.$modal.hide('close-warn')
|
||||||
this.closingTabIndex = null
|
this.$store.commit('deleteTab', tab)
|
||||||
this.$store.commit('deleteTab', index)
|
|
||||||
},
|
},
|
||||||
saveAndClose (index) {
|
saveAndClose (tab) {
|
||||||
this.$root.$on('inquirySaved', () => {
|
this.$root.$on('inquirySaved', () => {
|
||||||
this.closeTab(index)
|
this.closeTab(tab)
|
||||||
this.$root.$off('inquirySaved')
|
this.$root.$off('inquirySaved')
|
||||||
})
|
})
|
||||||
this.selectTab(this.tabs[index].id)
|
this.selectTab(tab.id)
|
||||||
this.$modal.hide('close-warn')
|
this.$modal.hide('close-warn')
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$root.$emit('saveInquiry')
|
this.$root.$emit('saveInquiry')
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<splitpanes
|
<splitpanes
|
||||||
class="schema-tabs-splitter"
|
class="schema-tabs-splitter"
|
||||||
:before="{ size: 20, max: 30 }"
|
:before="{ size: schemaWidth, max: 30 }"
|
||||||
:after="{ size: 80, max: 100 }"
|
:after="{ size: 100 - schemaWidth, max: 100 }"
|
||||||
|
:default="{ before: 20, after: 80 }"
|
||||||
>
|
>
|
||||||
<template #left-pane>
|
<template #left-pane>
|
||||||
<schema/>
|
<schema/>
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
import Splitpanes from '@/components/Splitpanes'
|
import Splitpanes from '@/components/Splitpanes'
|
||||||
import Schema from './Schema'
|
import Schema from './Schema'
|
||||||
import Tabs from './Tabs'
|
import Tabs from './Tabs'
|
||||||
import { send } from '@/lib/utils/events'
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Workspace',
|
name: 'Workspace',
|
||||||
@@ -28,9 +29,14 @@ export default {
|
|||||||
Splitpanes,
|
Splitpanes,
|
||||||
Tabs
|
Tabs
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
schemaWidth: this.$route.query.hide_schema === '1' ? 0 : 20
|
||||||
|
}
|
||||||
|
},
|
||||||
async beforeCreate () {
|
async beforeCreate () {
|
||||||
const schema = this.$store.state.db.schema
|
const schema = this.$store.state.db.schema
|
||||||
if (!schema || schema.length === 0) {
|
if ((!schema || schema.length === 0) && this.$store.state.tabs.length === 0) {
|
||||||
const stmt = [
|
const stmt = [
|
||||||
'/*',
|
'/*',
|
||||||
' * Your database is empty. In order to start building charts',
|
' * Your database is empty. In order to start building charts',
|
||||||
@@ -51,7 +57,7 @@ export default {
|
|||||||
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
||||||
this.$store.commit('setCurrentTabId', tabId)
|
this.$store.commit('setCurrentTabId', tabId)
|
||||||
|
|
||||||
send('inquiry.create', undefined, { auto: true })
|
events.send('inquiry.create', null, { auto: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('Splitpanes.vue', () => {
|
|||||||
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.height).to.equal('40%')
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.height).to.equal('40%')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toggles correctly', async () => {
|
it('toggles correctly - no maximized initially', async () => {
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(Splitpanes, {
|
const wrapper = shallowMount(Splitpanes, {
|
||||||
slots: {
|
slots: {
|
||||||
@@ -70,6 +70,64 @@ describe('Splitpanes.vue', () => {
|
|||||||
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('40%')
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('40%')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('toggles correctly - with maximized initially', async () => {
|
||||||
|
// mount the component
|
||||||
|
let wrapper = shallowMount(Splitpanes, {
|
||||||
|
slots: {
|
||||||
|
leftPane: '<div />',
|
||||||
|
rightPane: '<div />'
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
before: { size: 0, max: 100 },
|
||||||
|
after: { size: 100, max: 100 },
|
||||||
|
default: { before: 20, after: 80 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.toggle-btn').trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('20%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('80%')
|
||||||
|
|
||||||
|
await wrapper.findAll('.toggle-btn').at(0).trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('0%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('100%')
|
||||||
|
|
||||||
|
await wrapper.find('.toggle-btn').trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('20%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('80%')
|
||||||
|
|
||||||
|
await wrapper.findAll('.toggle-btn').at(1).trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('100%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('0%')
|
||||||
|
|
||||||
|
wrapper = shallowMount(Splitpanes, {
|
||||||
|
slots: {
|
||||||
|
leftPane: '<div />',
|
||||||
|
rightPane: '<div />'
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
before: { size: 100, max: 100 },
|
||||||
|
after: { size: 0, max: 100 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.toggle-btn').trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('50%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('50%')
|
||||||
|
|
||||||
|
await wrapper.findAll('.toggle-btn').at(0).trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('0%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('100%')
|
||||||
|
|
||||||
|
await wrapper.find('.toggle-btn').trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('50%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('50%')
|
||||||
|
|
||||||
|
await wrapper.findAll('.toggle-btn').at(1).trigger('click')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('100%')
|
||||||
|
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('0%')
|
||||||
|
})
|
||||||
|
|
||||||
it('drag - vertical', async () => {
|
it('drag - vertical', async () => {
|
||||||
const root = document.createElement('div')
|
const root = document.createElement('div')
|
||||||
const place = document.createElement('div')
|
const place = document.createElement('div')
|
||||||
|
|||||||
@@ -160,39 +160,39 @@ describe('SQLite extensions', function () {
|
|||||||
it('supports transitive_closure', async function () {
|
it('supports transitive_closure', async function () {
|
||||||
const actual = await db.execute(`
|
const actual = await db.execute(`
|
||||||
CREATE TABLE node(
|
CREATE TABLE node(
|
||||||
node_id INTEGER NOT NULL PRIMARY KEY,
|
node_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
parent_id INTEGER,
|
parent_id INTEGER,
|
||||||
name VARCHAR(127),
|
name VARCHAR(127),
|
||||||
FOREIGN KEY (parent_id) REFERENCES node(node_id)
|
FOREIGN KEY (parent_id) REFERENCES node(node_id)
|
||||||
);
|
|
||||||
CREATE INDEX node_parent_id_idx ON node(parent_id);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE node_closure USING transitive_closure(
|
|
||||||
tablename = "node",
|
|
||||||
idcolumn = "node_id",
|
|
||||||
parentcolumn = "parent_id"
|
|
||||||
);
|
);
|
||||||
|
CREATE INDEX node_parent_id_idx ON node(parent_id);
|
||||||
|
|
||||||
INSERT INTO node VALUES
|
CREATE VIRTUAL TABLE node_closure USING transitive_closure(
|
||||||
(1, NULL, 'tests'),
|
tablename = "node",
|
||||||
(2, 1, 'lib'),
|
idcolumn = "node_id",
|
||||||
(3, 2, 'database'),
|
parentcolumn = "parent_id"
|
||||||
(4, 2, 'utils'),
|
);
|
||||||
(5, 2, 'storedQueries.spec.js'),
|
|
||||||
(6, 3, '_sql.spec.js'),
|
|
||||||
(7, 3, '_statements.spec.js'),
|
|
||||||
(8, 3, 'database.spec.js'),
|
|
||||||
(9, 3, 'sqliteExtensions.spec.js'),
|
|
||||||
(10, 4, 'fileIo.spec.js'),
|
|
||||||
(11, 4, 'time.spec.js');
|
|
||||||
|
|
||||||
SELECT name
|
INSERT INTO node VALUES
|
||||||
FROM node
|
(1, NULL, 'tests'),
|
||||||
WHERE node_id IN (
|
(2, 1, 'lib'),
|
||||||
SELECT nc.id
|
(3, 2, 'database'),
|
||||||
FROM node_closure AS nc
|
(4, 2, 'utils'),
|
||||||
WHERE nc.root = 2 AND nc.depth = 2
|
(5, 2, 'storedQueries.spec.js'),
|
||||||
);
|
(6, 3, '_sql.spec.js'),
|
||||||
|
(7, 3, '_statements.spec.js'),
|
||||||
|
(8, 3, 'database.spec.js'),
|
||||||
|
(9, 3, 'sqliteExtensions.spec.js'),
|
||||||
|
(10, 4, 'fileIo.spec.js'),
|
||||||
|
(11, 4, 'time.spec.js');
|
||||||
|
|
||||||
|
SELECT name
|
||||||
|
FROM node
|
||||||
|
WHERE node_id IN (
|
||||||
|
SELECT nc.id
|
||||||
|
FROM node_closure AS nc
|
||||||
|
WHERE nc.root = 2 AND nc.depth = 2
|
||||||
|
);
|
||||||
`)
|
`)
|
||||||
expect(actual.values).to.eql({
|
expect(actual.values).to.eql({
|
||||||
name: [
|
name: [
|
||||||
@@ -293,7 +293,7 @@ describe('SQLite extensions', function () {
|
|||||||
|
|
||||||
it('supports decimal', async function () {
|
it('supports decimal', async function () {
|
||||||
const actual = await db.execute(`
|
const actual = await db.execute(`
|
||||||
select
|
SELECT
|
||||||
decimal_add(decimal('0.1'), decimal('0.2')) "add",
|
decimal_add(decimal('0.1'), decimal('0.2')) "add",
|
||||||
decimal_sub(0.2, 0.1) sub,
|
decimal_sub(0.2, 0.1) sub,
|
||||||
decimal_mul(power(2, 69), 2) mul,
|
decimal_mul(power(2, 69), 2) mul,
|
||||||
@@ -430,4 +430,29 @@ describe('SQLite extensions', function () {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('supports pearson', async function () {
|
||||||
|
const actual = await db.execute(`
|
||||||
|
CREATE TABLE dataset(x REAL, y REAL, z REAL);
|
||||||
|
INSERT INTO dataset VALUES
|
||||||
|
(5,3,3.2), (5,6,4.3), (5,9,5.4),
|
||||||
|
(10,3,4), (10,6,3.8), (10,9,3.6),
|
||||||
|
(15,3,4.8), (15,6,4), (15,9,3.5);
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pearson(x, x) xx,
|
||||||
|
pearson(x, y) xy,
|
||||||
|
abs(-0.12666 - pearson(x, z)) < 0.00001 xz,
|
||||||
|
pearson(y, x) yx,
|
||||||
|
pearson(y, y) yy,
|
||||||
|
abs(0.10555 - pearson(y, z)) < 0.00001 yz,
|
||||||
|
abs(-0.12666 - pearson(z, x)) < 0.00001 zx,
|
||||||
|
abs(0.10555 - pearson(z, y)) < 0.00001 zy,
|
||||||
|
pearson(z, z) zz
|
||||||
|
FROM dataset;
|
||||||
|
`)
|
||||||
|
expect(actual.values).to.eql({
|
||||||
|
xx: [1], xy: [0], xz: [1], yx: [0], yy: [1], yz: [1], zx: [1], zy: [1], zz: [1]
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -87,14 +87,14 @@ describe('storedInquiries.js', () => {
|
|||||||
|
|
||||||
it('isTabNeedName returns false when the inquiry has a name and is not predefined', () => {
|
it('isTabNeedName returns false when the inquiry has a name and is not predefined', () => {
|
||||||
const tab = {
|
const tab = {
|
||||||
initName: 'foo'
|
name: 'foo'
|
||||||
}
|
}
|
||||||
expect(storedInquiries.isTabNeedName(tab)).to.equal(false)
|
expect(storedInquiries.isTabNeedName(tab)).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isTabNeedName returns true when the inquiry has no name and is not predefined', () => {
|
it('isTabNeedName returns true when the inquiry has no name and is not predefined', () => {
|
||||||
const tab = {
|
const tab = {
|
||||||
initName: null,
|
name: null,
|
||||||
tempName: 'Untitled'
|
tempName: 'Untitled'
|
||||||
}
|
}
|
||||||
expect(storedInquiries.isTabNeedName(tab)).to.equal(true)
|
expect(storedInquiries.isTabNeedName(tab)).to.equal(true)
|
||||||
@@ -102,7 +102,7 @@ describe('storedInquiries.js', () => {
|
|||||||
|
|
||||||
it('isTabNeedName returns true when the inquiry is predefined', () => {
|
it('isTabNeedName returns true when the inquiry is predefined', () => {
|
||||||
const tab = {
|
const tab = {
|
||||||
initName: 'foo',
|
name: 'foo',
|
||||||
isPredefined: true
|
isPredefined: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,14 +351,13 @@ describe('storedInquiries.js', () => {
|
|||||||
query: 'select * from foo',
|
query: 'select * from foo',
|
||||||
viewType: 'chart',
|
viewType: 'chart',
|
||||||
viewOptions: [],
|
viewOptions: [],
|
||||||
initName: null,
|
name: null,
|
||||||
$refs: {
|
dataView: {
|
||||||
dataView: {
|
getOptionsForSave () {
|
||||||
getOptionsForSave () {
|
return ['chart']
|
||||||
return ['chart']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
const value = storedInquiries.save(tab, 'foo')
|
const value = storedInquiries.save(tab, 'foo')
|
||||||
expect(value.id).to.equal(tab.id)
|
expect(value.id).to.equal(tab.id)
|
||||||
@@ -376,19 +375,18 @@ describe('storedInquiries.js', () => {
|
|||||||
query: 'select * from foo',
|
query: 'select * from foo',
|
||||||
viewType: 'chart',
|
viewType: 'chart',
|
||||||
viewOptions: [],
|
viewOptions: [],
|
||||||
initName: null,
|
name: null,
|
||||||
$refs: {
|
dataView: {
|
||||||
dataView: {
|
getOptionsForSave () {
|
||||||
getOptionsForSave () {
|
return ['chart']
|
||||||
return ['chart']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const first = storedInquiries.save(tab, 'foo')
|
const first = storedInquiries.save(tab, 'foo')
|
||||||
|
|
||||||
tab.initName = 'foo'
|
tab.name = 'foo'
|
||||||
tab.query = 'select * from foo'
|
tab.query = 'select * from foo'
|
||||||
storedInquiries.save(tab)
|
storedInquiries.save(tab)
|
||||||
const inquiries = storedInquiries.getStoredInquiries()
|
const inquiries = storedInquiries.getStoredInquiries()
|
||||||
@@ -409,12 +407,10 @@ describe('storedInquiries.js', () => {
|
|||||||
query: 'select * from foo',
|
query: 'select * from foo',
|
||||||
viewType: 'chart',
|
viewType: 'chart',
|
||||||
viewOptions: [],
|
viewOptions: [],
|
||||||
initName: 'foo predefined',
|
name: 'foo predefined',
|
||||||
$refs: {
|
dataView: {
|
||||||
dataView: {
|
getOptionsForSave () {
|
||||||
getOptionsForSave () {
|
return ['chart']
|
||||||
return ['chart']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isPredefined: true
|
isPredefined: true
|
||||||
|
|||||||
189
tests/lib/tab.spec.js
Normal file
189
tests/lib/tab.spec.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import Tab from '@/lib/tab.js'
|
||||||
|
|
||||||
|
describe('tab.js', () => {
|
||||||
|
it('Creates a tab for new inquiry', () => {
|
||||||
|
const state = {
|
||||||
|
untitledLastIndex: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state)
|
||||||
|
expect(newTab).to.include({
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled 5',
|
||||||
|
query: undefined,
|
||||||
|
viewOptions: undefined,
|
||||||
|
isPredefined: undefined,
|
||||||
|
viewType: 'chart',
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: false,
|
||||||
|
state: state
|
||||||
|
})
|
||||||
|
expect(newTab.layout).to.include({
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
})
|
||||||
|
expect(newTab.id).to.have.lengthOf(21)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Creates a tab for existing inquiry', () => {
|
||||||
|
const state = {
|
||||||
|
untitledLastIndex: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
const inquiry = {
|
||||||
|
id: 'qwerty',
|
||||||
|
query: 'SELECT * from foo',
|
||||||
|
viewType: 'pivot',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
createdAt: '2022-12-05T18:30:30'
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state, inquiry)
|
||||||
|
expect(newTab).to.include({
|
||||||
|
id: 'qwerty',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
tempName: 'Foo inquiry',
|
||||||
|
query: 'SELECT * from foo',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
isPredefined: undefined,
|
||||||
|
viewType: 'pivot',
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true,
|
||||||
|
state: state
|
||||||
|
})
|
||||||
|
expect(newTab.layout).to.include({
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Set isGettingResults true when execute', async () => {
|
||||||
|
let resolveQuering
|
||||||
|
// mock store state
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1,
|
||||||
|
dbName: 'fooDb',
|
||||||
|
db: {
|
||||||
|
execute: sinon.stub().returns(new Promise(resolve => {
|
||||||
|
resolveQuering = resolve
|
||||||
|
})),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state, {
|
||||||
|
id: 'qwerty',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'cart',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
createdAt: '2022-12-05T18:30:30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(newTab.isGettingResults).to.equal(false)
|
||||||
|
newTab.execute()
|
||||||
|
expect(newTab.isGettingResults).to.equal(true)
|
||||||
|
resolveQuering()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Updates result with query execution result', async () => {
|
||||||
|
const result = {
|
||||||
|
columns: ['id', 'name'],
|
||||||
|
values: {
|
||||||
|
id: [1, 2],
|
||||||
|
name: ['Harry', 'Drako']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock store state
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1,
|
||||||
|
dbName: 'fooDb',
|
||||||
|
db: {
|
||||||
|
execute: sinon.stub().resolves(result),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state, {
|
||||||
|
id: 'qwerty',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'cart',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
createdAt: '2022-12-05T18:30:30'
|
||||||
|
})
|
||||||
|
|
||||||
|
await newTab.execute()
|
||||||
|
expect(newTab.isGettingResults).to.equal(false)
|
||||||
|
expect(newTab.result).to.eql(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Updates error with query execution error', async () => {
|
||||||
|
// mock store state
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1,
|
||||||
|
dbName: 'fooDb',
|
||||||
|
db: {
|
||||||
|
execute: sinon.stub().rejects(new Error('No such table')),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state, {
|
||||||
|
id: 'qwerty',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'cart',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
createdAt: '2022-12-05T18:30:30'
|
||||||
|
})
|
||||||
|
|
||||||
|
await newTab.execute()
|
||||||
|
expect(newTab.error.type).to.eql('error')
|
||||||
|
expect(newTab.error.message.toString()).to.equal('Error: No such table')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Updates schema after query execution', async () => {
|
||||||
|
const result = {
|
||||||
|
columns: ['id', 'name'],
|
||||||
|
values: {
|
||||||
|
id: [],
|
||||||
|
name: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock store state
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1,
|
||||||
|
dbName: 'fooDb',
|
||||||
|
db: {
|
||||||
|
execute: sinon.stub().resolves(result),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = new Tab(state, {
|
||||||
|
id: 'qwerty',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'cart',
|
||||||
|
viewOptions: 'this is view options object',
|
||||||
|
name: 'Foo inquiry',
|
||||||
|
createdAt: '2022-12-05T18:30:30'
|
||||||
|
})
|
||||||
|
|
||||||
|
await newTab.execute()
|
||||||
|
expect(state.db.refreshSchema.calledOnce).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -11,7 +11,7 @@ describe('actions', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = await addTab({ state })
|
let id = await addTab({ state })
|
||||||
expect(state.tabs[0]).to.eql({
|
expect(state.tabs[0]).to.include({
|
||||||
id: id,
|
id: id,
|
||||||
name: null,
|
name: null,
|
||||||
tempName: 'Untitled',
|
tempName: 'Untitled',
|
||||||
@@ -22,7 +22,7 @@ describe('actions', () => {
|
|||||||
expect(state.untitledLastIndex).to.equal(1)
|
expect(state.untitledLastIndex).to.equal(1)
|
||||||
|
|
||||||
id = await addTab({ state })
|
id = await addTab({ state })
|
||||||
expect(state.tabs[1]).to.eql({
|
expect(state.tabs[1]).to.include({
|
||||||
id: id,
|
id: id,
|
||||||
name: null,
|
name: null,
|
||||||
tempName: 'Untitled 1',
|
tempName: 'Untitled 1',
|
||||||
@@ -41,14 +41,13 @@ describe('actions', () => {
|
|||||||
const tab = {
|
const tab = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'test',
|
name: 'test',
|
||||||
tempName: null,
|
|
||||||
query: 'SELECT * from foo',
|
query: 'SELECT * from foo',
|
||||||
viewType: 'chart',
|
viewType: 'chart',
|
||||||
viewOptions: {},
|
viewOptions: 'an object with view options',
|
||||||
isSaved: true
|
isSaved: true
|
||||||
}
|
}
|
||||||
await addTab({ state }, tab)
|
await addTab({ state }, tab)
|
||||||
expect(state.tabs[0]).to.eql(tab)
|
expect(state.tabs[0]).to.include(tab)
|
||||||
expect(state.untitledLastIndex).to.equal(0)
|
expect(state.untitledLastIndex).to.equal(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ const {
|
|||||||
updateTab,
|
updateTab,
|
||||||
deleteTab,
|
deleteTab,
|
||||||
setCurrentTabId,
|
setCurrentTabId,
|
||||||
setCurrentTab,
|
|
||||||
updatePredefinedInquiries,
|
updatePredefinedInquiries,
|
||||||
setDb
|
setDb,
|
||||||
|
setLoadingPredefinedInquiries,
|
||||||
|
setPredefinedInquiriesLoaded
|
||||||
} = mutations
|
} = mutations
|
||||||
|
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
@@ -35,8 +36,7 @@ describe('mutations', () => {
|
|||||||
isPredefined: false
|
isPredefined: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTab = {
|
const newValues = {
|
||||||
index: 0,
|
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'new test',
|
name: 'new test',
|
||||||
query: 'SELECT * from bar',
|
query: 'SELECT * from bar',
|
||||||
@@ -49,7 +49,7 @@ describe('mutations', () => {
|
|||||||
tabs: [tab]
|
tabs: [tab]
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTab(state, newTab)
|
updateTab(state, { tab, newValues })
|
||||||
expect(state.tabs[0]).to.eql({
|
expect(state.tabs[0]).to.eql({
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'new test',
|
name: 'new test',
|
||||||
@@ -73,8 +73,7 @@ describe('mutations', () => {
|
|||||||
isPredefined: true
|
isPredefined: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTab = {
|
const newValues = {
|
||||||
index: 0,
|
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'new test',
|
name: 'new test',
|
||||||
query: 'SELECT * from bar',
|
query: 'SELECT * from bar',
|
||||||
@@ -88,7 +87,7 @@ describe('mutations', () => {
|
|||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTab(state, newTab)
|
updateTab(state, { tab, newValues })
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
expect(state.tabs).to.have.lengthOf(1)
|
||||||
expect(state.currentTabId).to.equal(2)
|
expect(state.currentTabId).to.equal(2)
|
||||||
expect(state.tabs[0].id).to.equal(2)
|
expect(state.tabs[0].id).to.equal(2)
|
||||||
@@ -109,8 +108,7 @@ describe('mutations', () => {
|
|||||||
isSaved: false
|
isSaved: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTab = {
|
const newValues = {
|
||||||
index: 0,
|
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'new test'
|
name: 'new test'
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,7 @@ describe('mutations', () => {
|
|||||||
tabs: [tab]
|
tabs: [tab]
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTab(state, newTab)
|
updateTab(state, { tab, newValues })
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
expect(state.tabs).to.have.lengthOf(1)
|
||||||
expect(state.tabs[0].id).to.equal(1)
|
expect(state.tabs[0].id).to.equal(1)
|
||||||
expect(state.tabs[0].name).to.equal('new test')
|
expect(state.tabs[0].name).to.equal('new test')
|
||||||
@@ -139,8 +137,7 @@ describe('mutations', () => {
|
|||||||
isPredefined: true
|
isPredefined: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTab = {
|
const newValues = {
|
||||||
index: 0,
|
|
||||||
isSaved: false
|
isSaved: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +145,7 @@ describe('mutations', () => {
|
|||||||
tabs: [tab]
|
tabs: [tab]
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTab(state, newTab)
|
updateTab(state, { tab, newValues })
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
expect(state.tabs).to.have.lengthOf(1)
|
||||||
expect(state.tabs[0].id).to.equal(1)
|
expect(state.tabs[0].id).to.equal(1)
|
||||||
expect(state.tabs[0].name).to.equal('test')
|
expect(state.tabs[0].name).to.equal('test')
|
||||||
@@ -182,7 +179,7 @@ describe('mutations', () => {
|
|||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTab(state, 0)
|
deleteTab(state, tab1)
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
expect(state.tabs).to.have.lengthOf(1)
|
||||||
expect(state.tabs[0].id).to.equal(2)
|
expect(state.tabs[0].id).to.equal(2)
|
||||||
expect(state.currentTabId).to.equal(2)
|
expect(state.currentTabId).to.equal(2)
|
||||||
@@ -214,7 +211,7 @@ describe('mutations', () => {
|
|||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTab(state, 1)
|
deleteTab(state, tab2)
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
expect(state.tabs).to.have.lengthOf(1)
|
||||||
expect(state.tabs[0].id).to.equal(1)
|
expect(state.tabs[0].id).to.equal(1)
|
||||||
expect(state.currentTabId).to.equal(1)
|
expect(state.currentTabId).to.equal(1)
|
||||||
@@ -256,7 +253,7 @@ describe('mutations', () => {
|
|||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTab(state, 1)
|
deleteTab(state, tab2)
|
||||||
expect(state.tabs).to.have.lengthOf(2)
|
expect(state.tabs).to.have.lengthOf(2)
|
||||||
expect(state.tabs[0].id).to.equal(1)
|
expect(state.tabs[0].id).to.equal(1)
|
||||||
expect(state.tabs[1].id).to.equal(3)
|
expect(state.tabs[1].id).to.equal(3)
|
||||||
@@ -279,45 +276,14 @@ describe('mutations', () => {
|
|||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTab(state, 0)
|
deleteTab(state, tab1)
|
||||||
expect(state.tabs).to.have.lengthOf(0)
|
expect(state.tabs).to.have.lengthOf(0)
|
||||||
expect(state.currentTabId).to.equal(null)
|
expect(state.currentTabId).to.equal(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deleteTab - not opened', () => {
|
|
||||||
const tab1 = {
|
|
||||||
id: 1,
|
|
||||||
name: 'foo',
|
|
||||||
tempName: null,
|
|
||||||
query: 'SELECT * from foo',
|
|
||||||
viewType: 'chart',
|
|
||||||
viewOptions: {},
|
|
||||||
isSaved: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const tab2 = {
|
|
||||||
id: 2,
|
|
||||||
name: 'bar',
|
|
||||||
tempName: null,
|
|
||||||
query: 'SELECT * from bar',
|
|
||||||
viewType: 'chart',
|
|
||||||
viewOptions: {},
|
|
||||||
isSaved: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
tabs: [tab1, tab2],
|
|
||||||
currentTabId: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTab(state, 1)
|
|
||||||
expect(state.tabs).to.have.lengthOf(1)
|
|
||||||
expect(state.tabs[0].id).to.equal(1)
|
|
||||||
expect(state.currentTabId).to.equal(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('setCurrentTabId', () => {
|
it('setCurrentTabId', () => {
|
||||||
const state = {
|
const state = {
|
||||||
|
tabs: [{ id: 1 }, { id: 2 }],
|
||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,15 +291,6 @@ describe('mutations', () => {
|
|||||||
expect(state.currentTabId).to.equal(2)
|
expect(state.currentTabId).to.equal(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('setCurrentTab', () => {
|
|
||||||
const state = {
|
|
||||||
currentTab: { id: 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentTab(state, { id: 2 })
|
|
||||||
expect(state.currentTab).to.eql({ id: 2 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updatePredefinedInquiries - single', () => {
|
it('updatePredefinedInquiries - single', () => {
|
||||||
const inquiry = {
|
const inquiry = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -377,4 +334,22 @@ describe('mutations', () => {
|
|||||||
updatePredefinedInquiries(state, inquiries)
|
updatePredefinedInquiries(state, inquiries)
|
||||||
expect(state.predefinedInquiries).to.eql(inquiries)
|
expect(state.predefinedInquiries).to.eql(inquiries)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('setLoadingPredefinedInquiries', () => {
|
||||||
|
const state = {
|
||||||
|
loadingPredefinedInquiries: false
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingPredefinedInquiries(state, true)
|
||||||
|
expect(state.loadingPredefinedInquiries).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('setPredefinedInquiriesLoaded', () => {
|
||||||
|
const state = {
|
||||||
|
predefinedInquiriesLoaded: false
|
||||||
|
}
|
||||||
|
|
||||||
|
setPredefinedInquiriesLoaded(state, true)
|
||||||
|
expect(state.predefinedInquiriesLoaded).to.equal(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
147
tests/views/LoadView.spec.js
Normal file
147
tests/views/LoadView.spec.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import LoadView from '@/views/LoadView'
|
||||||
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
import database from '@/lib/database'
|
||||||
|
import realMutations from '@/store/mutations'
|
||||||
|
import realActions from '@/store/actions'
|
||||||
|
import flushPromises from 'flush-promises'
|
||||||
|
import Tab from '@/lib/tab'
|
||||||
|
|
||||||
|
describe('LoadView.vue', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Loads db and inquiries and redirects to workspace if no errors', async () => {
|
||||||
|
const state = {
|
||||||
|
tabs: []
|
||||||
|
}
|
||||||
|
const mutations = {
|
||||||
|
setCurrentTabId: sinon.stub().callsFake(realMutations.setCurrentTabId),
|
||||||
|
setDb: sinon.stub().callsFake(realMutations.setDb)
|
||||||
|
}
|
||||||
|
const actions = {
|
||||||
|
addTab: sinon.stub().callsFake(realActions.addTab)
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, mutations, actions })
|
||||||
|
const $route = {
|
||||||
|
path: '/workspace',
|
||||||
|
query: {
|
||||||
|
data_url: 'https://my-url/test.db',
|
||||||
|
data_format: 'sqlite',
|
||||||
|
inquiry_url: 'https://my-url/test_inquiries.json',
|
||||||
|
inquiry_id: [1],
|
||||||
|
maximize: 'dataView'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const $router = { push: sinon.stub() }
|
||||||
|
|
||||||
|
const readFile = sinon.stub(fu, 'readFile')
|
||||||
|
const dataRes = new Response()
|
||||||
|
dataRes.blob = sinon.stub().resolves({})
|
||||||
|
readFile.onCall(0).returns(Promise.resolve(dataRes))
|
||||||
|
|
||||||
|
const inquiriesRes = new Response()
|
||||||
|
inquiriesRes.json = sinon.stub().resolves({
|
||||||
|
version: 2,
|
||||||
|
inquiries: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }]
|
||||||
|
})
|
||||||
|
readFile.onCall(1).returns(Promise.resolve(inquiriesRes))
|
||||||
|
const db = {
|
||||||
|
loadDb: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
sinon.stub(database, 'getNewDatabase').returns(db)
|
||||||
|
Tab.prototype.execute = sinon.stub()
|
||||||
|
|
||||||
|
const wrapper = mount(LoadView, {
|
||||||
|
store,
|
||||||
|
mocks: { $route, $router },
|
||||||
|
stubs: ['router-link']
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// DB file is read
|
||||||
|
expect(fu.readFile.firstCall.args[0]).to.equal('https://my-url/test.db')
|
||||||
|
|
||||||
|
// Db is loaded
|
||||||
|
expect(db.loadDb.firstCall.args[0]).to.equal(await dataRes.blob.returnValues[0])
|
||||||
|
|
||||||
|
// Inquiries file is read
|
||||||
|
expect(fu.readFile.secondCall.args[0])
|
||||||
|
.to.equal('https://my-url/test_inquiries.json')
|
||||||
|
|
||||||
|
// Tab for inquiry is created
|
||||||
|
expect(actions.addTab.calledOnce).to.equal(true)
|
||||||
|
expect(actions.addTab.firstCall.args[1]).eql({
|
||||||
|
id: undefined,
|
||||||
|
name: 'foo',
|
||||||
|
layout: {
|
||||||
|
dataView: 'bottom',
|
||||||
|
sqlEditor: 'hidden',
|
||||||
|
table: 'above'
|
||||||
|
},
|
||||||
|
maximize: 'dataView'
|
||||||
|
})
|
||||||
|
const executedTab = Tab.prototype.execute.firstCall.thisValue
|
||||||
|
expect(executedTab.tempName).to.equal('foo')
|
||||||
|
expect(wrapper.find('#open-workspace-btn').exists()).to.equal(false)
|
||||||
|
expect($router.push.called).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Doesn\'t redirect and show the button if there is an error', async () => {
|
||||||
|
const state = {
|
||||||
|
tabs: []
|
||||||
|
}
|
||||||
|
const mutations = {
|
||||||
|
setCurrentTabId: sinon.stub().callsFake(realMutations.setCurrentTabId),
|
||||||
|
setDb: sinon.stub().callsFake(realMutations.setDb)
|
||||||
|
}
|
||||||
|
const actions = {
|
||||||
|
addTab: sinon.stub().callsFake(realActions.addTab)
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, mutations, actions })
|
||||||
|
const $route = {
|
||||||
|
path: '/workspace',
|
||||||
|
query: {
|
||||||
|
data_url: 'https://my-url/test.db',
|
||||||
|
data_format: 'sqlite',
|
||||||
|
inquiry_url: 'https://my-url/test_inquiries.json',
|
||||||
|
inquiry_id: [1],
|
||||||
|
maximize: 'dataView'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const $router = { push: sinon.stub() }
|
||||||
|
|
||||||
|
const readFile = sinon.stub(fu, 'readFile')
|
||||||
|
const dataRes = new Response()
|
||||||
|
dataRes.blob = sinon.stub().rejects(new Error('Something is wrong'))
|
||||||
|
readFile.onCall(0).returns(Promise.resolve(dataRes))
|
||||||
|
|
||||||
|
const inquiriesRes = new Response()
|
||||||
|
inquiriesRes.json = sinon.stub().resolves({
|
||||||
|
version: 2,
|
||||||
|
inquiries: [{ id: 1 }]
|
||||||
|
})
|
||||||
|
readFile.onCall(1).returns(Promise.resolve(inquiriesRes))
|
||||||
|
sinon.stub(database, 'getNewDatabase').returns({
|
||||||
|
loadDb: sinon.stub().resolves()
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapper = mount(LoadView, {
|
||||||
|
store,
|
||||||
|
mocks: { $route, $router },
|
||||||
|
stubs: ['router-link']
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.find('#open-workspace-btn').exists()).to.equal(true)
|
||||||
|
expect($router.push.called).to.equal(false)
|
||||||
|
expect(wrapper.find('#logs').text()).to.include('Something is wrong')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -19,7 +19,9 @@ describe('Inquiries.vue', () => {
|
|||||||
predefinedInquiries: []
|
predefinedInquiries: []
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
updatePredefinedInquiries: sinon.stub()
|
setPredefinedInquiriesLoaded: sinon.stub(),
|
||||||
|
updatePredefinedInquiries: sinon.stub(),
|
||||||
|
setLoadingPredefinedInquiries: sinon.stub()
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
const wrapper = shallowMount(Inquiries, { store })
|
const wrapper = shallowMount(Inquiries, { store })
|
||||||
@@ -327,6 +329,7 @@ describe('Inquiries.vue', () => {
|
|||||||
sinon.stub(storedInquiries, 'getStoredInquiries').returns([inquiryInStorage])
|
sinon.stub(storedInquiries, 'getStoredInquiries').returns([inquiryInStorage])
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
|
tabs: [],
|
||||||
predefinedInquiries: []
|
predefinedInquiries: []
|
||||||
}
|
}
|
||||||
const actions = { addTab: sinon.stub().resolves(1) }
|
const actions = { addTab: sinon.stub().resolves(1) }
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe('MainMenu.vue', () => {
|
|||||||
it('Save is not visible if there is no tabs', () => {
|
it('Save is not visible if there is no tabs', () => {
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: null,
|
currentTab: null,
|
||||||
tabs: [{}],
|
tabs: [],
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -62,13 +62,15 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Save is disabled if current tab.isSaved is true', async () => {
|
it('Save is disabled if current tab.isSaved is true', async () => {
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -83,17 +85,19 @@ describe('MainMenu.vue', () => {
|
|||||||
expect(wrapper.find('#save-btn').element.disabled).to.equal(false)
|
expect(wrapper.find('#save-btn').element.disabled).to.equal(false)
|
||||||
|
|
||||||
await vm.$set(state.tabs[0], 'isSaved', true)
|
await vm.$set(state.tabs[0], 'isSaved', true)
|
||||||
|
await vm.$nextTick()
|
||||||
expect(wrapper.find('#save-btn').element.disabled).to.equal(true)
|
expect(wrapper.find('#save-btn').element.disabled).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Creates a tab', async () => {
|
it('Creates a tab', async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const newInquiryId = 1
|
const newInquiryId = 1
|
||||||
@@ -121,13 +125,14 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Creates a tab and redirects to workspace', async () => {
|
it('Creates a tab and redirects to workspace', async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const newInquiryId = 1
|
const newInquiryId = 1
|
||||||
@@ -156,13 +161,14 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
it('Ctrl R calls currentTab.execute if running is enabled and route.path is "/workspace"',
|
it('Ctrl R calls currentTab.execute if running is enabled and route.path is "/workspace"',
|
||||||
async () => {
|
async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -201,13 +207,14 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
it('Ctrl Enter calls currentTab.execute if running is enabled and route.path is "/workspace"',
|
it('Ctrl Enter calls currentTab.execute if running is enabled and route.path is "/workspace"',
|
||||||
async () => {
|
async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -245,13 +252,14 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Ctrl B calls createNewInquiry', async () => {
|
it('Ctrl B calls createNewInquiry', async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -280,13 +288,14 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
it('Ctrl S calls checkInquiryBeforeSave if the tab is unsaved and route path is /workspace',
|
it('Ctrl S calls checkInquiryBeforeSave if the tab is unsaved and route path is /workspace',
|
||||||
async () => {
|
async () => {
|
||||||
|
const tab = {
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state })
|
const store = new Vuex.Store({ state })
|
||||||
@@ -325,13 +334,16 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
it('Saves the inquiry when no need the new name',
|
it('Saves the inquiry when no need the new name',
|
||||||
async () => {
|
async () => {
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ id: 1, name: 'foo', isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -364,13 +376,15 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
// check that the tab was updated
|
// check that the tab was updated
|
||||||
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
||||||
index: 0,
|
tab,
|
||||||
name: 'foo',
|
newValues: {
|
||||||
id: 1,
|
name: 'foo',
|
||||||
query: 'SELECT * FROM foo',
|
id: 1,
|
||||||
viewType: 'chart',
|
query: 'SELECT * FROM foo',
|
||||||
viewOptions: [],
|
viewType: 'chart',
|
||||||
isSaved: true
|
viewOptions: [],
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
}))).to.equal(true)
|
}))).to.equal(true)
|
||||||
|
|
||||||
// check that 'inquirySaved' event was triggered on $root
|
// check that 'inquirySaved' event was triggered on $root
|
||||||
@@ -378,13 +392,17 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Shows en error when the new name is needed but not specifyied', async () => {
|
it('Shows en error when the new name is needed but not specifyied', async () => {
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -424,13 +442,17 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Saves the inquiry with a new name', async () => {
|
it('Saves the inquiry with a new name', async () => {
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -475,13 +497,15 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
// check that the tab was updated
|
// check that the tab was updated
|
||||||
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
||||||
index: 0,
|
tab: tab,
|
||||||
name: 'foo',
|
newValues: {
|
||||||
id: 1,
|
name: 'foo',
|
||||||
query: 'SELECT * FROM foo',
|
id: 1,
|
||||||
viewType: 'chart',
|
query: 'SELECT * FROM foo',
|
||||||
viewOptions: [],
|
viewType: 'chart',
|
||||||
isSaved: true
|
viewOptions: [],
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
}))).to.equal(true)
|
}))).to.equal(true)
|
||||||
|
|
||||||
// check that 'inquirySaved' event was triggered on $root
|
// check that 'inquirySaved' event was triggered on $root
|
||||||
@@ -489,23 +513,26 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Saves a predefined inquiry with a new name', async () => {
|
it('Saves a predefined inquiry with a new name', async () => {
|
||||||
const state = {
|
const tab = {
|
||||||
currentTab: {
|
id: 1,
|
||||||
query: 'SELECT * FROM foo',
|
name: 'foo',
|
||||||
execute: sinon.stub(),
|
query: 'SELECT * FROM foo',
|
||||||
tabIndex: 0,
|
execute: sinon.stub(),
|
||||||
isPredefined: true,
|
isPredefined: true,
|
||||||
result: {
|
result: {
|
||||||
columns: ['id', 'name'],
|
columns: ['id', 'name'],
|
||||||
values: [
|
values: [
|
||||||
[1, 'Harry Potter'],
|
[1, 'Harry Potter'],
|
||||||
[2, 'Drako Malfoy']
|
[2, 'Drako Malfoy']
|
||||||
]
|
]
|
||||||
},
|
|
||||||
viewType: 'chart',
|
|
||||||
viewOptions: []
|
|
||||||
},
|
},
|
||||||
tabs: [{ id: 1, name: 'foo', isSaved: false, isPredefined: true }],
|
viewType: 'chart',
|
||||||
|
viewOptions: [],
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
|
const state = {
|
||||||
|
currentTab: tab,
|
||||||
|
tabs: [tab],
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -553,13 +580,15 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
// check that the tab was updated
|
// check that the tab was updated
|
||||||
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
expect(mutations.updateTab.calledOnceWith(state, sinon.match({
|
||||||
index: 0,
|
tab,
|
||||||
name: 'bar',
|
newValues: {
|
||||||
id: 2,
|
name: 'bar',
|
||||||
query: 'SELECT * FROM foo',
|
id: 2,
|
||||||
viewType: 'chart',
|
query: 'SELECT * FROM foo',
|
||||||
viewOptions: [],
|
viewType: 'chart',
|
||||||
isSaved: true
|
viewOptions: [],
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
}))).to.equal(true)
|
}))).to.equal(true)
|
||||||
|
|
||||||
// check that 'inquirySaved' event was triggered on $root
|
// check that 'inquirySaved' event was triggered on $root
|
||||||
@@ -580,13 +609,17 @@ describe('MainMenu.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Cancel saving', async () => {
|
it('Cancel saving', async () => {
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
execute: sinon.stub(),
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: tab,
|
||||||
query: 'SELECT * FROM foo',
|
tabs: [tab],
|
||||||
execute: sinon.stub(),
|
|
||||||
tabIndex: 0
|
|
||||||
},
|
|
||||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isSaved: false }],
|
|
||||||
db: {}
|
db: {}
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
|||||||
@@ -138,15 +138,37 @@ describe('hint.js', () => {
|
|||||||
'getHints returns [ ] if there is only one option and token is completed with this option',
|
'getHints returns [ ] if there is only one option and token is completed with this option',
|
||||||
() => {
|
() => {
|
||||||
// mock CM.hint.sql and editor
|
// mock CM.hint.sql and editor
|
||||||
sinon.stub(CM.hint, 'sql').returns({ list: [{ text: 'SELECT' }] })
|
sinon.stub(CM.hint, 'sql').returns({
|
||||||
|
list: [{ text: 'SELECT' }],
|
||||||
|
from: null, // from/to doesn't metter because getRange is mocked
|
||||||
|
to: null
|
||||||
|
})
|
||||||
|
|
||||||
const editor = {
|
const editor = {
|
||||||
getTokenAt () {
|
getRange () {
|
||||||
return {
|
return 'select'
|
||||||
string: 'select',
|
}
|
||||||
type: 'keyword'
|
}
|
||||||
}
|
|
||||||
},
|
const hints = getHints(editor, {})
|
||||||
getCursor: sinon.stub()
|
expect(hints.list).to.eql([])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'getHints returns [ ] if there is only one string option and token ' +
|
||||||
|
'is completed with this option',
|
||||||
|
() => {
|
||||||
|
// mock CM.hint.sql and editor
|
||||||
|
sinon.stub(CM.hint, 'sql').returns({
|
||||||
|
list: ['house.name'],
|
||||||
|
from: null, // from/to doesn't metter because getRange is mocked
|
||||||
|
to: null
|
||||||
|
})
|
||||||
|
const editor = {
|
||||||
|
getRange () {
|
||||||
|
return 'house.name'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hints = getHints(editor, {})
|
const hints = getHints(editor, {})
|
||||||
@@ -160,15 +182,11 @@ describe('hint.js', () => {
|
|||||||
{ text: 'SELECT' },
|
{ text: 'SELECT' },
|
||||||
{ text: 'ST' }
|
{ text: 'ST' }
|
||||||
]
|
]
|
||||||
sinon.stub(CM.hint, 'sql').returns({ list })
|
sinon.stub(CM.hint, 'sql').returns({ list, from: null, to: null })
|
||||||
const editor = {
|
const editor = {
|
||||||
getTokenAt () {
|
getRange () {
|
||||||
return {
|
return 'se'
|
||||||
string: 'se',
|
}
|
||||||
type: 'keyword'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCursor: sinon.stub()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hints = getHints(editor, {})
|
const hints = getHints(editor, {})
|
||||||
@@ -182,15 +200,11 @@ describe('hint.js', () => {
|
|||||||
() => {
|
() => {
|
||||||
// mock CM.hint.sql and editor
|
// mock CM.hint.sql and editor
|
||||||
const list = [{ text: 'SELECT' }]
|
const list = [{ text: 'SELECT' }]
|
||||||
sinon.stub(CM.hint, 'sql').returns({ list })
|
sinon.stub(CM.hint, 'sql').returns({ list, from: null, to: null })
|
||||||
const editor = {
|
const editor = {
|
||||||
getTokenAt () {
|
getRange () {
|
||||||
return {
|
return 'sele'
|
||||||
string: 'sele',
|
}
|
||||||
type: 'keyword'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCursor: sinon.stub()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hints = getHints(editor, {})
|
const hints = getHints(editor, {})
|
||||||
|
|||||||
@@ -31,13 +31,23 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab: {
|
||||||
initName: 'foo',
|
id: 1,
|
||||||
initQuery: 'SELECT * FROM foo',
|
name: 'foo',
|
||||||
initViewType: 'chart',
|
query: 'SELECT * FROM foo',
|
||||||
initViewOptions: [],
|
viewType: 'chart',
|
||||||
tabIndex: 0,
|
viewOptions: {},
|
||||||
isPredefined: false
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -60,7 +70,23 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1
|
tab: {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.find('.tab-content-container').isVisible()).to.equal(false)
|
expect(wrapper.find('.tab-content-container').isVisible()).to.equal(false)
|
||||||
@@ -79,40 +105,51 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1
|
tab: {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.find('.tab-content-container').isVisible()).to.equal(false)
|
expect(wrapper.find('.tab-content-container').isVisible()).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Calls setCurrentTab when becomes active', async () => {
|
|
||||||
// mock store state
|
|
||||||
const state = {
|
|
||||||
currentTabId: 0
|
|
||||||
}
|
|
||||||
sinon.spy(mutations, 'setCurrentTab')
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
|
||||||
|
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Tab, {
|
|
||||||
store,
|
|
||||||
stubs: ['chart'],
|
|
||||||
propsData: {
|
|
||||||
id: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
state.currentTabId = 1
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(mutations.setCurrentTab.calledOnceWith(state, wrapper.vm)).to.equal(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Update tab state when a query is changed', async () => {
|
it('Update tab state when a query is changed', async () => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'SELECT * FROM foo', chart: [], isSaved: true }
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
@@ -124,13 +161,7 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab: state.tabs[0]
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await wrapper.findComponent({ name: 'SqlEditor' }).vm.$emit('input', ' limit 100')
|
await wrapper.findComponent({ name: 'SqlEditor' }).vm.$emit('input', ' limit 100')
|
||||||
@@ -141,7 +172,24 @@ describe('Tab.vue', () => {
|
|||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'SELECT * FROM foo', chart: [], isSaved: true }
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 1
|
currentTabId: 1
|
||||||
}
|
}
|
||||||
@@ -153,13 +201,7 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab: state.tabs[0]
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await wrapper.findComponent({ name: 'DataView' }).vm.$emit('update')
|
await wrapper.findComponent({ name: 'DataView' }).vm.$emit('update')
|
||||||
@@ -169,29 +211,38 @@ describe('Tab.vue', () => {
|
|||||||
it('Shows .result-in-progress message when executing query', async () => {
|
it('Shows .result-in-progress message when executing query', async () => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1,
|
currentTabId: 1
|
||||||
db: {
|
|
||||||
execute () { return new Promise(() => {}) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
// mount the component
|
// mount the component
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
wrapper.vm.execute()
|
tab.isGettingResults = true
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.find('.run-result-panel .result-in-progress').isVisible()).to.equal(true)
|
expect(wrapper.find('.run-result-panel .result-in-progress').isVisible()).to.equal(true)
|
||||||
})
|
})
|
||||||
@@ -199,30 +250,42 @@ describe('Tab.vue', () => {
|
|||||||
it('Shows error when executing query ends with error', async () => {
|
it('Shows error when executing query ends with error', async () => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1,
|
currentTabId: 1
|
||||||
db: {
|
|
||||||
execute: sinon.stub().rejects(new Error('There is no table foo')),
|
|
||||||
refreshSchema: sinon.stub().resolves()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.vm.execute()
|
tab.error = {
|
||||||
|
type: 'error',
|
||||||
|
message: 'There is no table foo'
|
||||||
|
}
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.find('.run-result-panel .result-before').isVisible()).to.equal(false)
|
expect(wrapper.find('.run-result-panel .result-before').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('.run-result-panel .result-in-progress').exists()).to.equal(false)
|
expect(wrapper.find('.run-result-panel .result-in-progress').exists()).to.equal(false)
|
||||||
expect(wrapper.findComponent({ name: 'logs' }).isVisible()).to.equal(true)
|
expect(wrapper.findComponent({ name: 'logs' }).isVisible()).to.equal(true)
|
||||||
@@ -239,11 +302,26 @@ describe('Tab.vue', () => {
|
|||||||
}
|
}
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1,
|
currentTabId: 1
|
||||||
db: {
|
}
|
||||||
execute: sinon.stub().resolves(result),
|
|
||||||
refreshSchema: sinon.stub().resolves()
|
const tab = {
|
||||||
}
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
@@ -253,83 +331,50 @@ describe('Tab.vue', () => {
|
|||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.vm.execute()
|
tab.result = result
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.find('.run-result-panel .result-before').isVisible()).to.equal(false)
|
expect(wrapper.find('.run-result-panel .result-before').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('.run-result-panel .result-in-progress').exists()).to.equal(false)
|
expect(wrapper.find('.run-result-panel .result-in-progress').exists()).to.equal(false)
|
||||||
expect(wrapper.findComponent({ name: 'logs' }).exists()).to.equal(false)
|
expect(wrapper.findComponent({ name: 'logs' }).exists()).to.equal(false)
|
||||||
expect(wrapper.findComponent({ name: 'SqlTable' }).vm.dataSet).to.eql(result)
|
expect(wrapper.findComponent({ name: 'SqlTable' }).vm.dataSet).to.eql(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Updates schema after query execution', async () => {
|
|
||||||
const result = {
|
|
||||||
columns: ['id', 'name'],
|
|
||||||
values: {
|
|
||||||
id: [],
|
|
||||||
name: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mock store state
|
|
||||||
const state = {
|
|
||||||
currentTabId: 1,
|
|
||||||
dbName: 'fooDb',
|
|
||||||
db: {
|
|
||||||
execute: sinon.stub().resolves(result),
|
|
||||||
refreshSchema: sinon.stub().resolves()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
|
||||||
|
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Tab, {
|
|
||||||
store,
|
|
||||||
stubs: ['chart'],
|
|
||||||
propsData: {
|
|
||||||
id: 1,
|
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await wrapper.vm.execute()
|
|
||||||
expect(state.db.refreshSchema.calledOnce).to.equal(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Switches views', async () => {
|
it('Switches views', async () => {
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1,
|
currentTabId: 1
|
||||||
db: {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
|
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
attachTo: place,
|
attachTo: place,
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
tab
|
||||||
initName: 'foo',
|
|
||||||
initQuery: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
|
||||||
initViewOptions: [],
|
|
||||||
initViewType: 'chart',
|
|
||||||
tabIndex: 0,
|
|
||||||
isPredefined: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -361,4 +406,119 @@ describe('Tab.vue', () => {
|
|||||||
expect(wrapper.find('.above .sql-editor-panel').exists()).to.equal(true)
|
expect(wrapper.find('.above .sql-editor-panel').exists()).to.equal(true)
|
||||||
expect(wrapper.find('.bottomPane .run-result-panel').exists()).to.equal(true)
|
expect(wrapper.find('.bottomPane .run-result-panel').exists()).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Maximize top panel if maximized panel is above', () => {
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, mutations })
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
maximize: 'sqlEditor',
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = mount(Tab, {
|
||||||
|
attachTo: place,
|
||||||
|
store,
|
||||||
|
stubs: ['chart'],
|
||||||
|
propsData: {
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('.above').element.parentElement.style.height)
|
||||||
|
.to.equal('100%')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Maximize bottom panel if maximized panel is below', () => {
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, mutations })
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
maximize: 'table',
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = mount(Tab, {
|
||||||
|
attachTo: place,
|
||||||
|
store,
|
||||||
|
stubs: ['chart'],
|
||||||
|
propsData: {
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('.bottomPane').element.parentElement.style.height)
|
||||||
|
.to.equal('100%')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Panel size is 50 is nothing to maximize', () => {
|
||||||
|
const state = {
|
||||||
|
currentTabId: 1
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, mutations })
|
||||||
|
const tab = {
|
||||||
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isPredefined: false,
|
||||||
|
result: null,
|
||||||
|
isGettingResults: false,
|
||||||
|
error: null,
|
||||||
|
time: 0,
|
||||||
|
isSaved: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = mount(Tab, {
|
||||||
|
attachTo: place,
|
||||||
|
store,
|
||||||
|
stubs: ['chart'],
|
||||||
|
propsData: {
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('.above').element.parentElement.style.height)
|
||||||
|
.to.equal('50%')
|
||||||
|
expect(wrapper.find('.bottomPane').element.parentElement.style.height)
|
||||||
|
.to.equal('50%')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -94,8 +94,33 @@ describe('Tabs.vue', () => {
|
|||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'select * from foo', chart: [], isSaved: true },
|
{
|
||||||
{ id: 2, name: null, tempName: 'Untitled', query: '', chart: [], isSaved: false }
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'select * from foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: '',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
@@ -125,8 +150,33 @@ describe('Tabs.vue', () => {
|
|||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'select * from foo', chart: [], isSaved: true },
|
{
|
||||||
{ id: 2, name: null, tempName: 'Untitled', query: '', chart: [], isSaved: false }
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'select * from foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: '',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
@@ -166,8 +216,33 @@ describe('Tabs.vue', () => {
|
|||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'select * from foo', chart: [], isSaved: true },
|
{
|
||||||
{ id: 2, name: null, tempName: 'Untitled', query: '', chart: [], isSaved: false }
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'select * from foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: '',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
@@ -211,8 +286,33 @@ describe('Tabs.vue', () => {
|
|||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 1, name: 'foo', query: 'select * from foo', chart: [], isSaved: true },
|
{
|
||||||
{ id: 2, name: null, tempName: 'Untitled', query: '', chart: [], isSaved: false }
|
id: 1,
|
||||||
|
name: 'foo',
|
||||||
|
query: 'select * from foo',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: null,
|
||||||
|
tempName: 'Untitled',
|
||||||
|
query: '',
|
||||||
|
viewType: 'chart',
|
||||||
|
viewOptions: {},
|
||||||
|
layout: {
|
||||||
|
sqlEditor: 'above',
|
||||||
|
table: 'bottom',
|
||||||
|
dataView: 'hidden'
|
||||||
|
},
|
||||||
|
isSaved: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
currentTabId: 2
|
currentTabId: 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ describe('Workspace.vue', () => {
|
|||||||
tabs: []
|
tabs: []
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({ state, actions, mutations })
|
const store = new Vuex.Store({ state, actions, mutations })
|
||||||
|
const $route = { path: '/workspace', query: {} }
|
||||||
mount(Workspace, {
|
mount(Workspace, {
|
||||||
store,
|
store,
|
||||||
stubs: ['router-link']
|
stubs: ['router-link'],
|
||||||
|
mocks: { $route }
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(state.tabs[0].query).to.include('Your database is empty.')
|
expect(state.tabs[0].query).to.include('Your database is empty.')
|
||||||
@@ -24,4 +26,20 @@ describe('Workspace.vue', () => {
|
|||||||
expect(state.tabs[0].viewOptions).to.equal(undefined)
|
expect(state.tabs[0].viewOptions).to.equal(undefined)
|
||||||
expect(state.tabs[0].isSaved).to.equal(false)
|
expect(state.tabs[0].isSaved).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Collapse schema if hide_schema is 1', () => {
|
||||||
|
const state = {
|
||||||
|
db: {},
|
||||||
|
tabs: []
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({ state, actions, mutations })
|
||||||
|
const $route = { path: '/workspace', query: { hide_schema: '1' } }
|
||||||
|
const vm = mount(Workspace, {
|
||||||
|
store,
|
||||||
|
stubs: ['router-link'],
|
||||||
|
mocks: { $route }
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(vm.find('#schema-container').element.offsetWidth).to.equal(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user