1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-07 02:28:54 +08:00

5 Commits

Author SHA1 Message Date
lana-k
3c456ef135 fix sql hint: read properties of undefined #99 2022-07-29 15:27:01 +02:00
lana-k
c713c713b7 fix paths #97 2022-07-20 23:15:14 +02:00
lana-k
babf0074c0 add artifact with source map #97 2022-07-20 22:49:26 +02:00
lana-k
e71e6700c1 improve events 2022-07-20 22:47:40 +02:00
lana-k
84e66b8167 Update README.md 2022-07-10 23:18:52 +02:00
19 changed files with 101 additions and 79 deletions

View File

@@ -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,6 +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"

View File

@@ -21,7 +21,7 @@ https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-
The latest release of sqliteviz is deployed on [sqliteviz.com/app][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].
@@ -35,7 +35,7 @@ It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4]
[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://sqliteviz.com/app/ [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

View File

@@ -1,6 +1,6 @@
{ {
"name": "sqliteviz", "name": "sqliteviz",
"version": "0.20.0", "version": "0.21.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@@ -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 })
} }
} }
} }

View File

@@ -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
}) })

View File

@@ -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()
@@ -79,7 +79,7 @@ class Database {
this.dbName = file ? fu.getFileName(file) : 'database' this.dbName = file ? fu.getFileName(file) : 'database'
this.refreshSchema() 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) {

View File

@@ -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
@@ -106,7 +106,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 +115,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 () {

View File

@@ -1,10 +1,12 @@
export function send (name, value, labels) { export default {
send (name, value, labels) {
const event = new CustomEvent('sqliteviz-app-event', { const event = new CustomEvent('sqliteviz-app-event', {
detail: { detail: {
name, name,
value, value,
labels labels: labels || {}
} }
}) })
window.dispatchEvent(event) window.dispatchEvent(event)
}
} }

View File

@@ -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')
}) })
} }

View File

@@ -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',
@@ -115,7 +115,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')
@@ -169,7 +169,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') {

View File

@@ -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
}) })

View File

@@ -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,10 @@ export default {
notifyOnLogging: 1 notifyOnLogging: 1
}) })
this.$watch( this.$watch(
() => JSON.stringify( () => this.state.data.map(trace => `${trace.type}-${trace.mode}`)
this.state.data.map(trace => `${trace.type}-${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
}) })

View File

@@ -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
}) })
} }

View File

@@ -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
) )
} }

View File

@@ -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' }
) )

View File

@@ -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
} }

View File

@@ -56,7 +56,7 @@ import DataView from './DataView'
import RunResult from './RunResult' import RunResult from './RunResult'
import time from '@/lib/utils/time' 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',
@@ -110,7 +110,7 @@ export default {
this.layout[from] = this.layout[to] this.layout[from] = this.layout[to]
this.layout[to] = fromPosition this.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', { index: this.tabIndex, isSaved: false })
@@ -126,19 +126,19 @@ export default {
this.time = time.getPeriod(start, new Date()) this.time = time.getPeriod(start, new Date())
if (this.result && this.result.values) { if (this.result && this.result.values) {
send('resultset.create', events.send('resultset.create',
this.result.values[this.result.columns[0]].length this.result.values[this.result.columns[0]].length
) )
} }
send('query.run', parseFloat(this.time), { status: 'success' }) events.send('query.run', parseFloat(this.time), { status: 'success' })
} catch (err) { } catch (err) {
this.error = { this.error = {
type: 'error', type: 'error',
message: err message: err
} }
send('query.run', 0, { status: 'error' }) events.send('query.run', 0, { status: 'error' })
} }
state.db.refreshSchema() state.db.refreshSchema()
this.isGettingResults = false this.isGettingResults = false

View File

@@ -19,7 +19,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',
@@ -51,7 +51,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 })
} }
} }
} }

View File

@@ -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, {})
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'
} }
},
getCursor: sinon.stub()
} }
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, {})