Browse Source

feat: Formula auto-complete - case-insensitive & GQL support

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/448/head
Pranav C 3 years ago committed by Pranav C
parent
commit
3527f218e7
  1. 9
      packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
  2. 79
      packages/nc-gui/helpers/NcAutocompleteTree.js
  3. 10
      packages/nc-gui/helpers/index.js
  4. 19
      packages/nc-gui/plugins/ncApis/gqlApi.js
  5. 28
      packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts

9
packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue

@ -60,7 +60,7 @@
<script>
import NcAutocompleteTree from '@/help/NcAutocompleteTree'
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'
import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import debounce from 'debounce'
import jsep from 'jsep'
@ -190,11 +190,12 @@ export default {
return arr
},
appendText(it) {
const text = it.text.slice(this.wordToComplete.length)
const text = it.text
const len = this.wordToComplete.length
if (it.type === 'function') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text + '()', text.length + 1))
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text + '()', len, 1))
} else {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text))
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len))
}
},
_handleInputDeb: debounce(async function(self) {

79
packages/nc-gui/helpers/NcAutocompleteTree.js

@ -0,0 +1,79 @@
// ref : https://medium.com/weekly-webtips/js-implementing-auto-complete-f4c5a5d5c009
class NcAutocompleteTree {
constructor() {
this.trie = null
this.suggestions = []
}
newNode() {
return {
isLeaf: false,
children: {}
}
}
add(word) {
if (!this.trie) {
this.trie = this.newNode()
}
let root = this.trie
for (const letter of word.text.toLowerCase()) {
if (!(letter in root.children)) {
root.children[letter] = this.newNode()
}
root = root.children[letter]
}
root.value = root.value || []
root.value.push(word)
}
find(word) {
let root = this.trie
for (const letter of word) {
if (letter in root.children) {
root = root.children[letter]
} else {
return null // if not found return null
}
}
return root // return the root where it ends search
}
traverse(root) {
if (root.value && root.value.length) {
this.suggestions.push(...root.value)
}
for (const letter in root.children) {
this.traverse(root.children[letter])
}
}
complete(word, CHILDREN = null) {
this.suggestions = []
const root = this.find(word.toLowerCase())
if (!root) {
return this.suggestions
} // cannot suggest anything
const children = root.children
let spread = 0
for (const letter in children) {
this.traverse(children[letter], word + letter)
spread++
if (CHILDREN && spread === CHILDREN) {
break
}
}
return this.suggestions
}
}
export default NcAutocompleteTree

10
packages/nc-gui/helpers/index.js

@ -14,23 +14,21 @@ export const isValidURL = (str) => {
export const parseIfInteger = v => /^\d+$/.test(v) ? +v : v
// ref : https://stackoverflow.com/a/11077016
export function insertAtCursor(myField, myValue, len) {
export function insertAtCursor(myField, myValue, len = 0, b = 0) {
// IE support
if (document.selection) {
myField.focus()
const sel = document.selection.createRange()
sel.text = myValue
}
// MOZILLA and others
} // MOZILLA and others
else if (myField.selectionStart || myField.selectionStart == '0') {
const startPos = myField.selectionStart
const endPos = myField.selectionEnd
myField.value = myField.value.substring(0, startPos) +
myField.value = myField.value.substring(0, startPos - len) +
myValue +
myField.value.substring(endPos, myField.value.length)
const pos = +startPos + (len ?? myValue.length)
const pos = +startPos - len + myValue.length - b
// https://stackoverflow.com/a/4302688
if (myField.setSelectionRange) {
myField.focus()

19
packages/nc-gui/plugins/ncApis/gqlApi.js

@ -38,7 +38,9 @@ export default class GqlApi {
}
generateQueryParams(params) {
if (!params) { return '(where:"")' }
if (!params) {
return '(where:"")'
}
const res = []
if ('limit' in params) {
res.push(`limit: ${params.limit}`)
@ -65,7 +67,7 @@ export default class GqlApi {
return `{${this.gqlQueryListName}${this.generateQueryParams(params)}{${this.gqlReqBody}${await this.gqlRelationReqBody(params)}}}`
}
async gqlReadQuery(id) {
async gqlReadQuery(id, params = {}) {
return `{${this.gqlQueryReadName}(id:"${id}"){${this.gqlReqBody}${await this.gqlRelationReqBody(params)}}}`
}
@ -89,6 +91,7 @@ export default class GqlApi {
return `\n${this.columns.map(c => c._cn).join('\n')}\n`
}
// todo: query only visible columns
async gqlRelationReqBody(params) {
let str = ''
if (params.hm) {
@ -130,6 +133,14 @@ export default class GqlApi {
}
}
}
// add formula columns to query
str += this.meta.v.reduce((arr, v) => {
if (v.formula) {
arr.push(v._cn)
}
return arr
}, []).join('\n')
return str
}
@ -207,9 +218,9 @@ export default class GqlApi {
return data1.data.data[this.gqlMutationDeleteName]
}
async read(id) {
async read(id, params = {}) {
const data = await this.post(`/nc/${this.$ctx.projectId}/v1/graphql`, {
query: await this.gqlReadQuery(id),
query: await this.gqlReadQuery(id, params),
variables: null
})
return data.data.data[this.gqlQueryReadName]

28
packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts

@ -1785,7 +1785,11 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
}, ...Object.values(this.resolvers).map(r => r.mapResolvers(this.customResolver))]);
this.log(`initGraphqlRoute : Building graphql schema`);
const schemaStr = mergeTypeDefs([...Object.values(this.schemas).filter(Boolean), ` ${this.customResolver?.schema || ''} \n ${commonSchema}`], {
const schemaStr = mergeTypeDefs([
...Object.values(this.schemas).filter(Boolean),
` ${this.customResolver?.schema || ''} \n ${commonSchema}`,
...this.typesWithFormulaProps
], {
commentDescriptions: true,
forceSchemaDefinition: true,
reverseDirectives: true,
@ -1972,7 +1976,29 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
}
// todo: dump it in db
// extending types for formula column
private get typesWithFormulaProps(): string[] {
const schemas = [];
for (const meta of Object.values(this.metas)) {
const props = [];
for (const v of meta.v) {
if (!v.formula) continue
props.push(`${v._cn}: JSON`)
}
if (props.length) {
schemas.push(`type ${meta._tn} {\n${props.join('\n')}\n}`)
}
}
return schemas;
}
async onMetaUpdate(tn: string): Promise<void> {
await super.onMetaUpdate(tn);
return this.reInitializeGraphqlEndpoint();
}
}
/**

Loading…
Cancel
Save