@dataloop-ai/components 0.17.63 → 0.17.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/components/basic/DlButton/DlButton.vue +17 -8
- package/src/components/basic/DlButton/utils.ts +44 -1
- package/src/components/compound/DlDialogBox/DlDialogBox.vue +2 -2
- package/src/components/compound/DlSearches/DlSmartSearch/DlSmartSearch.vue +84 -16
- package/src/components/compound/DlSearches/DlSmartSearch/components/DlSmartSearchFilters.vue +1 -0
- package/src/components/compound/DlSearches/DlSmartSearch/components/DlSmartSearchInput.vue +11 -10
- package/src/components/compound/DlSearches/DlSmartSearch/components/FiltersQuery.vue +2 -5
- package/src/components/compound/DlSearches/DlSmartSearch/utils/highlightSyntax.ts +1 -1
- package/src/components/compound/DlSearches/DlSmartSearch/utils/utils.ts +13 -6
- package/src/demos/SmartSearchDemo/DlSmartSearchDemo.vue +57 -6
- package/src/hooks/use-suggestions.ts +116 -17
- package/src/utils/parse-smart-query.ts +112 -111
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dataloop-ai/components",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.65",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "./index.ts",
|
|
6
6
|
"./models": "./models.ts",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"@dataloop-ai/icons": "^3.0.6",
|
|
26
26
|
"@types/lodash": "^4.14.184",
|
|
27
27
|
"chart.js": "^3.9.1",
|
|
28
|
+
"flat": "^5.0.2",
|
|
28
29
|
"lodash": "^4.17.21",
|
|
29
30
|
"moment": "^2.29.4",
|
|
30
31
|
"sass": "^1.51.0",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"@storybook/client-api": "^7.0.4",
|
|
46
47
|
"@storybook/vue3": "^7.0.4",
|
|
47
48
|
"@storybook/vue3-vite": "^7.0.4",
|
|
49
|
+
"@types/flat": "^5.0.2",
|
|
48
50
|
"@types/jsdom": "^16.2.14",
|
|
49
51
|
"@types/node": "^18.7.18",
|
|
50
52
|
"@types/resize-observer-browser": "^0.1.7",
|
|
@@ -62,6 +62,9 @@ import {
|
|
|
62
62
|
setColorOnHover,
|
|
63
63
|
setBorderOnHover,
|
|
64
64
|
setBgOnHover,
|
|
65
|
+
setBgOnPressed,
|
|
66
|
+
setBorderOnPressed,
|
|
67
|
+
setTextOnPressed,
|
|
65
68
|
setIconSize,
|
|
66
69
|
setIconPadding,
|
|
67
70
|
setMaxHeight
|
|
@@ -291,7 +294,8 @@ export default defineComponent({
|
|
|
291
294
|
disabled: this.disabled,
|
|
292
295
|
flat: this.flat,
|
|
293
296
|
shaded: this.shaded,
|
|
294
|
-
color: this.color
|
|
297
|
+
color: this.color,
|
|
298
|
+
outlined: this.outlined
|
|
295
299
|
}),
|
|
296
300
|
'--dl-button-text-color-hover': setColorOnHover({
|
|
297
301
|
disabled: this.disabled,
|
|
@@ -314,13 +318,18 @@ export default defineComponent({
|
|
|
314
318
|
filled: this.filled,
|
|
315
319
|
color: this.color
|
|
316
320
|
}),
|
|
317
|
-
'--dl-button-text-color-pressed':
|
|
318
|
-
|
|
319
|
-
:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
:
|
|
323
|
-
|
|
321
|
+
'--dl-button-text-color-pressed': setTextOnPressed({
|
|
322
|
+
shaded: this.shaded,
|
|
323
|
+
outlined: this.shaded
|
|
324
|
+
}),
|
|
325
|
+
'--dl-button-bg-pressed': setBgOnPressed({
|
|
326
|
+
shaded: this.shaded,
|
|
327
|
+
outlined: this.outlined
|
|
328
|
+
}),
|
|
329
|
+
'--dl-button-border-pressed': setBorderOnPressed({
|
|
330
|
+
shaded: this.shaded,
|
|
331
|
+
outlined: this.outlined
|
|
332
|
+
})
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
|
|
@@ -70,6 +70,9 @@ export const setTextColor = ({
|
|
|
70
70
|
if (disabled) {
|
|
71
71
|
return getColor('', 'dl-color-disabled')
|
|
72
72
|
}
|
|
73
|
+
if (shaded && outlined) {
|
|
74
|
+
return 'var(--dl-color-text-darker-buttons)'
|
|
75
|
+
}
|
|
73
76
|
if (outlined) {
|
|
74
77
|
return getColor(textColor, 'dl-color-secondary')
|
|
75
78
|
}
|
|
@@ -111,13 +114,17 @@ export const setBorder = ({
|
|
|
111
114
|
disabled,
|
|
112
115
|
flat,
|
|
113
116
|
color = '',
|
|
114
|
-
shaded
|
|
117
|
+
shaded,
|
|
118
|
+
outlined
|
|
115
119
|
}: Partial<DlButtonProps>) => {
|
|
116
120
|
if (disabled) {
|
|
117
121
|
return flat
|
|
118
122
|
? 'var(--dl-color-transparent)'
|
|
119
123
|
: 'var(--dl-color-separator)'
|
|
120
124
|
}
|
|
125
|
+
if (shaded && outlined) {
|
|
126
|
+
return 'var(--dl-color-separator)'
|
|
127
|
+
}
|
|
121
128
|
if (flat || shaded) {
|
|
122
129
|
return 'var(--dl-color-transparent)'
|
|
123
130
|
}
|
|
@@ -177,3 +184,39 @@ export const setBgOnHover = ({
|
|
|
177
184
|
|
|
178
185
|
return 'var(--dl-color-panel-background)'
|
|
179
186
|
}
|
|
187
|
+
|
|
188
|
+
export const setBgOnPressed = ({
|
|
189
|
+
shaded,
|
|
190
|
+
outlined
|
|
191
|
+
}: Partial<DlButtonProps>) => {
|
|
192
|
+
if (shaded && outlined) {
|
|
193
|
+
return 'var(--dl-color-text-buttons)'
|
|
194
|
+
}
|
|
195
|
+
if (shaded) {
|
|
196
|
+
return 'var(--dl-color-secondary)'
|
|
197
|
+
}
|
|
198
|
+
return 'var(--dl-button-bg)'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const setTextOnPressed = ({
|
|
202
|
+
shaded,
|
|
203
|
+
outlined
|
|
204
|
+
}: Partial<DlButtonProps>) => {
|
|
205
|
+
if (shaded && outlined) {
|
|
206
|
+
return 'var(--dl-color-secondary)'
|
|
207
|
+
}
|
|
208
|
+
if (shaded) {
|
|
209
|
+
return 'var(--dl-color-text-buttons)'
|
|
210
|
+
}
|
|
211
|
+
return 'var(--dl-button-text-color)'
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const setBorderOnPressed = ({
|
|
215
|
+
shaded,
|
|
216
|
+
outlined
|
|
217
|
+
}: Partial<DlButtonProps>) => {
|
|
218
|
+
if (shaded && outlined) {
|
|
219
|
+
return 'var(--dl-color-secondary)'
|
|
220
|
+
}
|
|
221
|
+
return 'var(--dl-button-border)'
|
|
222
|
+
}
|
|
@@ -262,7 +262,7 @@ export default defineComponent({
|
|
|
262
262
|
|
|
263
263
|
.header {
|
|
264
264
|
display: flex;
|
|
265
|
-
padding: 16px;
|
|
265
|
+
padding: var(--dl-dialog-box-header-padding, 16px);
|
|
266
266
|
border-bottom: var(--dl-dialog-separator);
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -281,7 +281,7 @@ export default defineComponent({
|
|
|
281
281
|
|
|
282
282
|
.footer {
|
|
283
283
|
display: flex;
|
|
284
|
-
padding: 20px 16px;
|
|
284
|
+
padding: var(--dl-dialog-box-footer-padding, 20px 16px);
|
|
285
285
|
border-top: var(--dl-dialog-separator);
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
:default-width="width"
|
|
23
23
|
@save="saveQueryDialogBoxModel = true"
|
|
24
24
|
@focus="setFocused"
|
|
25
|
-
@update:modelValue="
|
|
25
|
+
@update:modelValue="debouncedInputModel"
|
|
26
26
|
@dql-edit="jsonEditorModel = !jsonEditorModel"
|
|
27
27
|
/>
|
|
28
28
|
</div>
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
<dl-button
|
|
45
45
|
class="dl-smart-search__buttons--filters"
|
|
46
46
|
shaded
|
|
47
|
+
outlined
|
|
47
48
|
size="s"
|
|
48
49
|
>
|
|
49
50
|
Saved Filters
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
v-model="jsonEditorModel"
|
|
66
67
|
:height="500"
|
|
67
68
|
:width="800"
|
|
69
|
+
style="--dl-dialog-box-footer-padding: 10px 16px"
|
|
68
70
|
>
|
|
69
71
|
<template #header>
|
|
70
72
|
<dl-dialog-box-header
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
label="Align Left"
|
|
91
93
|
flat
|
|
92
94
|
color="secondary"
|
|
95
|
+
padding="0px 3px"
|
|
93
96
|
@click="alignJsonText"
|
|
94
97
|
/>
|
|
95
98
|
</div>
|
|
@@ -111,11 +114,13 @@
|
|
|
111
114
|
label="Delete Query"
|
|
112
115
|
flat
|
|
113
116
|
color="secondary"
|
|
117
|
+
padding="0"
|
|
114
118
|
@click="handleQueryRemove"
|
|
115
119
|
/>
|
|
116
120
|
</div>
|
|
117
121
|
<div class="json-editor__footer-save">
|
|
118
122
|
<dl-button
|
|
123
|
+
style="margin-right: 14px"
|
|
119
124
|
outlined
|
|
120
125
|
label="Save As"
|
|
121
126
|
@click="saveQueryDialogBoxModel = true"
|
|
@@ -152,7 +157,10 @@
|
|
|
152
157
|
</div>
|
|
153
158
|
</template>
|
|
154
159
|
</dl-dialog-box>
|
|
155
|
-
<dl-dialog-box
|
|
160
|
+
<dl-dialog-box
|
|
161
|
+
v-model="saveQueryDialogBoxModel"
|
|
162
|
+
style="--dl-dialog-box-footer-padding: 14px 17px"
|
|
163
|
+
>
|
|
156
164
|
<template #header>
|
|
157
165
|
<dl-dialog-box-header
|
|
158
166
|
title="Save Query"
|
|
@@ -162,17 +170,22 @@
|
|
|
162
170
|
<template #body>
|
|
163
171
|
<dl-input
|
|
164
172
|
v-model="newQueryName"
|
|
173
|
+
title="Query name"
|
|
165
174
|
style="text-align: center"
|
|
166
175
|
placeholder="Type query name"
|
|
167
176
|
/>
|
|
168
177
|
</template>
|
|
169
178
|
<template #footer>
|
|
170
179
|
<div class="dl-smart-search__buttons--save">
|
|
171
|
-
<dl-button
|
|
180
|
+
<dl-button
|
|
181
|
+
:disabled="!newQueryName"
|
|
182
|
+
outlined
|
|
183
|
+
@click="handleSaveQuery"
|
|
184
|
+
>
|
|
172
185
|
Save
|
|
173
186
|
</dl-button>
|
|
174
187
|
<dl-button
|
|
175
|
-
|
|
188
|
+
:disabled="!newQueryName"
|
|
176
189
|
@click="handleSaveQuery(true)"
|
|
177
190
|
>
|
|
178
191
|
Save and Search
|
|
@@ -183,7 +196,15 @@
|
|
|
183
196
|
</div>
|
|
184
197
|
</template>
|
|
185
198
|
<script lang="ts">
|
|
186
|
-
import {
|
|
199
|
+
import {
|
|
200
|
+
defineComponent,
|
|
201
|
+
PropType,
|
|
202
|
+
ref,
|
|
203
|
+
nextTick,
|
|
204
|
+
toRef,
|
|
205
|
+
onMounted,
|
|
206
|
+
watch
|
|
207
|
+
} from 'vue-demi'
|
|
187
208
|
import { DlTypography, DlMenu } from '../../../essential'
|
|
188
209
|
import { DlButton } from '../../../basic'
|
|
189
210
|
import { DlSelect } from '../../DlSelect'
|
|
@@ -209,6 +230,7 @@ import {
|
|
|
209
230
|
} from './utils/utils'
|
|
210
231
|
import { v4 } from 'uuid'
|
|
211
232
|
import { parseSmartQuery, stringifySmartQuery } from '../../../../utils'
|
|
233
|
+
import { debounce } from 'lodash'
|
|
212
234
|
|
|
213
235
|
export default defineComponent({
|
|
214
236
|
components: {
|
|
@@ -223,7 +245,15 @@ export default defineComponent({
|
|
|
223
245
|
DlMenu,
|
|
224
246
|
DlSelect
|
|
225
247
|
},
|
|
248
|
+
model: {
|
|
249
|
+
prop: 'modelValue',
|
|
250
|
+
event: 'update:modelValue'
|
|
251
|
+
},
|
|
226
252
|
props: {
|
|
253
|
+
modelValue: {
|
|
254
|
+
type: Object,
|
|
255
|
+
default: {} as { [key: string]: any }
|
|
256
|
+
},
|
|
227
257
|
status: {
|
|
228
258
|
type: Object as PropType<SearchStatus>,
|
|
229
259
|
default: () => ({ type: 'info', message: '' })
|
|
@@ -239,9 +269,9 @@ export default defineComponent({
|
|
|
239
269
|
colorSchema: {
|
|
240
270
|
type: Object as PropType<ColorSchema>,
|
|
241
271
|
default: () => ({
|
|
242
|
-
fields: '
|
|
243
|
-
operators: '
|
|
244
|
-
keywords: '
|
|
272
|
+
fields: 'var(--dl-color-secondary)',
|
|
273
|
+
operators: 'var(--dl-color-positive)',
|
|
274
|
+
keywords: 'var(--dl-color-medium)'
|
|
245
275
|
})
|
|
246
276
|
},
|
|
247
277
|
isLoading: {
|
|
@@ -267,10 +297,17 @@ export default defineComponent({
|
|
|
267
297
|
width: {
|
|
268
298
|
type: String,
|
|
269
299
|
default: '450px'
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* If true, the validation will be a closed set based on the schema provided
|
|
303
|
+
*/
|
|
304
|
+
strict: {
|
|
305
|
+
type: Boolean,
|
|
306
|
+
default: false
|
|
270
307
|
}
|
|
271
308
|
},
|
|
272
|
-
emits: ['save-query', 'remove-query', 'search-query'],
|
|
273
|
-
setup(props) {
|
|
309
|
+
emits: ['save-query', 'remove-query', 'search-query', 'update:modelValue'],
|
|
310
|
+
setup(props, { emit }) {
|
|
274
311
|
const inputModel = ref('')
|
|
275
312
|
const jsonEditorModel = ref(false)
|
|
276
313
|
const searchBarWidth = ref('100%')
|
|
@@ -295,21 +332,30 @@ export default defineComponent({
|
|
|
295
332
|
value: ''
|
|
296
333
|
})
|
|
297
334
|
|
|
335
|
+
const strictRef = toRef(props, 'strict')
|
|
336
|
+
|
|
298
337
|
const { suggestions, error, findSuggestions } = useSuggestions(
|
|
299
338
|
props.schema,
|
|
300
|
-
props.aliases
|
|
339
|
+
props.aliases,
|
|
340
|
+
{ strict: strictRef }
|
|
301
341
|
)
|
|
302
342
|
|
|
303
343
|
const handleInputModel = (value: string) => {
|
|
304
344
|
inputModel.value = value
|
|
305
|
-
const json =
|
|
306
|
-
|
|
345
|
+
const json = toJSON(removeBrackets(value))
|
|
346
|
+
emit('update:modelValue', json)
|
|
347
|
+
const stringified = JSON.stringify(json)
|
|
348
|
+
const newQuery = replaceWithAliases(stringified, props.aliases)
|
|
307
349
|
activeQuery.value.query = newQuery
|
|
308
|
-
|
|
350
|
+
nextTick(() => {
|
|
351
|
+
findSuggestions(value)
|
|
352
|
+
})
|
|
309
353
|
isQuerying.value = false
|
|
310
354
|
oldInputQuery.value = value
|
|
311
355
|
}
|
|
312
356
|
|
|
357
|
+
const debouncedInputModel = debounce(handleInputModel, 300)
|
|
358
|
+
|
|
313
359
|
const toJSON = (value: string) => {
|
|
314
360
|
return parseSmartQuery(
|
|
315
361
|
replaceWithJsDates(value) ?? inputModel.value
|
|
@@ -328,6 +374,23 @@ export default defineComponent({
|
|
|
328
374
|
toJSON(inputModel.value)
|
|
329
375
|
}
|
|
330
376
|
}
|
|
377
|
+
|
|
378
|
+
const modelRef: any = toRef(props, 'modelValue')
|
|
379
|
+
|
|
380
|
+
watch(modelRef, (val: any) => {
|
|
381
|
+
if (val) {
|
|
382
|
+
const stringQuery = stringifySmartQuery(val)
|
|
383
|
+
debouncedInputModel(stringQuery)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
onMounted(() => {
|
|
388
|
+
if (props.modelValue) {
|
|
389
|
+
const stringQuery = stringifySmartQuery(props.modelValue)
|
|
390
|
+
debouncedInputModel(stringQuery)
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
331
394
|
return {
|
|
332
395
|
uuid: `dl-smart-search-${v4()}`,
|
|
333
396
|
inputModel,
|
|
@@ -349,6 +412,7 @@ export default defineComponent({
|
|
|
349
412
|
preventUpdate,
|
|
350
413
|
selectedOption,
|
|
351
414
|
handleInputModel,
|
|
415
|
+
debouncedInputModel,
|
|
352
416
|
setFocused,
|
|
353
417
|
findSuggestions,
|
|
354
418
|
toJSON
|
|
@@ -634,8 +698,12 @@ export default defineComponent({
|
|
|
634
698
|
display: flex;
|
|
635
699
|
justify-content: space-between;
|
|
636
700
|
}
|
|
637
|
-
&-
|
|
638
|
-
|
|
701
|
+
&-delete {
|
|
702
|
+
align-items: center;
|
|
703
|
+
display: flex;
|
|
704
|
+
& > * {
|
|
705
|
+
margin-bottom: 6px;
|
|
706
|
+
}
|
|
639
707
|
}
|
|
640
708
|
}
|
|
641
709
|
.json-query {
|
|
@@ -66,15 +66,16 @@
|
|
|
66
66
|
v-if="withSaveButton"
|
|
67
67
|
class="dl-smart-search-input__save-btn-wrapper"
|
|
68
68
|
>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
<div>
|
|
70
|
+
<dl-button
|
|
71
|
+
icon="icon-dl-save"
|
|
72
|
+
size="16px"
|
|
73
|
+
flat
|
|
74
|
+
:disabled="saveStatus"
|
|
75
|
+
@click="save"
|
|
76
|
+
/>
|
|
76
77
|
<dl-tooltip> Save Query </dl-tooltip>
|
|
77
|
-
</
|
|
78
|
+
</div>
|
|
78
79
|
<dl-button
|
|
79
80
|
icon="icon-dl-edit"
|
|
80
81
|
size="16px"
|
|
@@ -612,7 +613,7 @@ export default defineComponent({
|
|
|
612
613
|
}
|
|
613
614
|
|
|
614
615
|
&--disabled {
|
|
615
|
-
border-color: var(--dl-color-
|
|
616
|
+
border-color: var(--dl-color-separator);
|
|
616
617
|
}
|
|
617
618
|
}
|
|
618
619
|
|
|
@@ -650,7 +651,7 @@ export default defineComponent({
|
|
|
650
651
|
height: auto;
|
|
651
652
|
|
|
652
653
|
min-height: 14px;
|
|
653
|
-
max-height:
|
|
654
|
+
max-height: var(--dl-smart-search-bar-wrapper-height);
|
|
654
655
|
display: block;
|
|
655
656
|
}
|
|
656
657
|
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
class="query__header"
|
|
9
9
|
@mousedown="$emit('select')"
|
|
10
10
|
>
|
|
11
|
-
<dl-icon
|
|
12
|
-
:icon="icon"
|
|
13
|
-
style="margin-bottom: 3px"
|
|
14
|
-
/>
|
|
11
|
+
<dl-icon :icon="icon" />
|
|
15
12
|
<span class="query__header--title">
|
|
16
13
|
{{ name }}
|
|
17
14
|
</span>
|
|
@@ -80,7 +77,7 @@ export default defineComponent({
|
|
|
80
77
|
display: flex;
|
|
81
78
|
align-items: center;
|
|
82
79
|
&--title {
|
|
83
|
-
font-size:
|
|
80
|
+
font-size: 12px;
|
|
84
81
|
margin: 0px 12px;
|
|
85
82
|
}
|
|
86
83
|
}
|
|
@@ -95,7 +95,7 @@ function renderText(text: string) {
|
|
|
95
95
|
const words = text?.split(/(\s+)/)
|
|
96
96
|
const output = words?.map((word) => {
|
|
97
97
|
if (styleModel.keywords.values.includes(word)) {
|
|
98
|
-
return `<strong style='${SPAN_STYLES}'>${word}</strong>`
|
|
98
|
+
return `<strong style='${SPAN_STYLES}; color:${styleModel.keywords.color}'>${word}</strong>`
|
|
99
99
|
} else if (styleModel.fields.values.includes(word)) {
|
|
100
100
|
return `<span style='color:${styleModel.fields.color}; ${SPAN_STYLES}'>${word}</span>`
|
|
101
101
|
} else if (styleModel.operators.values.includes(word)) {
|
|
@@ -69,12 +69,19 @@ export function replaceWithAliases(json: string, aliases: Alias[]) {
|
|
|
69
69
|
})
|
|
70
70
|
return newJson
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
|
|
73
|
+
export function revertAliases(str: string, aliases: Alias[]) {
|
|
74
|
+
const words: string[] = []
|
|
75
|
+
for (const alias of aliases) {
|
|
76
|
+
words.push(alias.key)
|
|
77
|
+
}
|
|
78
|
+
const replacement = (match: string) => {
|
|
79
|
+
const index = words.indexOf(match)
|
|
80
|
+
return aliases[index].alias
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const regex = new RegExp(words.join('|'), 'gi')
|
|
84
|
+
return str.replace(regex, replacement)
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
export function createColorSchema(
|
|
@@ -6,26 +6,30 @@
|
|
|
6
6
|
dense
|
|
7
7
|
label="Disabled"
|
|
8
8
|
/>
|
|
9
|
+
<dl-checkbox
|
|
10
|
+
v-model="strictState"
|
|
11
|
+
dense
|
|
12
|
+
label="Strict"
|
|
13
|
+
/>
|
|
9
14
|
</div>
|
|
10
15
|
<div
|
|
11
16
|
style="width: 100px"
|
|
12
17
|
class="props"
|
|
13
18
|
/>
|
|
14
19
|
<dl-smart-search
|
|
20
|
+
v-model="queryObject"
|
|
15
21
|
:aliases="aliases"
|
|
16
22
|
:schema="schema"
|
|
17
|
-
:color-schema="
|
|
18
|
-
fields: 'blue',
|
|
19
|
-
operators: 'green',
|
|
20
|
-
keywords: 'bold'
|
|
21
|
-
}"
|
|
23
|
+
:color-schema="colorSchema"
|
|
22
24
|
:filters="filters"
|
|
23
25
|
:disabled="switchState"
|
|
24
26
|
:is-loading="isLoading"
|
|
27
|
+
:strict="strictState"
|
|
25
28
|
@remove-query="handleRemoveQuery"
|
|
26
29
|
@save-query="handleSaveQuery"
|
|
27
30
|
@search-query="handleSearchQuery"
|
|
28
31
|
/>
|
|
32
|
+
{{ queryObject }}
|
|
29
33
|
</div>
|
|
30
34
|
</template>
|
|
31
35
|
|
|
@@ -33,7 +37,6 @@
|
|
|
33
37
|
import { defineComponent } from 'vue-demi'
|
|
34
38
|
import { DlSmartSearch, DlCheckbox } from '../../components'
|
|
35
39
|
import { Query } from '../../components/types'
|
|
36
|
-
import { aliases, schema } from './schema'
|
|
37
40
|
|
|
38
41
|
export default defineComponent({
|
|
39
42
|
name: 'DlSmartSearchDemo',
|
|
@@ -42,11 +45,59 @@ export default defineComponent({
|
|
|
42
45
|
DlCheckbox
|
|
43
46
|
},
|
|
44
47
|
data() {
|
|
48
|
+
const schema: any = {
|
|
49
|
+
id: ['string', 'number'],
|
|
50
|
+
filename: 'string',
|
|
51
|
+
name: 'string',
|
|
52
|
+
url: 'string',
|
|
53
|
+
type: 'string',
|
|
54
|
+
dataset: 'string',
|
|
55
|
+
datasetId: 'string',
|
|
56
|
+
dir: 'string',
|
|
57
|
+
thumbnail: 'string',
|
|
58
|
+
createdAt: 'date',
|
|
59
|
+
annotated: 'boolean',
|
|
60
|
+
hidden: 'boolean',
|
|
61
|
+
metadata: {
|
|
62
|
+
system: {
|
|
63
|
+
width: 'number',
|
|
64
|
+
height: 'number',
|
|
65
|
+
'*': 'any'
|
|
66
|
+
},
|
|
67
|
+
test: 'any',
|
|
68
|
+
'*': 'any'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const colorSchema: any = {
|
|
73
|
+
fields: 'var(--dl-color-secondary)',
|
|
74
|
+
operators: 'var(--dl-color-positive)',
|
|
75
|
+
keywords: 'var(--dl-color-medium)'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const aliases: any = [
|
|
79
|
+
{
|
|
80
|
+
alias: 'ItemID',
|
|
81
|
+
key: 'id'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
alias: 'ItemHeight',
|
|
85
|
+
key: 'metadata.system.height'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
alias: 'ItemWidth',
|
|
89
|
+
key: 'metadata.system.width'
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
|
|
45
93
|
return {
|
|
46
94
|
schema,
|
|
47
95
|
aliases,
|
|
96
|
+
colorSchema,
|
|
48
97
|
switchState: false,
|
|
98
|
+
strictState: false,
|
|
49
99
|
isLoading: false,
|
|
100
|
+
queryObject: {},
|
|
50
101
|
filters: {
|
|
51
102
|
saved: [
|
|
52
103
|
{
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Ref, ref } from 'vue-demi'
|
|
2
2
|
import { splitByQuotes } from '../utils/splitByQuotes'
|
|
3
|
+
import { flatten } from 'flat'
|
|
4
|
+
import { isObject } from 'lodash'
|
|
3
5
|
|
|
4
6
|
export type Schema = {
|
|
5
7
|
[key: string]:
|
|
@@ -50,6 +52,15 @@ const operatorToDataTypeMap: OperatorToDataTypeMap = {
|
|
|
50
52
|
$nin: []
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
const knownDataTypes = [
|
|
56
|
+
'number',
|
|
57
|
+
'boolean',
|
|
58
|
+
'string',
|
|
59
|
+
'date',
|
|
60
|
+
'datetime',
|
|
61
|
+
'time'
|
|
62
|
+
]
|
|
63
|
+
|
|
53
64
|
type Suggestion = string
|
|
54
65
|
|
|
55
66
|
type Expression = {
|
|
@@ -79,14 +90,37 @@ export const dateIntervalPattern = new RegExp(
|
|
|
79
90
|
'gi'
|
|
80
91
|
)
|
|
81
92
|
|
|
82
|
-
export const useSuggestions = (
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
export const useSuggestions = (
|
|
94
|
+
schema: Schema,
|
|
95
|
+
aliases: Alias[],
|
|
96
|
+
options: { strict?: Ref<boolean> } = {}
|
|
97
|
+
) => {
|
|
98
|
+
const { strict } = options
|
|
99
|
+
const initialSuggestions = Object.keys(schema)
|
|
100
|
+
const aliasedKeys = aliases.map((alias) => alias.key)
|
|
101
|
+
const aliasedSuggestions = initialSuggestions.map((suggestion) =>
|
|
102
|
+
aliasedKeys.includes(suggestion)
|
|
103
|
+
? aliases.find((alias) => alias.key === suggestion)?.alias
|
|
104
|
+
: suggestion
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for (const alias of aliases) {
|
|
108
|
+
if (aliasedSuggestions.includes(alias.alias)) {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
aliasedSuggestions.push(alias.alias)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const sortString = (a: string, b: string) =>
|
|
115
|
+
a.localeCompare(b, undefined, { sensitivity: 'base' })
|
|
116
|
+
const sortedSuggestions = aliasedSuggestions.sort(sortString)
|
|
117
|
+
|
|
118
|
+
const suggestions: Ref<Suggestion[]> = ref(sortedSuggestions)
|
|
85
119
|
const error: Ref<string | null> = ref(null)
|
|
86
120
|
|
|
87
121
|
const findSuggestions = (input: string) => {
|
|
88
122
|
input = input.replace(/\s+/g, ' ').trimStart()
|
|
89
|
-
localSuggestions =
|
|
123
|
+
localSuggestions = sortedSuggestions
|
|
90
124
|
|
|
91
125
|
const words = splitByQuotes(input, space)
|
|
92
126
|
const expressions = mapWordsToExpressions(words)
|
|
@@ -110,9 +144,7 @@ export const useSuggestions = (schema: Schema, aliases: Alias[]) => {
|
|
|
110
144
|
continue
|
|
111
145
|
}
|
|
112
146
|
|
|
113
|
-
const
|
|
114
|
-
if (!alias) continue
|
|
115
|
-
const dataType = getDataTypeByAliasKey(schema, alias.key)
|
|
147
|
+
const dataType = getDataType(schema, aliases, matchedField)
|
|
116
148
|
if (!dataType) {
|
|
117
149
|
localSuggestions = []
|
|
118
150
|
continue
|
|
@@ -139,7 +171,9 @@ export const useSuggestions = (schema: Schema, aliases: Alias[]) => {
|
|
|
139
171
|
}
|
|
140
172
|
|
|
141
173
|
if (Array.isArray(dataType)) {
|
|
142
|
-
localSuggestions = dataType
|
|
174
|
+
localSuggestions = dataType.filter(
|
|
175
|
+
(type) => !knownDataTypes.includes(type)
|
|
176
|
+
)
|
|
143
177
|
|
|
144
178
|
if (!value) continue
|
|
145
179
|
|
|
@@ -176,11 +210,11 @@ export const useSuggestions = (schema: Schema, aliases: Alias[]) => {
|
|
|
176
210
|
if (!matchedKeyword || !isNextCharSpace(input, matchedKeyword))
|
|
177
211
|
continue
|
|
178
212
|
|
|
179
|
-
localSuggestions =
|
|
213
|
+
localSuggestions = sortedSuggestions
|
|
180
214
|
}
|
|
181
215
|
|
|
182
216
|
error.value = input.length
|
|
183
|
-
? getError(schema, aliases, expressions)
|
|
217
|
+
? getError(schema, aliases, expressions, { strict })
|
|
184
218
|
: null
|
|
185
219
|
|
|
186
220
|
suggestions.value = localSuggestions
|
|
@@ -194,11 +228,38 @@ const errors = {
|
|
|
194
228
|
INVALID_VALUE: (field: string) => `Invalid value for "${field}" field`
|
|
195
229
|
}
|
|
196
230
|
|
|
231
|
+
const isInputAllowed = (input: string, allowedKeys: string[]): boolean => {
|
|
232
|
+
for (const key of allowedKeys) {
|
|
233
|
+
const keyParts = key.split('.')
|
|
234
|
+
const inputParts = input.split('.')
|
|
235
|
+
|
|
236
|
+
if (keyParts.length > inputParts.length) {
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let isMatch = true
|
|
241
|
+
for (let i = 0; i < keyParts.length; i++) {
|
|
242
|
+
if (keyParts[i] !== '*' && keyParts[i] !== inputParts[i]) {
|
|
243
|
+
isMatch = false
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isMatch) {
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
|
|
197
256
|
const getError = (
|
|
198
257
|
schema: Schema,
|
|
199
258
|
aliases: Alias[],
|
|
200
|
-
expressions: Expression[]
|
|
259
|
+
expressions: Expression[],
|
|
260
|
+
options: { strict?: Ref<boolean> } = {}
|
|
201
261
|
): string | null => {
|
|
262
|
+
const { strict } = options
|
|
202
263
|
const hasErrorInStructure = expressions
|
|
203
264
|
.flatMap((exp) => Object.values(exp))
|
|
204
265
|
.some((el, index, arr) => {
|
|
@@ -214,11 +275,29 @@ const getError = (
|
|
|
214
275
|
.filter(({ field, value }) => field !== null && value !== null)
|
|
215
276
|
.reduce<string | null>((acc, { field, value, operator }, _, arr) => {
|
|
216
277
|
if (acc === 'warning') return acc
|
|
217
|
-
const
|
|
218
|
-
|
|
278
|
+
const key: string = getAliasObjByAlias(aliases, field)?.key ?? field
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Handle nested keys to validate if the key exists in the schema or not.
|
|
282
|
+
*/
|
|
283
|
+
const keys: string[] = []
|
|
284
|
+
for (const key of Object.keys(schema)) {
|
|
285
|
+
if (isObject(schema[key]) && !Array.isArray(schema[key])) {
|
|
286
|
+
const flattened = flatten({ [key]: schema[key] })
|
|
287
|
+
keys.push(...Object.keys(flattened))
|
|
288
|
+
} else {
|
|
289
|
+
keys.push(key)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const isValid = isInputAllowed(key, keys)
|
|
294
|
+
if (!keys.includes(key) && !isValid) {
|
|
295
|
+
return strict.value ? errors.INVALID_EXPRESSION : 'warning'
|
|
296
|
+
}
|
|
297
|
+
|
|
219
298
|
const valid = isValidByDataType(
|
|
220
299
|
validateBracketValues(value),
|
|
221
|
-
|
|
300
|
+
getDataType(schema, aliases, key),
|
|
222
301
|
operator
|
|
223
302
|
)
|
|
224
303
|
|
|
@@ -236,8 +315,16 @@ const isValidByDataType = (
|
|
|
236
315
|
dataType: string | string[],
|
|
237
316
|
operator: string // TODO: use operator
|
|
238
317
|
): boolean => {
|
|
318
|
+
if (dataType === 'any') {
|
|
319
|
+
return true
|
|
320
|
+
}
|
|
321
|
+
|
|
239
322
|
if (Array.isArray(dataType)) {
|
|
240
|
-
|
|
323
|
+
let isOneOf = !!getValueMatch(dataType, str)
|
|
324
|
+
for (const type of dataType) {
|
|
325
|
+
isOneOf = isOneOf || isValidByDataType(str, type, operator)
|
|
326
|
+
}
|
|
327
|
+
return isOneOf
|
|
241
328
|
}
|
|
242
329
|
|
|
243
330
|
switch (dataType) {
|
|
@@ -283,6 +370,8 @@ const isValidString = (str: string) => {
|
|
|
283
370
|
}
|
|
284
371
|
|
|
285
372
|
const getOperatorByDataType = (dataType: string) => {
|
|
373
|
+
if (dataType === 'boolean') return ['$eq', '$neq']
|
|
374
|
+
|
|
286
375
|
return Object.keys(operatorToDataTypeMap).filter((key) => {
|
|
287
376
|
const value = operatorToDataTypeMap[key]
|
|
288
377
|
return value.length === 0 || value.includes(dataType)
|
|
@@ -300,19 +389,29 @@ const mapWordsToExpression = (words: string[]): Expression => {
|
|
|
300
389
|
}
|
|
301
390
|
}
|
|
302
391
|
|
|
303
|
-
const
|
|
392
|
+
const getDataType = (
|
|
304
393
|
schema: Schema,
|
|
394
|
+
aliases: Alias[],
|
|
305
395
|
key: string
|
|
306
396
|
): string | string[] | null => {
|
|
307
|
-
const
|
|
397
|
+
const aliasedKey = getAliasObjByAlias(aliases, key)?.key ?? key
|
|
398
|
+
|
|
399
|
+
const nestedKey = aliasedKey.split('.')
|
|
308
400
|
|
|
309
401
|
if (nestedKey.length === 1) {
|
|
310
402
|
return (schema[nestedKey[0]] as string | string[]) ?? null
|
|
311
403
|
}
|
|
312
404
|
|
|
313
405
|
let value = schema[nestedKey[0]] as Schema
|
|
406
|
+
if (!value) return null
|
|
407
|
+
|
|
314
408
|
for (let i = 1; i < nestedKey.length; i++) {
|
|
409
|
+
if (!value) return null
|
|
410
|
+
|
|
315
411
|
const nextKey = nestedKey[i]
|
|
412
|
+
if (!value[nextKey] && value['*']) {
|
|
413
|
+
return 'any'
|
|
414
|
+
}
|
|
316
415
|
value = (value[nextKey] as Schema) ?? null
|
|
317
416
|
}
|
|
318
417
|
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
/* eslint-disable no-empty */
|
|
2
2
|
|
|
3
|
-
import { isFinite, isObject, isString } from 'lodash'
|
|
3
|
+
import { isBoolean, isFinite, isNumber, isObject, isString } from 'lodash'
|
|
4
|
+
|
|
5
|
+
const GeneratePureValue = (value: any) => {
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
if (value === 'true') {
|
|
8
|
+
return true
|
|
9
|
+
}
|
|
10
|
+
if (value === 'false') {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const num = Number(value)
|
|
16
|
+
if (isFinite(num)) {
|
|
17
|
+
return num
|
|
18
|
+
}
|
|
19
|
+
return value.replaceAll('"', '').replaceAll("'", '')
|
|
20
|
+
} catch (e) {}
|
|
21
|
+
}
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
4
24
|
|
|
5
25
|
export const parseSmartQuery = (query: string) => {
|
|
6
26
|
const queryArr = query.split(' OR ')
|
|
@@ -15,44 +35,31 @@ export const parseSmartQuery = (query: string) => {
|
|
|
15
35
|
let key: string
|
|
16
36
|
let value: string | number | object
|
|
17
37
|
|
|
18
|
-
const cleanValue = (value: any) => {
|
|
19
|
-
if (typeof value === 'string') {
|
|
20
|
-
try {
|
|
21
|
-
const num = Number(value)
|
|
22
|
-
if (isFinite(num)) {
|
|
23
|
-
return num
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {}
|
|
26
|
-
return value.replaceAll('"', '').replaceAll("'", '')
|
|
27
|
-
}
|
|
28
|
-
return value
|
|
29
|
-
}
|
|
30
|
-
|
|
31
38
|
for (const term of andTerms) {
|
|
32
39
|
switch (true) {
|
|
33
40
|
case term.includes('>='):
|
|
34
41
|
[key, value] = term.split('>=').map((x) => x.trim())
|
|
35
|
-
andQuery[key] = { $gte:
|
|
42
|
+
andQuery[key] = { $gte: GeneratePureValue(value) }
|
|
36
43
|
break
|
|
37
44
|
case term.includes('<='):
|
|
38
45
|
[key, value] = term.split('<=').map((x) => x.trim())
|
|
39
|
-
andQuery[key] = { $lte:
|
|
46
|
+
andQuery[key] = { $lte: GeneratePureValue(value) }
|
|
40
47
|
break
|
|
41
48
|
case term.includes('>'):
|
|
42
49
|
[key, value] = term.split('>').map((x) => x.trim())
|
|
43
|
-
andQuery[key] = { $gt:
|
|
50
|
+
andQuery[key] = { $gt: GeneratePureValue(value) }
|
|
44
51
|
break
|
|
45
52
|
case term.includes('<'):
|
|
46
53
|
[key, value] = term.split('<').map((x) => x.trim())
|
|
47
|
-
andQuery[key] = { $lt:
|
|
54
|
+
andQuery[key] = { $lt: GeneratePureValue(value) }
|
|
48
55
|
break
|
|
49
56
|
case term.includes('!='):
|
|
50
57
|
[key, value] = term.split('!=').map((x) => x.trim())
|
|
51
|
-
andQuery[key] = { $ne:
|
|
58
|
+
andQuery[key] = { $ne: GeneratePureValue(value) }
|
|
52
59
|
break
|
|
53
60
|
case term.includes('='):
|
|
54
61
|
[key, value] = term.split('=').map((x) => x.trim())
|
|
55
|
-
andQuery[key] =
|
|
62
|
+
andQuery[key] = GeneratePureValue(value)
|
|
56
63
|
break
|
|
57
64
|
case term.includes('IN'):
|
|
58
65
|
[key, value] = term.split('IN').map((x) => x.trim())
|
|
@@ -64,15 +71,15 @@ export const parseSmartQuery = (query: string) => {
|
|
|
64
71
|
.split('NOT-IN')
|
|
65
72
|
.map((x) => x.trim())[1]
|
|
66
73
|
.split(',')
|
|
67
|
-
.map((x) =>
|
|
68
|
-
andQuery[key] = { $nin:
|
|
74
|
+
.map((x) => GeneratePureValue(x.trim()))
|
|
75
|
+
andQuery[key] = { $nin: GeneratePureValue(queryValue) }
|
|
69
76
|
} else {
|
|
70
77
|
queryValue = term
|
|
71
78
|
.split('IN')
|
|
72
79
|
.map((x) => x.trim())[1]
|
|
73
80
|
.split(',')
|
|
74
|
-
.map((x) =>
|
|
75
|
-
andQuery[key] = { $in:
|
|
81
|
+
.map((x) => GeneratePureValue(x.trim()))
|
|
82
|
+
andQuery[key] = { $in: GeneratePureValue(queryValue) }
|
|
76
83
|
}
|
|
77
84
|
break
|
|
78
85
|
}
|
|
@@ -90,104 +97,98 @@ export const stringifySmartQuery = (query: { [key: string]: any }) => {
|
|
|
90
97
|
let result = ''
|
|
91
98
|
|
|
92
99
|
for (const key in query) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
(subQuery
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
result += subQueries.join(' OR ')
|
|
103
|
-
}
|
|
104
|
-
continue
|
|
100
|
+
const value = query[key]
|
|
101
|
+
|
|
102
|
+
if (key === '$or') {
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
const subQueries = value.map(
|
|
105
|
+
(subQuery: { [key: string]: any }) =>
|
|
106
|
+
stringifySmartQuery(subQuery)
|
|
107
|
+
)
|
|
108
|
+
result += subQueries.join(' OR ')
|
|
105
109
|
}
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
if (result.length) {
|
|
114
|
+
result += ' AND '
|
|
115
|
+
}
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
if (isObject(value)) {
|
|
118
|
+
for (const operator in value) {
|
|
119
|
+
if (value.hasOwnProperty(operator)) {
|
|
120
|
+
let operatorValue = (
|
|
121
|
+
value as {
|
|
122
|
+
[key: string]: string | number | string[] | number[]
|
|
123
|
+
}
|
|
124
|
+
)[operator]
|
|
125
|
+
switch (operator) {
|
|
126
|
+
case '$eq':
|
|
127
|
+
result += `${key} = ${
|
|
128
|
+
isString(operatorValue)
|
|
129
|
+
? `'${operatorValue}'`
|
|
130
|
+
: operatorValue
|
|
131
|
+
}`
|
|
132
|
+
break
|
|
133
|
+
case '$ne':
|
|
134
|
+
result += `${key} != ${
|
|
135
|
+
isString(operatorValue)
|
|
136
|
+
? `'${operatorValue}'`
|
|
137
|
+
: operatorValue
|
|
138
|
+
}`
|
|
139
|
+
break
|
|
140
|
+
case '$gt':
|
|
141
|
+
result += `${key} > ${operatorValue}`
|
|
142
|
+
break
|
|
143
|
+
case '$gte':
|
|
144
|
+
result += `${key} >= ${operatorValue}`
|
|
145
|
+
break
|
|
146
|
+
case '$lt':
|
|
147
|
+
result += `${key} < ${operatorValue}`
|
|
148
|
+
break
|
|
149
|
+
case '$lte':
|
|
150
|
+
result += `${key} <= ${operatorValue}`
|
|
151
|
+
break
|
|
152
|
+
case '$in':
|
|
153
|
+
if (!Array.isArray(operatorValue)) {
|
|
154
|
+
operatorValue = [operatorValue] as
|
|
119
155
|
| string[]
|
|
120
156
|
| number[]
|
|
121
157
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
isString(operatorValue)
|
|
127
|
-
? `'${operatorValue}'`
|
|
128
|
-
: operatorValue
|
|
129
|
-
}`
|
|
130
|
-
break
|
|
131
|
-
case '$ne':
|
|
132
|
-
result += `${key} != ${
|
|
133
|
-
isString(operatorValue)
|
|
134
|
-
? `'${operatorValue}'`
|
|
135
|
-
: operatorValue
|
|
136
|
-
}`
|
|
137
|
-
break
|
|
138
|
-
case '$gt':
|
|
139
|
-
result += `${key} > ${operatorValue}`
|
|
140
|
-
break
|
|
141
|
-
case '$gte':
|
|
142
|
-
result += `${key} >= ${operatorValue}`
|
|
143
|
-
break
|
|
144
|
-
case '$lt':
|
|
145
|
-
result += `${key} < ${operatorValue}`
|
|
146
|
-
break
|
|
147
|
-
case '$lte':
|
|
148
|
-
result += `${key} <= ${operatorValue}`
|
|
149
|
-
break
|
|
150
|
-
case '$in':
|
|
151
|
-
if (!Array.isArray(operatorValue)) {
|
|
152
|
-
operatorValue = [operatorValue] as
|
|
153
|
-
| string[]
|
|
154
|
-
| number[]
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const inValues: string = (
|
|
158
|
-
operatorValue as any[]
|
|
158
|
+
|
|
159
|
+
const inValues: string = (operatorValue as any[])
|
|
160
|
+
.map((x: string | number) =>
|
|
161
|
+
isString(x) ? `'${x}'` : x
|
|
159
162
|
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const ninValues: string = (
|
|
174
|
-
operatorValue as any[]
|
|
163
|
+
.join(', ')
|
|
164
|
+
result += `${key} IN ${inValues} `
|
|
165
|
+
break
|
|
166
|
+
case '$nin':
|
|
167
|
+
if (!Array.isArray(operatorValue)) {
|
|
168
|
+
operatorValue = [operatorValue] as
|
|
169
|
+
| string[]
|
|
170
|
+
| number[]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const ninValues: string = (operatorValue as any[])
|
|
174
|
+
.map((x: string | number) =>
|
|
175
|
+
isString(x) ? `'${x}'` : x
|
|
175
176
|
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
break
|
|
183
|
-
default:
|
|
184
|
-
throw new Error(`Invalid operator: ${operator}`)
|
|
185
|
-
}
|
|
177
|
+
.join(', ')
|
|
178
|
+
|
|
179
|
+
result += `${key} NOT-IN ${ninValues}`
|
|
180
|
+
break
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`Invalid operator: ${operator}`)
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
|
-
} else {
|
|
189
|
-
result += `${key} = '${value}'`
|
|
190
185
|
}
|
|
186
|
+
} else if (isNumber(value)) {
|
|
187
|
+
result += `${key} = ${value}`
|
|
188
|
+
} else if (isBoolean(value)) {
|
|
189
|
+
result += `${key} = ${value}`
|
|
190
|
+
} else {
|
|
191
|
+
result += `${key} = '${value}'`
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|